Anda memerlukan satu item data per minggu dan sasaran (sebelum menggabungkan jumlah per perusahaan). Itu hanya CROSS JOIN
antara generate_series()
dan goals
. Bagian yang (mungkin) mahal adalah untuk mendapatkan state
saat ini dari updates
untuk setiap. Suka @Paul sudah disarankan
, sebuah LATERAL
bergabung sepertinya alat terbaik. Lakukan hanya untuk updates
, dan gunakan teknik yang lebih cepat dengan LIMIT 1
.
Dan sederhanakan penanganan tanggal dengan date_trunc()
.
SELECT w_start
, g.company_id
, count(*) FILTER (WHERE u.status = 'green') AS green_count
, count(*) FILTER (WHERE u.status = 'amber') AS amber_count
, count(*) FILTER (WHERE u.status = 'red') AS red_count
FROM generate_series(date_trunc('week', NOW() - interval '2 months')
, date_trunc('week', NOW())
, interval '1 week') w_start
CROSS JOIN goals g
LEFT JOIN LATERAL (
SELECT status
FROM updates
WHERE goal_id = g.id
AND created_at < w_start
ORDER BY created_at DESC
LIMIT 1
) u ON true
GROUP BY w_start, g.company_id
ORDER BY w_start, g.company_id;
Untuk membuat ini cepat anda memerlukan indeks multikolom :
CREATE INDEX updates_special_idx ON updates (goal_id, created_at DESC, status);
Urutan menurun untuk created_at
adalah yang terbaik, tetapi tidak sepenuhnya diperlukan. Postgres dapat memindai indeks mundur hampir sama cepatnya. ( Namun, tidak berlaku untuk urutan terbalik dari beberapa kolom.
)
Indeks kolom di itu memesan. Mengapa?
Dan kolom ketiga status
hanya ditambahkan untuk memungkinkan pemindaian hanya indeks
yang cepat di updates
. Kasus terkait:
1.000 sasaran selama 9 minggu (interval 2 bulan Anda tumpang tindih dengan setidaknya 9 minggu) hanya memerlukan 9k indeks pencarian untuk tabel ke-2 yang hanya terdiri dari 1.000 baris. Untuk tabel kecil seperti ini, kinerja seharusnya tidak menjadi masalah. Namun begitu Anda memiliki beberapa ribu lebih di setiap tabel, kinerja akan menurun dengan pemindaian berurutan.
w_start
mewakili awal setiap minggu. Akibatnya, penghitungan adalah untuk awal minggu. Anda bisa masih mengekstrak tahun dan minggu (atau detail lainnya mewakili minggu Anda), jika Anda bersikeras:
EXTRACT(isoyear from w_start) AS year
, EXTRACT(week from w_start) AS week
Terbaik dengan ISOYEAR
, seperti yang dijelaskan @Paul.
Terkait:
- Apa perbedaan antara LATERAL dan subquery di PostgreSQL?
- Optimalkan kueri GROUP BY untuk mengambil data terbaru per pengguna
- Pilih dulu baris di setiap grup GROUP BY?
- PostgreSQL:menjalankan hitungan baris untuk kueri 'menurut menit'