web-dev-qa-db-ja.com

LATERALを使用したPostgreSQLのランダム化の組み合わせ

次の例では、グループ_からランダムに行を抽出するテーブルfooがあります。

_CREATE TABLE foo (
  line INT
);
INSERT INTO foo (line)
SELECT generate_series(0, 999, 1);
_

_line % 10_でグループ化するとします。私はこれを行うことができます:

_SELECT DISTINCT ON (bin) bin, line
FROM (
    SELECT line, line % 10 AS bin, random() x
    FROM foo
    ORDER BY x
) X
_

私がやりたいのは、各ビンから数回ランダムにピックを取得することです。 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;
_

ただし、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
...
_

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;
_

だから私の質問は、なぜ最初の方法がうまくいかなかったのですか?

編集:問題は、サブクエリが何らかの方法で相関している場合にLATERALがforループのようにしか機能しないことにあるようです(@ypercubeのコメントのおかげです)。したがって、私の元のアプローチは、次の小さな変更を追加することで修正できます

_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;
_
6
beldaz

_DISTINCT ON_の代わりにLIMIT (3)を使用して、このようなクエリを記述します。

generate_series(0, 9)は、すべての個別のビンを取得するために使用されます。 「ビン」が0から9までのすべての整数ではない場合は、代わりに_(SELECT DISTINCT line % 10 FROM foo) AS g (bin)_を使用できます。

_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 ;
_

また、出力でrandom()番号が必要ない場合は、サブクエリでORDER BY random()を使用して、selectおよびorder by句からxを削除できます-または_ORDER BY d.x_を_ORDER BY d.line_に置き換えます。

4
ypercubeᵀᴹ

私がやりたいのは、各ビンから数回ランダムにピックを取得することです。

この問題を解決する方法はたくさんあります。それぞれがランダム性を導入し、時間がかかります。

  1. TABLESAMPLE SYSTEMおよび tsm_system_rows
  2. TABLESAMPLE BERNOULLI
  3. アドホックビンを作成し、統計でサイコロを振ります。
  4. アドホックビンを作成し、それらをランダムに並べて選択します。

ほとんどの場合、TABLEAMPLE SYSTEMおよびtsm_system_rowsは、テーブルの「公正な」サンプリングを取得するのに十分です。テーブル全体を訪問する必要がないという追加の利点があります。

より等間隔のサンプルが必要な場合は、TABLESAMPLE BERNOULLIはテーブル全体を訪問し、内部のすべてのページから選択します。

アドホックに進みたい場合は、これで十分でしょう。

SELECT *
FROM (
  SELECT dense_rank() OVER (PARTITION BY bin ORDER BY random()), *
  FROM (
    SELECT line % 10 AS bin, line
    FROM foo                          
  ) AS t
) AS t                       
WHERE dense_rank <= 3
ORDER BY line;
3
Evan Carroll