少なくとも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<>フィドルはこちら