Postgresでのランダムな行の迅速な選択


95

数百万の行を含むpostgresのテーブルがあります。インターネットで確認したところ、次のことがわかりました

SELECT myid FROM mytable ORDER BY RANDOM() LIMIT 1;

それは機能しますが、本当に遅いです...そのクエリを作成する別の方法、またはすべてのテーブルを読み取らずにランダムな行を選択する直接的な方法はありますか?ちなみに「myid」は整数ですが、空のフィールドにすることもできます。


1
あなたが複数のランダムな行を選択したい場合は、この質問を参照してください。stackoverflow.com/q/8674718/247696
Flimm

回答:


97

あなたは使って実験することがありますOFFSETのように、

SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1;

Nの行数ですmytable。最初にを実行SELECT COUNT(*)して、の値を把握する必要がある場合がありますN

更新(Antony Hatchkinsによる)

floorここで使用する必要があります:

SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1;

2行のテーブルを考えます。最も近いintへの暗黙的な丸めのため、0行をrandom()*N生成0 <= x < 2してSELECT myid FROM mytable OFFSET 1.7 LIMIT 1;返します。


Nより小さいSELECT COUNT(*)値を使用するのは理にかなっていますか?つまり、表のすべての値ではなく一部のみを使用していますか?
フアン

@Juanそれはあなたの要件に依存します。
NPE

使用したEXPLAIN SELECT ...Nの異なる値がクエリのために同じコストを与えるとし、その後、私は推測するNの最大値のために行く方が良いです
フアン・

3
以下の私の回答のバグ修正を参照してください
Antony Hatchkins '26 / 10/26

2
これには、1つのエラーによるオフがあります。最初の行を返すことはなく、最後の行の後に行を返そうとするため、エラー1 / COUNT(*)が生成されます。
Ian

60

PostgreSQL 9.5では、サンプル選択を大幅に高速化する新しいアプローチが導入されました:TABLESAMPLE

構文は

SELECT * FROM my_table TABLESAMPLE BERNOULLI(percentage);
SELECT * FROM my_table TABLESAMPLE SYSTEM(percentage);

正確なパーセンテージを計算するには、テーブルのCOUNTを知る必要があるため、1つの行のみを選択する場合、これは最適なソリューションではありません。

遅いCOUNTを回避し、1行から数十億行のテーブルに対して高速のTABLESAMPLEを使用するには、次のようにします。

 SELECT * FROM my_table TABLESAMPLE SYSTEM(0.000001) LIMIT 1;
 -- if you got no result:
 SELECT * FROM my_table TABLESAMPLE SYSTEM(0.00001) LIMIT 1;
 -- if you got no result:
 SELECT * FROM my_table TABLESAMPLE SYSTEM(0.0001) LIMIT 1;
 -- if you got no result:
 SELECT * FROM my_table TABLESAMPLE SYSTEM(0.001) LIMIT 1;
 ...

これはそれほどエレガントに見えないかもしれませんが、おそらく他のどの回答よりも高速です。

BERNULLIまたはSYSTEMを使用するかどうかを決定するには、http: //blog.2ndquadrant.com/tablesample-in-postgresql-9-5-2/で違いについて読んでください。


2
これは他のどの回答よりもはるかに速くて簡単です-これが一番上にあるはずです。
ヘイデンシフ2017

1
なぜサブクエリを使用してカウントを取得できないのですか?SELECT * FROM my_table TABLESAMPLE SYSTEM(SELECT 1/COUNT(*) FROM my_table) LIMIT 1;
machineghost

2
@machineghost "遅いCOUNTを回避するには..." ...データが小さすぎて、適切な時間でカウントできる場合は、それを試してください。:-)
alfonx

2
@machineghost SELECT reltuples FROM pg_class WHERE relname = 'my_table'カウントの推定に使用します。
Hynek -Pichi- Vychodil

@ Hynek-Pichi-Vychodil非常に良い入力!見積もりが古くなっていないことを確認するには、最近VACUUM ANALYZEdを実行する必要があります。通常、巨大なテーブルはそれほど速く成長しません...ありがとう!
alfonx

34

私はこれをサブクエリで試してみましたが、うまくいきました。オフセット、少なくともPostgresql v8.4.4では正常に動作します。

select * from mytable offset random() * (select count(*) from mytable) limit 1 ;

実際、v8.4はこれが機能するために不可欠であり、8.3以下では機能しません。
Antony Hatchkins、

1
以下の私の回答のバグ修正を参照してください
Antony Hatchkins '26 / 10/26

30

使用する必要がありますfloor

SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1;

2行のテーブルを考えます。random()*N0 <= x <2を生成し、たとえばSELECT myid FROM mytable OFFSET 1.7 LIMIT 1;、最も近いintへの暗黙的な丸めのために0行を返します。
アントニーハッチキンズ

残念ながら、より高いLIMITを使用する場合、これは機能しません... 3つの項目を取得する必要があるため、ORDER BY RANDOM()構文を使用する必要があります。
Alexis Wilke、2012年

1
3つの連続したクエリは1つよりも高速ですがorder by random()3*O(N) < O(NlogN)実際の数値はインデックスによりわずかに異なります。
アントニーハッチキンズ

私の問題は、3つの項目を区別する必要があり、OFFSETが決定WHERE myid NOT IN (1st-myid)WHERE myid NOT IN (1st-myid, 2nd-myid)行うため機能しないことです。うーん...私は2番目と3番目のSELECTでNを1と2減らすことができると思います。
Alexis Wilke

あなたや誰かがこの答えを拡張して、なぜ私が使う必要があるのかについての答えを教えてくれませんかfloor()?それはどんな利点を提供しますか?
ADTC 2014

14

いくつかの異なるオプションについては、このリンクをチェックしてください。 http://www.depesz.com/index.php/2007/09/16/my-thoughts-on-getting-random-row/

更新: (A.ハッチキンス)

(非常に)長い記事の要約は次のとおりです。

著者は4つのアプローチをリストします:

1) ORDER BY random() LIMIT 1; -遅い

2) ORDER BY id where id>=random()*N LIMIT 1 -ギャップがある場合は不均一

3)ランダムな列-時々更新する必要があります

4)カスタムランダム集計 -狡猾な方法、遅い可能性があります:random()はN回生成する必要があります

を使用してメソッド#2を改善することを提案します

5)ORDER BY id where id=random()*N LIMIT 1 結果が空の場合、後続の再クエリ。


彼らはなぜオフセットをカバーしなかったのかしら?ORDER列を使用することは、ランダムな行を取得するだけでは問題外です。幸いにも、OFFSETは答えで十分カバーされています。
androidguy 2017年

4

ランダムな行をフェッチする最も簡単で最速の方法は、tsm_system_rows拡張機能を使用することです。

CREATE EXTENSION IF NOT EXISTS tsm_system_rows;

次に、必要な行の正確な数を選択できます。

SELECT myid  FROM mytable TABLESAMPLE SYSTEM_ROWS(1);

これは、PostgreSQL 9.5以降で使用できます。

参照:https : //www.postgresql.org/docs/current/static/tsm-system-rows.html


1
公正な警告、これは完全にランダムではありません。小さなテーブルでは、常に最初の行を順番に返すようにしました。
ベンAubinの

1
はい、これはドキュメントで明確に説明されています(上記のリンク):«組み込みのSYSTEMサンプリングメソッドと同様に、SYSTEM_ROWSはブロックレベルのサンプリングを実行するため、サンプルは完全にランダムではありませんが、クラスタリングの影響を受ける可能性があります。行数が要求されます。»。データセットが小さい場合は、ORDER BY random() LIMIT 1;十分高速である必要があります。
daamien

私はそれを観た。リンクをクリックしない人、またはリンクが将来死ぬかどうかを明確にしたかっただけです。
ベンAubinの

1
これは、クエリを実行してから1つまたはいくつかのレコードをランダムに選択するのとは対照的または比較して、テーブルからランダムな行を選択してからフィルタリングする場合にのみ機能することにも注意してください。
nomen

3

なしで非常に高速なソリューションを思いつきましたTABLESAMPLE。よりはるかに速いOFFSET random()*N LIMIT 1。テーブル数も必要ありません。

たとえば、ランダムではあるが予測可能なデータを使用して式インデックスを作成することmd5(primary key)です。

以下は、100万行のサンプルデータを使用したテストです。

create table randtest (id serial primary key, data int not null);

insert into randtest (data) select (random()*1000000)::int from generate_series(1,1000000);

create index randtest_md5_id_idx on randtest (md5(id::text));

explain analyze
select * from randtest where md5(id::text)>md5(random()::text)
order by md5(id::text) limit 1;

結果:

 Limit  (cost=0.42..0.68 rows=1 width=8) (actual time=6.219..6.220 rows=1 loops=1)
   ->  Index Scan using randtest_md5_id_idx on randtest  (cost=0.42..84040.42 rows=333333 width=8) (actual time=6.217..6.217 rows=1 loops=1)
         Filter: (md5((id)::text) > md5((random())::text))
         Rows Removed by Filter: 1831
 Total runtime: 6.245 ms

このクエリは時々(約1 / Number_of_rowsの確率で)0行を返す可能性があるため、確認して再実行する必要があります。また、確率はまったく同じではありません。一部の行は他の行よりも確率が高くなっています。

比較のために:

explain analyze SELECT id FROM randtest OFFSET random()*1000000 LIMIT 1;

結果は大きく異なりますが、かなり悪い場合があります。

 Limit  (cost=1442.50..1442.51 rows=1 width=4) (actual time=179.183..179.184 rows=1 loops=1)
   ->  Seq Scan on randtest  (cost=0.00..14425.00 rows=1000000 width=4) (actual time=0.016..134.835 rows=915702 loops=1)
 Total runtime: 179.211 ms
(3 rows)

2
はい、そうです。本当にランダムです。md5値は、別の既存の値の次の大きい値になる可能性が非常に低く、番号空間に大きなギャップがある場合の値の可能性は非常に大きくなります(間にある可能な値の数だけ大きい) 。結果の分布はランダムではありません。
Erwin Brandstetter、2015年

非常に興味深いですが、宝くじのようなクエリのユースケースで機能しますか?クエリは、利用可能なすべてのチケットを調べ、ランダムに1つのチケットのみを返す必要があります。あなたのテクニックで悲観的ロック(選択...更新)を使用することもできますか?
Mathieu

宝くじに関連するものについては、実際に公平で暗号的に安全なランダムサンプリングを使用する必要があります。たとえば、既存のIDが見つかるまで、1からmax(id)までの乱数を選択します。この回答の方法は公平でも安全でもありません。高速です。「行の1%をランダムに取得して何かをテストする」、「ランダムに5つのエントリを表示する」などの用途に使用できます。
Tometzky、2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.