あなたの仕様(およびコメントの追加情報)を考えると、
- ギャップが少ない(または適度に少ない)数値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
を使用します)。 。 -
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;
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
)
TABLE 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
LANGUAGE plpgsql VOLATILE ROWS 1000 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
)
TABLE random_pick
LIMIT _limit;
END
$func$;
電話:
SELECT * FROM f_random_sample();
SELECT * FROM f_random_sample(500, 1.05);
このジェネリックを任意のテーブルで機能させることもできます。PK列とテーブルの名前をポリモーフィック型として取得し、EXECUTE
を使用します。 ...しかし、それはこの質問の範囲を超えています。参照:
- PL / pgSQL関数をリファクタリングして、さまざまなSELECTクエリの出力を返します
可能な代替案
要件で繰り返しの同一セットが許可されている場合 呼び出し(そして繰り返し呼び出しについて話している)私はマテリアライズドビューを検討します 。上記のクエリを1回実行し、結果をテーブルに書き込みます。ユーザーは、非常に速い速度で準ランダムな選択を取得します。選択した間隔またはイベントでランダムピックを更新します。
Postgres9.5ではTABLESAMPLE SYSTEM (n)
が導入されています
n
の場所 はパーセンテージです。マニュアル:
BERNOULLI
およびSYSTEM
サンプリング方法はそれぞれ、サンプリングするテーブルの割合である単一の引数を受け入れます。これは、0から100までのパーセンテージとして表されます。 。この引数は、任意のreal
にすることができます -価値のある表現。
大胆な強調鉱山。 非常に高速です 、ただし、結果は正確にランダムではありません 。再びマニュアル:
SYSTEM
メソッドはBERNOULLI
よりも大幅に高速です 小さなサンプリングパーセンテージが指定されているが、クラスタリング効果の結果としてテーブルのランダムでないサンプルを返す可能性がある場合のメソッド。
返される行数は大きく異なる可能性があります。この例では、大まかに 1000行:
SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);
関連:
- PostgreSQLでテーブルの行数をすばやく検出する方法
または 追加モジュールtsm_system_rowsをインストールします 要求された行の数を正確に取得し(十分な場合)、より便利な構文を可能にするため:
SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);
詳細については、エヴァンの回答を参照してください。
しかし、それはまだ正確にランダムではありません。