PostgreSQLで行をランダムに選択したいので、これを試しました:
select * from table where random() < 0.01;
しかし、他の人はこれをお勧めします:
select * from table order by random() limit 1000;
5億行の非常に大きなテーブルがあるので、高速にしたい。
どちらのアプローチが良いですか?違いは何ですか?ランダムな行を選択する最良の方法は何ですか?
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
、だましや隙間で食べられなかった余剰分を取り除きます。すべての行は、選択される完全に等しいチャンスを持っています。
このクエリを簡略化できます。上記のクエリの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スペースに比較的少ないギャップがまだ必要です。そうしないと、制限に達する前に再帰が空になる可能性があります。または、パフォーマンスを最適化する目的に反する十分に大きなバッファーで開始する必要があります。
重複は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回実行し、結果をテーブルに書き込みます。ユーザーは、驚くほどの速さで準ランダム選択を取得します。選択した間隔またはイベントでランダムピックを更新します。
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の回答を参照してください。
しかし、それはまだ完全にランダムではありません。
JOIN bigtbl t
、の略ですJOIN bigtbl AS t
。t
のテーブルエイリアスですbigtbl
。その目的は構文を短くすることですが、この特定のケースでは必要ありません。私の回答ではクエリを簡略化し、シンプルなバージョンを追加しました。
を使用して、両方の実行プランを調べて比較できます。
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「テーブル全体がメモリに収まりません」のように「大きい」。
random() < 0.02
、そのリストをシャッフルし、それからlimit 1000
!並べ替えは数千行で安価になります(笑)。
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
select your_columns from your_table ORDER BY random() limit 1
〜45mil行上の幹部に2分かかる
PostgreSQL 9.5以降、テーブルからランダムな要素を取得するための新しい構文があります。
SELECT * FROM mytable TABLESAMPLE SYSTEM (5);
この例では、の要素の5%が得られますmytable
。
このブログ投稿の詳細については、http://www.postgresql.org/docs/current/static/sql-select.htmlをご覧ください。
TABLESAMPLE SYSTEM_ROWS(400)
して、400のランダムな行のサンプルを取得できます。このステートメントを使用するには、組み込みのtsm_system_rows
拡張機能を有効にする必要があります。
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.01
1つの欠点は、出力レコードの数が可変になることです。
ランダムに並べ替えるよりも、一連のデータをシャッフルする方がよいことに注意してください。Fisher-YatesShuffleは、で実行されO(N)
ます。しかし、SQLにシャッフルを実装することは、かなり難しいように思えます。
select * from table order by random() limit 1000;
必要な行数がわかっている場合は、を確認してくださいtsm_system_rows
。
moduleは、SELECTコマンドのTABLESAMPLE句で使用できるテーブルサンプリングメソッドSYSTEM_ROWSを提供します。
このテーブルサンプリングメソッドは、読み取る行の最大数である単一の整数引数を受け入れます。テーブルに十分な行が含まれていない場合を除いて、結果のサンプルには常に正確にその数の行が含まれます。その場合、テーブル全体が選択されます。組み込みのSYSTEMサンプリング方法と同様に、SYSTEM_ROWSはブロックレベルのサンプリングを実行するため、サンプルは完全にランダムではありませんが、特に少数の行のみが要求される場合は、クラスタリングの影響を受ける可能性があります。
最初に拡張機能をインストールします
CREATE EXTENSION tsm_system_rows;
次に、クエリ
SELECT *
FROM table
TABLESAMPLE SYSTEM_ROWS(1000);
SYSTEM
方法に対する顕著な改善です。
tsm_system_rows
tsm_system_time
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_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
必要に応じて-あなただけ一度に一つのランダムな値、変更が必要な場合LIMIT
には1
。
の適切なインデックスid_values
を使用すると、UPDATE-RETURNINGはほとんど負荷なしで非常に高速に実行されるはずです。1回のデータベースラウンドトリップでランダム化された値を返します。「適格」行の基準は、必要に応じて複雑にすることができます。新しい行はid_values
いつでもテーブルに追加でき、マテリアライズドビューが更新されるとすぐにアプリケーションからアクセスできるようになります(オフピーク時に実行される可能性があります)。マテリアライズドビューの作成と更新は遅くなりますが、新しいIDがid_values
テーブルに追加されたときにのみ実行する必要があります。
r
typeで呼び出される列を追加します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
では、行数>または複数の行を選択する必要がある場合に、ステートメントを再度実行する必要があります。
私はパーティーに少し遅れていることを知っていますが、pg_sampleというこの素晴らしいツールを見つけました。
pg_sample
-参照整合性を維持しながら、大きなPostgreSQLデータベースから小さなサンプルデータセットを抽出します。
私はこれを350M行のデータベースで試してみましたが、非常に高速で、ランダム性についてはわかりません。
./pg_sample --limit="small_table = *" --limit="large_table = 100000" -U postgres source_db | psql -U postgres target_db