ランダムな行を選択する最良の方法PostgreSQL


344

PostgreSQLで行をランダムに選択したいので、これを試しました:

select * from table where random() < 0.01;

しかし、他の人はこれをお勧めします:

select * from table order by random() limit 1000;

5億行の非常に大きなテーブルがあるので、高速にしたい。

どちらのアプローチが良いですか?違いは何ですか?ランダムな行を選択する最良の方法は何ですか?


1
こんにちはジャック、あなたの応答をありがとう、実行時間は順番に遅くなりますが、もしあればどちらが違うのか知りたいです...
nanounanue

ええと…どういたしまして では、さまざまなアプローチのベンチマークを試みましたか?

あり多くの方法が速いです。それはすべて、要件と作業する必要のあるものに依存します。正確に1000行が必要ですか?テーブルには数値IDがありますか?ギャップがない/少ない/多い?速度はどのくらい重要ですか?時間単位あたりのリクエスト数は?リクエストごとに異なるセットが必要ですか、それとも定義されたタイムスライスで同じにすることができますか?
Erwin Brandstetter、2011

6
最初のオプション "(random()<0.01)"は、0.01未満の乱数がない場合に応答で行を取得できない可能性があるため、数学的には正しくありません。しきい値以上。2番目のオプションは常に正しい
Herme

1
あなただけの1行を選択したい場合は、この質問を参照してください。stackoverflow.com/q/5297396/247696
Flimm

回答:


230

あなたの仕様(プラスコメントの追加情報)を考えると、

  • ギャップがほとんどない(または適度に少ない)数値ID列(整数)があります。
  • 明らかに、書き込み操作はありません。
  • ID列にはインデックスを付ける必要があります!主キーはうまく機能します。

以下のクエリでは、大きなテーブルの順次スキャンは必要ありません。インデックススキャンのみが必要です。

まず、メインクエリの見積もりを取得します。

SELECT count(*) AS ct              -- optional
     , min(id)  AS min_id
     , max(id)  AS max_id
     , max(id) - min(id) AS id_span
FROM   big;

おそらく高価な部分はcount(*)(巨大なテーブルの場合)だけです。上記の仕様を考えると、それは必要ありません。見積もりは問題なく行われ、ほとんど無料で利用できます(詳細な説明はこちら):

SELECT reltuples AS ct FROM pg_class WHERE oid = 'schema_name.big'::regclass;

限りctではありません多くのより小さいid_span、クエリは、他のアプローチをアウトパフォームします。

WITH params AS (
    SELECT 1       AS min_id           -- minimum id <= current min id
         , 5100000 AS id_span          -- rounded up. (max_id - min_id + buffer)
    )
SELECT *
FROM  (
    SELECT p.min_id + trunc(random() * p.id_span)::integer AS id
    FROM   params p
          ,generate_series(1, 1100) g  -- 1000 + buffer
    GROUP  BY 1                        -- trim duplicates
    ) r
JOIN   big USING (id)
LIMIT  1000;                           -- trim surplus
  • id空間で乱数を生成します。「ギャップが少ない」ため、取得する行の数に10%(ブランクを簡単にカバーするのに十分)を追加します。

  • それぞれidが偶然に複数回選択される可能性があるため(大きなidスペースでは非常にまれです)、生成された数値をグループ化(またはを使用DISTINCT)します。

  • idsを大きなテーブルに結合します。これは、インデックスが設定されていると非常に高速になります。

  • 最後にid、だましや隙間で食べられなかった余剰分を取り除きます。すべての行は、選択される完全に等しいチャンスを持っています。

短縮版

このクエリを簡略化できます。上記のクエリのCTEは、教育目的のみです。

SELECT *
FROM  (
    SELECT DISTINCT 1 + trunc(random() * 5100000)::integer AS id
    FROM   generate_series(1, 1100) g
    ) r
JOIN   big USING (id)
LIMIT  1000;

rCTEで絞り込む

特に、ギャップと見積もりについて確信が持てない場合。

WITH RECURSIVE random_pick AS (
   SELECT *
   FROM  (
      SELECT 1 + trunc(random() * 5100000)::int AS id
      FROM   generate_series(1, 1030)  -- 1000 + few percent - adapt to your needs
      LIMIT  1030                      -- hint for query planner
      ) r
   JOIN   big b USING (id)             -- eliminate miss

   UNION                               -- eliminate dupe
   SELECT b.*
   FROM  (
      SELECT 1 + trunc(random() * 5100000)::int AS id
      FROM   random_pick r             -- plus 3 percent - adapt to your needs
      LIMIT  999                       -- less than 1000, hint for query planner
      ) r
   JOIN   big b USING (id)             -- eliminate miss
   )
SELECT *
FROM   random_pick
LIMIT  1000;  -- actual limit

基本クエリで小さな余剰を処理できます。ギャップが多すぎて最初の反復で十分な行が見つからない場合、rCTEは再帰的な項で反復を続けます。IDスペースに比較的少ないギャップがまだ必要です。そうしないと、制限に達する前に再帰が空になる可能性があります。または、パフォーマンスを最適化する目的に反する十分に大きなバッファーで開始する必要があります。

重複はUNION、rCTEのによって排除されます。

アウターLIMITは、十分な行があるとすぐにCTEを停止します。

このクエリは、利用可能なインデックスを使用し、実際にランダムな行を生成し、制限を満たすまで停止しないように注意深く作成されています(再帰が実行されない限り)。あなたがそれを書き直すつもりなら、ここにいくつかの落とし穴があります。

関数にラップ

さまざまなパラメーターで繰り返し使用する場合:

CREATE OR REPLACE FUNCTION f_random_sample(_limit int = 1000, _gaps real = 1.03)
  RETURNS SETOF big AS
$func$
DECLARE
   _surplus  int := _limit * _gaps;
   _estimate int := (           -- get current estimate from system
      SELECT c.reltuples * _gaps
      FROM   pg_class c
      WHERE  c.oid = 'big'::regclass);
BEGIN

   RETURN QUERY
   WITH RECURSIVE random_pick AS (
      SELECT *
      FROM  (
         SELECT 1 + trunc(random() * _estimate)::int
         FROM   generate_series(1, _surplus) g
         LIMIT  _surplus           -- hint for query planner
         ) r (id)
      JOIN   big USING (id)        -- eliminate misses

      UNION                        -- eliminate dupes
      SELECT *
      FROM  (
         SELECT 1 + trunc(random() * _estimate)::int
         FROM   random_pick        -- just to make it recursive
         LIMIT  _limit             -- hint for query planner
         ) r (id)
      JOIN   big USING (id)        -- eliminate misses
   )
   SELECT *
   FROM   random_pick
   LIMIT  _limit;
END
$func$  LANGUAGE plpgsql VOLATILE ROWS 1000;

コール:

SELECT * FROM f_random_sample();
SELECT * FROM f_random_sample(500, 1.05);

このジェネリックを任意のテーブルで機能するようにすることもできます。PK列の名前とテーブルをポリモーフィック型として使用しEXECUTEます。しかし、それはこの質問の範囲を超えています。見る:

可能な代替

要件で繰り返し呼び出しの同一セットが許可されている場合(繰り返し呼び出しについて話している場合)、具体化されたビューを検討します。上記のクエリを1回実行し、結果をテーブルに書き込みます。ユーザーは、驚くほどの速さで準ランダム選択を取得します。選択した間隔またはイベントでランダムピックを更新します。

Postgres 9.5が導入 TABLESAMPLE SYSTEM (n)

nパーセンテージはどこですか。マニュアル:

BERNOULLIそしてSYSTEMサンプリング方法はそれぞれとして表さ試料テーブルの一部である単一の引数、受け入れ 0と100の間の割合。この引数には、任意のreal値の式を指定できます。

大胆な強調鉱山。それはだ非常に速いが、結果は正確にランダムではありません。再びマニュアル:

このSYSTEMメソッドはBERNOULLI、小さいサンプリング率が指定されている場合のメソッドよりも大幅に高速ですが、クラスタリング効果の結果として、ランダムでないテーブルのサンプルを返す場合があります。

返される行の数は大幅に異なる場合があります。この例では、およそ 1000行を取得します。

SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);

関連:

または、追加のモジュールtsm_system_rowsをインストールして、リクエストされた行の数を正確に取得し(十分な場合)、より便利な構文を許可します。

SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);

詳細については、Evanの回答を参照してください。

しかし、それはまだ完全にランダムではありません。


tテーブルはどこに定義されていますか?tではなくrにする必要がありますか?
リュックM

1
@LucM:これはここで定義されています:JOIN bigtbl t、の略ですJOIN bigtbl AS ttテーブルエイリアスですbigtbl。その目的は構文を短くすることですが、この特定のケースでは必要ありません。私の回答ではクエリを簡略化し、シンプルなバージョンを追加しました。
Erwin Brandstetter、2012年

generate_series(1,1100)の値の範囲の目的は何ですか?
Awesome-o

@ Awesome-o:目標は1000行を取得することです。いくつかのギャップまたは(可能性は低いですが)重複する乱数を補正するために、追加の10%から始めます...説明は私の答えです。
Erwin Brandstetter、2014

アーウィン、私はあなたの「可能な選択肢」のバリエーションを投稿しました:stackoverflow.com/a/23634212/430128。あなたの考えに興味があります。
ラマン

100

を使用して、両方の実行プランを調べて比較できます。

EXPLAIN select * from table where random() < 0.01;
EXPLAIN select * from table order by random() limit 1000;

大きなテーブルの簡単なテスト1は、ORDER BY最初にテーブル全体を並べ替え、最初の1000アイテムを選択することを示しています。大きなテーブルを並べ替えると、そのテーブルが読み取られるだけでなく、一時ファイルの読み取りと書き込みも行われます。はwhere random() < 0.1テーブル全体を1回だけスキャンします。

大きなテーブルの場合、1回の完全なテーブルスキャンでも時間がかかる場合があるため、これは望ましくない場合があります。

3番目の提案は

select * from table where random() < 0.01 limit 1000;

これは、1000行が見つかるとすぐにテーブルスキャンを停止するため、すぐに戻ります。もちろん、これは少しランダム性を低下させますが、おそらくこれはあなたの場合には十分です。

編集:この考慮事項に加えて、これについて既に尋ねられた質問をチェックするかもしれません。クエリを使用すると、[postgresql] randomかなりの数のヒットが返されます。

そして、さらにいくつかのアプローチを概説するdepezのリンクされた記事:


1「テーブル全体がメモリに収まりません」のように「大きい」。


1
順序付けを行うための一時ファイルの書き込みについての良い点。それは確かに大ヒットです。できたらrandom() < 0.02、そのリストをシャッフルし、それからlimit 1000!並べ替えは数千行で安価になります(笑)。
ドナルドマイナー、2011

「select * from random where)<0.05 limit 500;」postgresqlの簡単な方法の1つです。結果の5%を選択し、一度に500行以下を処理する必要があるプロジェクトの1つでこれを利用しました。
tgharold 2014年

500mの行テーブルでサンプルを取得するために、なぜO(n)フルスキャンを検討するのでしょうか。大きなテーブルでは途方もなく遅く、完全に不要です。
mafu

75

random()によるpostgresqlの順序。ランダムな順序で行を選択します。

select your_columns from your_table ORDER BY random()

個別のrandom()によるpostgresqlの順序:

select * from 
  (select distinct your_columns from your_table) table_alias
ORDER BY random()

postgresqlをランダムな制限で1行に並べます。

select your_columns from your_table ORDER BY random() limit 1

1
select your_columns from your_table ORDER BY random() limit 1〜45mil行上の幹部に2分かかる
グエン

これをスピードアップする方法はありますか?
CpILL

43

PostgreSQL 9.5以降、テーブルからランダムな要素を取得するための新しい構文があります。

SELECT * FROM mytable TABLESAMPLE SYSTEM (5);

この例では、の要素の5%が得られますmytable

このブログ投稿の詳細については、http//www.postgresql.org/docs/current/static/sql-select.htmlをご覧ください。


3
ドキュメントからの重要なメモ:「SYSTEMメソッドは、ブロックレベルのサンプリングを行い、各ブロックが選択される可能性が指定されています。選択された各ブロックのすべての行が返されます。SYSTEMメソッドは、サンプリング率が小さい場合、BERNOULLIメソッドよりも大幅に高速です。が指定されていますが、クラスタリング効果の結果として、ランダムではないテーブルのサンプルが返される場合があります。
Tim、

1
割合ではなく行数を指定する方法はありますか?
Flimm

4
を使用TABLESAMPLE SYSTEM_ROWS(400)して、400のランダムな行のサンプルを取得できます。このステートメントを使用するには、組み込みのtsm_system_rows拡張機能を有効にする必要があります。
Micka Lel Le Baillif

残念ながら、削除では機能しません。
ガデルカリーム

27

ORDER BYを使用する方が低速になります。

select * from table where random() < 0.01;レコードごとに移動し、ランダムにフィルタリングするかどうかを決定します。これは、O(N)各レコードを一度だけチェックする必要があるためです。

select * from table order by random() limit 1000;テーブル全体をソートし、最初の1000を選択しますO(N * log N)。舞台裏でのブードゥーマジックのほかに、次の順序でです。

random() < 0.011つの欠点は、出力レコードの数が可変になることです。


ランダムに並べ替えるよりも、一連のデータをシャッフルする方がよいことに注意してください。Fisher-YatesShuffleは、で実行されO(N)ます。しかし、SQLにシャッフルを実装することは、かなり難しいように思えます。


3
ただし、最初の例の最後に制限1を追加できない理由はありません。唯一の問題は、レコードが返されない可能性があることです。そのため、コードでそれを考慮する必要があります。
Relequestual

フィッシャーイェイツの問題は、データセットから選択するために、データセット全体をメモリに保持する必要があることです。非常に大規模なデータセットには
適さ

16

ここで私のために働く決定です。理解して実行するのは非常に簡単だと思います。

SELECT 
  field_1, 
  field_2, 
  field_2, 
  random() as ordering
FROM 
  big_table
WHERE 
  some_conditions
ORDER BY
  ordering 
LIMIT 1000;

6
このソリューションはORDER BY random()うまく機能していると思いますが、大きなテーブルを扱う場合は効率的ではないかもしれません。
Anh Cao

15
select * from table order by random() limit 1000;

必要な行数がわかっている場合は、を確認してくださいtsm_system_rows

tsm_system_rows

moduleは、SELECTコマンドのTABLESAMPLE句で使用できるテーブルサンプリングメソッドSYSTEM_ROWSを提供します。

このテーブルサンプリングメソッドは、読み取る行の最大数である単一の整数引数を受け入れます。テーブルに十分な行が含まれていない場合を除いて、結果のサンプルには常に正確にその数の行が含まれます。その場合、テーブル全体が選択されます。組み込みのSYSTEMサンプリング方法と同様に、SYSTEM_ROWSはブロックレベルのサンプリングを実行するため、サンプルは完全にランダムではありませんが、特に少数の行のみが要求される場合は、クラスタリングの影響を受ける可能性があります。

最初に拡張機能をインストールします

CREATE EXTENSION tsm_system_rows;

次に、クエリ

SELECT *
FROM table
TABLESAMPLE SYSTEM_ROWS(1000);

2
私はあなたの追加された答えへのリンクを追加しました、それは組み込みのSYSTEM方法に対する顕著な改善です。
Erwin Brandstetter 2016

ここで質問(ランダムな単一レコード)に答えましたが、その間、および拡張機能のベンチマークとテストをかなり行いました。私の知る限り、ランダムな行の選択を最小限にとどめること以外は事実上役に立たない。私の分析の妥当性やその他について簡単に見てコメントしていただければ幸いです。tsm_system_rowstsm_system_time
Vérace

6

1つの行だけが必要な場合は、offsetから派生した計算を使用できますcount

select * from table_name limit 1
offset floor(random() * (select count(*) from table_name));

2

Erwin Brandstetterによって概説されている具体化されたビュー「可能な代替」のバリエーションが可能です。

たとえば、返されるランダム化された値の重複を避けたいとします。したがって、(ランダム化されていない)値のセットを含むプライマリテーブルにブール値を設定する必要があります。

これが入力テーブルであると仮定します:

id_values  id  |   used
           ----+--------
           1   |   FALSE
           2   |   FALSE
           3   |   FALSE
           4   |   FALSE
           5   |   FALSE
           ...

ID_VALUES必要に応じてテーブルにデータを入力します。次に、Erwinで説明されているように、ID_VALUESテーブルを一度ランダム化するマテリアライズドビューを作成します。

CREATE MATERIALIZED VIEW id_values_randomized AS
  SELECT id
  FROM id_values
  ORDER BY random();

マテリアライズドビューには使用済みの列が含まれていないことに注意してください。これはすぐに古くなるためです。また、ビューには、id_valuesテーブルにある可能性のある他の列を含める必要もありません。

ランダムな値を取得(および「消費」)するには、UPDATE-RETURNING onを使用して、結合でid_values選択し、必要な基準を適用して関連する可能性のみを取得します。例えば:id_valuesid_values_randomized

UPDATE id_values
SET used = TRUE
WHERE id_values.id IN 
  (SELECT i.id
    FROM id_values_randomized r INNER JOIN id_values i ON i.id = r.id
    WHERE (NOT i.used)
    LIMIT 5)
RETURNING id;

変更LIMIT必要に応じて-あなただけ一度に一つのランダムな値、変更が必要な場合LIMITには1

の適切なインデックスid_valuesを使用すると、UPDATE-RETURNINGはほとんど負荷なしで非常に高速に実行されるはずです。1回のデータベースラウンドトリップでランダム化された値を返します。「適格」行の基準は、必要に応じて複雑にすることができます。新しい行はid_valuesいつでもテーブルに追加でき、マテリアライズドビューが更新されるとすぐにアプリケーションからアクセスできるようになります(オフピーク時に実行される可能性があります)。マテリアライズドビューの作成と更新は遅くなりますが、新しいIDがid_valuesテーブルに追加されたときにのみ実行する必要があります。


とても興味深い。選択するだけでなく、pg_try_advisory_xact_lockを使用したselect..for更新を使用して更新する必要がある場合、それは機能しますか?(つまり、多くの同時読み取りと書き込みが必要です)
Mathieu

1

私の経験からの教訓:

offset floor(random() * N) limit 1よりも速くはありませんorder by random() limit 1

offsetPostgresでの並べ替えの時間を節約できるため、このアプローチの方が高速だと思いました。そうではなかったことがわかりました。


0

rtypeで呼び出される列を追加しますserial。インデックスr

200,000行あると想定して、乱数を生成します。nここで、0 n<<= 200、000です。

で行を選択しr > n、並べ替えてASC最小のものを選択します。

コード:

select * from YOUR_TABLE 
where r > (
    select (
        select reltuples::bigint AS estimate
        from   pg_class
        where  oid = 'public.YOUR_TABLE'::regclass) * random()
    )
order by r asc limit(1);

コードは自明です。中央のサブクエリは、https: //stackoverflow.com/a/7945274/1271094からテーブルの行数をすばやく推定するために使用されます

アプリケーションレベルnでは、行数>または複数の行を選択する必要がある場合に、ステートメントを再度実行する必要があります。


短くてエレガントなのでこれが好きです:)そしてそれを改善する方法も見つけました:EXPLAIN ANALYZEは、このように、random()がdoubleを返すのでPKEYインデックスは使用されないが、PKEYはBIGINTを必要とすることを教えてくれます。
fxtentacle 2016

select * from YOUR_TABLE where r>(select(select reltuples :: bigint AS見積もりfrom pg_class where oid = 'public.YOUR_TABLE' :: regclass)* random()):: BIGINT order by r asc limit(1);
fxtentacle 2016

0

私はパーティーに少し遅れていることを知っていますが、pg_sampleというこの素晴らしいツールを見つけました。

pg_sample -参照整合性を維持しながら、大きなPostgreSQLデータベースから小さなサンプルデータセットを抽出します。

私はこれを350M行のデータベースで試してみましたが、非常に高速で、ランダム性についてはわかりません。

./pg_sample --limit="small_table = *" --limit="large_table = 100000" -U postgres source_db | psql -U postgres target_db
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.