私はPostgreSQLでランダムに行を選択したいのですが、私はこれを試しました:
select * from table where random() < 0.01;
しかし、他にもこれをお勧めします:
select * from table order by random() limit 1000;
5億行の非常に大きなテーブルがあるので、高速にしたいです。
どちらのアプローチが良いですか?違いは何ですか?ランダムな行を選択するための最良の方法は何ですか?
あなたの仕様(コメントの中の追加情報)を考えて、
以下のクエリは大きなテーブルのシーケンシャルスキャンを必要とせず、インデックススキャンのみを必要とします。
まず、メインクエリの見積もりを取得します。
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
を使用する).
大きなテーブルにid
sを結合します。これはインデックスが設定されていれば非常に速いはずです。
最後に、重複やギャップによって食べられていない余分なid
sを切り捨てます。すべての行に完全に等しい機会が選択されます。
このクエリは簡素化できます。上記のクエリの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;
特に、ギャップや見積もりについてあまりよくわからない場合は。
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スペースにはまだ少数のギャップが必要です。そうでないと、制限に達する前に再帰が空になる可能性があります。
重複は、rCTEのUNION
によって排除されます。
外側の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回実行し、結果をテーブルに書き込みます。ユーザーは、軽い速度で疑似ランダム選択を得ます。選択した間隔またはイベントでランダムな選択を更新します。
TABLESAMPLE SYSTEM (n)
が導入されましたここで、n
はパーセントです。 マニュアル:
BERNOULLI
およびSYSTEM
のサンプリング方法はそれぞれ、サンプリングするテーブルの割合である1つの引数を受け入れます。これは、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の答え をご覧ください。
しかし、それはまだ正確にはランダムではありません。
を使用して、両方の実行計画を調べて比較できます。
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回のテーブル全体のスキャンでも時間がかかるため、これは望んでいないことかもしれません。
3番目の提案は
select * from table where random() < 0.01 limit 1000;
これは1000行が見つかるとすぐにテーブルスキャンを停止しますので、早く戻ります。もちろん、これはランダム性を少し落としますが、おそらくあなたの場合はこれで十分です。
編集:この考慮事項以外にも、これに関してすでに寄せられている質問を確認してください。クエリ[postgresql] random
を使用すると、かなりの数のヒットが返されます。
そしてその他のいくつかのアプローチを概説したdependentzのリンク記事。
1 「完全なテーブルはメモリに収まりません」のように「大きい」。
select your_columns from your_table ORDER BY random()
select * from
(select distinct your_columns from your_table) table_alias
ORDER BY random()
select your_columns from your_table ORDER BY random() limit 1
PostgreSQL 9.5以降では、テーブルからランダムな要素を取得するための新しい構文があります。
SELECT * FROM mytable TABLESAMPLE SYSTEM (5);
この例はmytable
からの5%の要素をあなたに与えるでしょう。
このブログ記事の詳細な説明を参照してください。 http://www.postgresql.org/docs/current/static/sql-select.html
ORDER BYのあるものは遅いものになるでしょう。
select * from table where random() < 0.01;
はレコードごとに進み、ランダムにフィルタリングするかどうかを決定します。これはO(N)
になるでしょう。なぜならそれは各レコードを一度だけチェックする必要があるだけだからです。
select * from table order by random() limit 1000;
はテーブル全体をソートしてから最初の1000を選びます。舞台裏でのブードゥー教の魔法のほかに、byO(N * log N)
による順序がそうです。
random() < 0.01
のマイナス面は、可変数の出力レコードが得られることです。
ランダムにソートするよりも、データのセットをシャッフルする方がよいことに注意してください。 O(N)
で実行されるFisher-Yates Shuffle ただし、SQLでシャッフルを実装することは非常に難しいように思えます。
これが私のために働く決定です。理解して実行するのはとても簡単だと思います。
SELECT
field_1,
field_2,
field_2,
random() as ordering
FROM
big_table
WHERE
some_conditions
ORDER BY
ordering
LIMIT 1000;
select * from table order by random() limit 1000;
必要な行数がわかっている場合は、 tsm_system_rows
をチェックしてください。
moduleは、テーブルサンプリングメソッドSYSTEM_ROWSを提供します。これは、SELECTコマンドのTABLESAMPLE句で使用できます。
このテーブルサンプリング方法は、読み込む行の最大数である単一の整数引数を受け入れます。結果として得られるサンプルには、テーブルに十分な行が含まれていない場合を除き、常にその数の行が含まれます。その場合、テーブル全体が選択されます。 組み込みのSYSTEMサンプリング方法と同様に、SYSTEM_ROWSはブロックレベルのサンプリングを実行するため、サンプルは完全にランダムではありませんが、特に少数の行しか要求されない場合、クラスタリング効果を受ける可能性があります。
まず拡張機能をインストールする
CREATE EXTENSION tsm_system_rows;
それからあなたの質問、
SELECT *
FROM table
TABLESAMPLE SYSTEM_ROWS(1000);
1行だけが必要な場合は、offset
から派生した計算済みのcount
を使用できます。
select * from table_name limit 1
offset floor(random() * (select count(*) from table_name));
マテリアライズド・ビュー "Alternative alternative"の変形例 Erwin Brandstetterによって概説された 。
たとえば、返されるランダム化された値の中で重複したくないとします。ですから、(ランダム化されていない)一連の値を含む主テーブルにブール値を設定する必要があります。
これが入力テーブルであると仮定します。
id_values id | used
----+--------
1 | FALSE
2 | FALSE
3 | FALSE
4 | FALSE
5 | FALSE
...
必要に応じてID_VALUES
テーブルを作成します。次に、Erwinの説明に従って、ID_VALUES
テーブルを1回ランダム化するマテリアライズドビューを作成します。
CREATE MATERIALIZED VIEW id_values_randomized AS
SELECT id
FROM id_values
ORDER BY random();
マテリアライズドビューにはusedカラムが含まれていないことに注意してください。これはすぐに古くなるためです。 id_values
テーブルにある可能性のある他の列をビューに含める必要もありません。
ランダムな値を取得(および「消費」)するには、id_values
でUPDATE-RETURNINGを使用し、結合でid_values
からid_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
を変更します - 一度に1つのランダムな値しか必要としない場合は、LIMIT
を1
に変更します。
id_values
に適切なインデックスがあれば、UPDATE-RETURNINGは非常に短時間で実行されるはずです。 1回のデータベースラウンドトリップでランダム化された値を返します。 「適格」行の基準は、必要に応じて複雑になる可能性があります。新しい行はいつでもid_values
テーブルに追加でき、マテリアライズドビューが更新されるとすぐにアプリケーションからアクセスできるようになります(ピーク時以外に実行される可能性があります)。マテリアライズドビューの作成と更新は遅くなりますが、新しいIDがid_values
テーブルに追加されたときにのみ実行する必要があります。
私はパーティーに少し遅れていることを知っています、しかし、私はちょうど pg_sample と呼ばれるこの素晴らしいツールを見つけました:
pg_sample
- 参照の整合性を維持しながら、より大きなPostgreSQLデータベースから小さなサンプルデータセットを抽出します。
私はこれを350Mの行データベースで試してみましたが、本当に速かったので、ランダムネスについては知りません。
./pg_sample --limit="small_table = *" --limit="large_table = 100000" -U postgres source_db | psql -U postgres target_db
タイプがr
の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
>行数、または複数の行を選択する必要がある場合は、アプリケーションレベルでステートメントをもう一度実行する必要があります。
私の経験からの一つの教訓:
offset floor(random() * N) limit 1
はorder by random() limit 1
より高速ではありません。
offset
アプローチは、Postgresでのソートの時間を節約するため、より高速になると思いました。そうではなかったことが判明しました。