web-dev-qa-db-ja.com

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

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

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

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

ありがとう

84
Juan

次のように、OFFSETを試してみてください。

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

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

更新(Antony Hatchkins)

ここではfloorを使用する必要があります。

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

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

92
NPE

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 oder SYSTEMを使用するかどうかを決定するには、 http://blog.2ndquadrant.com/tablesample-in-postgresql-9-5-2/ の違いについて読んでください。

43
alfonx

サブクエリでこれを試してみましたが、うまくいきました。少なくともPostgresql v8.4.4ではオフセットは問題なく機能します。

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

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

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

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

更新:(A.Hatchkins)

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

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

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

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

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

4)カスタム ランダムな集計 -unningなメソッド、遅い可能性:random()をN回生成する必要がある

を使用して方法#2を改善することを提案します

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

14
Kuberchaun

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

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

以下に、1M行のサンプルデータを使用したテストを示します。

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)
3
Tometzky

ランダムな行を取得する最も簡単で最速の方法は、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

2
daamien