PostgreSQL 随机组合与 LATERAL

bel*_*daz 7 postgresql random subquery

在下面的示例中,我有一个表foo,我想从中随机选择每组的一行。

CREATE TABLE foo (
  line INT
);
INSERT INTO foo (line)
SELECT generate_series(0, 999, 1);
Run Code Online (Sandbox Code Playgroud)

假设我想按 分组line % 10。我可以这样做:

SELECT DISTINCT ON (bin) bin, line
FROM (
    SELECT line, line % 10 AS bin, random() x
    FROM foo
    ORDER BY x
) X
Run Code Online (Sandbox Code Playgroud)

我想做的是多次从每个垃圾箱中随机选择。我原以为我能和做到这一点generate_series(),并LATERAL

SELECT i, line, bin
FROM
(
 SELECT generate_series(1,3) i
) m,
LATERAL
(SELECT DISTINCT ON (bin) bin, line
FROM (
    SELECT line, line % 10 bin, random() x
    FROM foo
    ORDER BY x
) X
ORDER BY bin) Q
ORDER BY bin, i;
Run Code Online (Sandbox Code Playgroud)

但是,当我在 PostgreSQL 9.5 中执行此操作时,我发现每次迭代line的给定bin值都相同i,例如,

i;line;bin
1;530;0
2;530;0
3;530;0
1;611;1
2;611;1
3;611;1
...
Run Code Online (Sandbox Code Playgroud)

我很困惑,因为我认为包含 的子查询random()对于generate_series().

编辑:我意识到我可以通过生成更多组合并从中选择来实现相同的目标

SELECT DISTINCT ON (bin, round) round, bin, line
FROM (
    SELECT line, line % 10 as bin, round
    FROM foo, generate_series(1,3) round
    ORDER BY bin, random()
) X;
Run Code Online (Sandbox Code Playgroud)

所以我的问题很简单,为什么第一种方法不起作用?

编辑:问题似乎是,如果子查询以某种方式相关(感谢@ypercube 的评论),则 LATERAL 仅充当 for 循环。因此,可以通过添加以下小的更改来修复我原来的方法

SELECT i, line, bin
FROM
(
 SELECT generate_series(1,3) i
) m,
LATERAL
(
SELECT DISTINCT ON (bin) bin, line
FROM (
    SELECT line, line % 10 bin, m.i, random() x -- <NOTE m.i HERE
    FROM foo
    ORDER BY x
) X
ORDER BY bin
LIMIT 3
) Q
ORDER BY bin, i;
Run Code Online (Sandbox Code Playgroud)

ype*_*eᵀᴹ 5

我会像这样编写查询,使用LIMIT (3)而不是DISTINCT ON.

generate_series(0, 9)是用来获取所有不同的垃圾箱。(SELECT DISTINCT line % 10 FROM foo) AS g (bin)如果“bins”不是从 0 到 9 的所有整数,您可以改用:

SELECT 
    g.bin, 
    ROW_NUMBER() OVER (PARTITION BY g.bin ORDER BY d.x) AS i,
    d.* 
FROM 
    generate_series(0, 9) AS g (bin), 
  LATERAL 
    ( SELECT f.*, random() x 
      FROM foo AS f 
      WHERE  f.line % 10 = g.bin 
      ORDER BY x 
      LIMIT 3
    ) AS d
ORDER BY 
    bin, x ;
Run Code Online (Sandbox Code Playgroud)

此外,如果您不需要random()输出中的数字,您可以ORDER BY random()在子查询中使用并x从 select 和 order by 子句中删除- 或替换ORDER BY d.xORDER BY d.line.