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

行ごとに最後のN個の関連行をクエリします

    少なくともPostgres9.3を想定しています。

    インデックス

    まず、複数列のインデックスが役立ちます:

    CREATE INDEX observations_special_idx
    ON observations(station_id, created_at DESC, id)
    

    created_at DESC は少し適切ですが、DESCがなくても、インデックスはほぼ同じ速度で逆方向にスキャンされます。 。

    created_atを想定 定義されているNOT NULL 、それ以外の場合は、DESC NULLS LASTを検討してください インデックスおよび クエリ:

    • PostgreSQLは日時ascで並べ替え、最初はnullですか?

    最後の列id インデックスのみのスキャンを取得する場合にのみ役立ちます。これは、多くの新しい行を絶えず追加する場合はおそらく機能しません。この場合、idを削除してください インデックスから。

    より単純なクエリ(まだ遅い)

    クエリを単純化してください。内側の副選択は役に立ちません:

    SELECT id
    FROM  (
      SELECT station_id, id, created_at
           , row_number() OVER (PARTITION BY station_id
                                ORDER BY created_at DESC) AS rn
      FROM   observations
      ) s
    WHERE  rn <= #{n}  -- your limit here
    ORDER  BY station_id, created_at DESC;
    

    少し速くなるはずですが、それでも遅いです。

    高速クエリ

    • 比較的少ないと仮定します ステーション 比較的多く ステーションごとの観測。
    • station_idも想定 NOT NULLとして定義されたID 。

    本当に 高速で、ルーズインデックススキャンに相当するものが必要です (Postgresにはまだ実装されていません)。関連する回答:

    • GROUP BYクエリを最適化して、ユーザーごとに最新のレコードを取得します

    stationsの別のテーブルがある場合 (可能性が高いと思われます)、 JOIN LATERALでこれをエミュレートできます (Postgres 9.3以降):

    SELECT o.id
    FROM   stations s
    CROSS  JOIN LATERAL (
       SELECT o.id
       FROM   observations o
       WHERE  o.station_id = s.station_id  -- lateral reference
       ORDER  BY o.created_at DESC
       LIMIT  #{n}  -- your limit here
       ) o
    ORDER  BY s.station_id, o.created_at DESC;
    

    stationsのテーブルがない場合 、次善の策は、それを作成して維持することです。おそらく、リレーショナル整合性を強制するために外部キー参照を追加します。

    それがオプションでない場合は、そのようなテーブルをその場で蒸留することができます。簡単なオプションは次のとおりです。

    SELECT DISTINCT station_id FROM observations;
    SELECT station_id FROM observations GROUP BY 1;

    ただし、どちらも順次スキャンが必要で、速度が遅くなります。 Postgresが上記のインデックス(またはstation_idを持つ任意のbtreeインデックス)を使用するようにします 先頭の列として)再帰CTE

    WITH RECURSIVE stations AS (
       (                  -- extra pair of parentheses ...
       SELECT station_id
       FROM   observations
       ORDER  BY station_id
       LIMIT  1
       )                  -- ... is required!
       UNION ALL
       SELECT (SELECT o.station_id
               FROM   observations o
               WHERE  o.station_id > s.station_id
               ORDER  BY o.station_id
               LIMIT  1)
       FROM   stations s
       WHERE  s.station_id IS NOT NULL  -- serves as break condition
       )
    SELECT station_id
    FROM   stations
    WHERE  station_id IS NOT NULL;      -- remove dangling row with NULL
    

    これをドロップインの代替品として使用してください stationsの場合 上記の単純なクエリの表:

    WITH RECURSIVE stations AS (
       (
       SELECT station_id
       FROM   observations
       ORDER  BY station_id
       LIMIT  1
       )
       UNION ALL
       SELECT (SELECT o.station_id
               FROM   observations o
               WHERE  o.station_id > s.station_id
               ORDER  BY o.station_id
               LIMIT  1)
       FROM   stations s
       WHERE  s.station_id IS NOT NULL
       )
    SELECT o.id
    FROM   stations s
    CROSS  JOIN LATERAL (
       SELECT o.id, o.created_at
       FROM   observations o
       WHERE  o.station_id = s.station_id
       ORDER  BY o.created_at DESC
       LIMIT  #{n}  -- your limit here
       ) o
    WHERE  s.station_id IS NOT NULL
    ORDER  BY s.station_id, o.created_at DESC;
    

    これでも、桁違いの速度よりも高速である必要があります。 。

    SQLフィドルはこちら(9.6)
    db<>フィドルはこちら



    1. SQLiteで外部キーのリストを返す

    2. プロパティマネージャーがデータベースを使用して効率を向上させる方法

    3. SQLServerでトリガーイベントのリストを返す方法

    4. MySQL、MariaDB、Percona Server、MongoDB、またはPostgreSQLのデプロイ-ClusterControlで簡単に