sql >> データベース >  >> RDS >> PostgreSQL

ランダムな行を選択するための最良の方法PostgreSQL

    あなたの仕様(およびコメントの追加情報)を考えると、

    • ギャップが少ない(または適度に少ない)数値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);
    

    詳細については、エヴァンの回答を参照してください。

    しかし、それはまだ正確にランダムではありません。



    1. 非常に大規模なデータベースをアンロードする

    2. SparkDataframesUPSERTからPostgresテーブルへ

    3. PG ::ConnectionBad:fe_sendauth:パスワードが提供されていません

    4. CSVファイルからCSVファイルのヘッダーを含むPostgreSQLテーブルにコピーするにはどうすればよいですか?