Dengan asumsi setidaknya Postgres 9.3.
Indeks
Pertama, indeks multikolom akan membantu:
CREATE INDEX observations_special_idx
ON observations(station_id, created_at DESC, id)
created_at DESC
sedikit lebih cocok, tetapi indeks masih akan dipindai mundur dengan kecepatan yang hampir sama tanpa DESC
.
Dengan asumsi created_at
didefinisikan NOT NULL
, jika tidak, pertimbangkan DESC NULLS LAST
dalam indeks dan permintaan:
- Urutkan PostgreSQL berdasarkan datetime asc, null dulu?
Kolom terakhir id
hanya berguna jika Anda hanya mendapatkan pemindaian indeks saja, yang mungkin tidak akan berfungsi jika Anda menambahkan banyak baris baru terus-menerus. Dalam hal ini, hapus id
dari indeks.
Kueri yang lebih sederhana (masih lambat)
Sederhanakan kueri Anda, subpilihan bagian dalam tidak membantu:
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;
Seharusnya sedikit lebih cepat, tapi tetap lambat.
Kueri cepat
- Dengan asumsi Anda memiliki relatif sedikit stasiun dan relatif banyak pengamatan per stasiun.
- Juga dengan asumsi
station_id
id didefinisikan sebagaiNOT NULL
.
Menjadi benar-benar cepat, Anda memerlukan pemindaian indeks yang longgar (belum diimplementasikan di Postgres). Jawaban terkait:
- Optimalkan kueri GROUP BY untuk mengambil data terbaru per pengguna
Jika Anda memiliki tabel terpisah dari stations
(yang tampaknya mungkin), Anda dapat meniru ini dengan 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;
Jika Anda tidak memiliki tabel stations
, hal terbaik berikutnya adalah membuat dan memeliharanya. Mungkin menambahkan referensi kunci asing untuk menegakkan integritas relasional.
Jika itu bukan pilihan, Anda dapat menyaring tabel seperti itu dengan cepat. Opsi sederhananya adalah:
SELECT DISTINCT station_id FROM observations;
SELECT station_id FROM observations GROUP BY 1;
Tetapi keduanya akan membutuhkan pemindaian berurutan dan lambat. Jadikan Postgres menggunakan indeks di atas (atau indeks btree apa pun dengan station_id
sebagai kolom utama) dengan CTE rekursif :
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
Gunakan itu sebagai pengganti drop-in untuk stations
tabel dalam kueri sederhana di atas:
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;
Ini masih harus lebih cepat dari yang Anda miliki dengan urutan besarnya .
SQL Fiddle di sini (9.6)
db<>biola di sini