Saya punya tabel (dalam PostgreSQL 9.4) yang terlihat seperti ini:
CREATE TABLE dates_ranges (kind int, start_date date, end_date date);
INSERT INTO dates_ranges VALUES
(1, '2018-01-01', '2018-01-31'),
(1, '2018-01-01', '2018-01-05'),
(1, '2018-01-03', '2018-01-06'),
(2, '2018-01-01', '2018-01-01'),
(2, '2018-01-01', '2018-01-02'),
(3, '2018-01-02', '2018-01-08'),
(3, '2018-01-05', '2018-01-10');
Sekarang saya ingin menghitung untuk tanggal yang diberikan dan untuk setiap jenis, ke berapa banyak baris dari dates_ranges
setiap tanggal jatuh. Nol bisa dihilangkan.
Hasil yang diinginkan:
+-------+------------+----+
| kind | as_of_date | n |
+-------+------------+----+
| 1 | 2018-01-01 | 2 |
| 1 | 2018-01-02 | 2 |
| 1 | 2018-01-03 | 3 |
| 2 | 2018-01-01 | 2 |
| 2 | 2018-01-02 | 1 |
| 3 | 2018-01-02 | 1 |
| 3 | 2018-01-03 | 1 |
+-------+------------+----+
Saya telah datang dengan dua solusi, satu dengan LEFT JOIN
danGROUP BY
SELECT
kind, as_of_date, COUNT(*) n
FROM
(SELECT d::date AS as_of_date FROM generate_series('2018-01-01'::timestamp, '2018-01-03'::timestamp, '1 day') d) dates
LEFT JOIN
dates_ranges ON dates.as_of_date BETWEEN start_date AND end_date
GROUP BY 1,2 ORDER BY 1,2
dan satu dengan LATERAL
, yang sedikit lebih cepat:
SELECT
kind, as_of_date, n
FROM
(SELECT d::date AS as_of_date FROM generate_series('2018-01-01'::timestamp, '2018-01-03'::timestamp, '1 day') d) dates,
LATERAL
(SELECT kind, COUNT(*) AS n FROM dates_ranges WHERE dates.as_of_date BETWEEN start_date AND end_date GROUP BY kind) ss
ORDER BY kind, as_of_date
Saya bertanya-tanya apakah ini cara yang lebih baik untuk menulis pertanyaan ini? Dan bagaimana cara memasukkan pasangan jenis tanggal dengan 0 hitungan?
Pada kenyataannya ada beberapa jenis yang berbeda, periode hingga lima tahun (1800 tanggal), dan ~ 30rb baris dalam dates_ranges
tabel (tetapi bisa tumbuh secara signifikan).
Tidak ada indeks. Lebih tepatnya dalam kasus saya ini adalah hasil dari subquery, tapi saya ingin membatasi pertanyaan menjadi satu masalah, jadi lebih umum.
sumber
(1,2018-01-01,2018-01-15)
dan(1,2018-01-20,2018-01-25)
apakah Anda ingin memperhitungkannya ketika menentukan berapa banyak tanggal yang tumpang tindih yang Anda miliki?2018-01-31
atau2018-01-30
atau2018-01-29
di dalamnya ketika kisaran pertama memiliki semua dari mereka?generate_series
adalah parameter eksternal - mereka tidak harus mencakup semua rentang dalamdates_ranges
tabel. Adapun pertanyaan pertama saya kira saya tidak memahaminya - barisdates_ranges
independen, saya tidak ingin menentukan tumpang tindih.Jawaban:
Kueri berikut juga berfungsi jika "nol yang hilang" tidak apa-apa:
tapi itu tidak lebih cepat dari
lateral
versi dengan dataset kecil. Mungkin skala lebih baik, karena tidak ada gabungan yang diperlukan, tetapi versi di atas mengagregasi semua baris, sehingga mungkin kehilangan di sana lagi.Kueri berikut mencoba menghindari pekerjaan yang tidak perlu dengan menghapus seri apa pun yang tidak tumpang tindih:
- dan saya harus menggunakan
overlaps
operator! Perhatikan bahwa Anda harus menambahkaninterval '1 day'
ke kanan karena operator yang tumpang tindih menganggap periode waktu terbuka di sebelah kanan (yang cukup logis karena tanggal sering dianggap timestamp dengan komponen waktu tengah malam).sumber
generate_series
bisa digunakan seperti itu. Setelah beberapa tes saya mengikuti pengamatan. Kueri Anda benar-benar berskala sangat baik dengan panjang rentang yang dipilih - karena pada dasarnya tidak ada perbedaan antara periode 3 tahun dan 10 tahun. Namun untuk periode yang lebih singkat (1 tahun) solusi saya lebih cepat - saya menduga alasannya adalah ada beberapa rentang yang sangat panjang didates_ranges
(seperti 2010-2100), yang memperlambat permintaan Anda. Membatasistart_date
danend_date
di dalam kueri batin seharusnya membantu. Saya perlu melakukan beberapa tes lagi.Bangun kisi-kisi semua kombinasi lalu
LATERAL
gabung ke meja Anda, seperti ini:Seharusnya juga secepat mungkin.
LEFT JOIN LATERAL ... on true
Awalnya saya punya , tapi ada agregat di subqueryc
, jadi kami selalu mendapatkan baris dan bisa menggunakannyaCROSS JOIN
juga. Tidak ada perbedaan dalam kinerja.Jika Anda memiliki tabel yang menyimpan semua jenis yang relevan , gunakan itu alih-alih menghasilkan daftar dengan subquery
k
.Para pemain
integer
adalah opsional. Anda dapatkanbigint
.Indeks akan membantu, terutama indeks multikolom aktif
(kind, start_date, end_date)
. Karena Anda membangun subquery, ini mungkin atau mungkin tidak mungkin dicapai.Menggunakan fungsi set-return seperti
generate_series()
dalamSELECT
daftar pada umumnya tidak disarankan dalam versi Postgres sebelum 10 (kecuali Anda tahu persis apa yang Anda lakukan). Lihat:Jika Anda memiliki banyak kombinasi dengan sedikit atau tanpa baris, bentuk yang setara ini mungkin lebih cepat:
sumber
SELECT
daftar - Saya pernah membaca bahwa itu tidak disarankan, namun sepertinya berfungsi dengan baik, jika hanya ada satu fungsi seperti itu. Jika saya yakin hanya akan ada satu, dapatkah terjadi kesalahan?SELECT
daftar berfungsi seperti yang diharapkan. Mungkin menambahkan komentar untuk memperingatkan agar tidak menambahkan yang lain. Atau pindahkan keFROM
daftar untuk memulai dengan Postgres versi lama. Mengapa perlu risiko komplikasi? (Itu juga SQL standar dan tidak akan membingungkan orang yang datang dari RDBMS lain.)Menggunakan
daterange
tipePostgreSQL memiliki
daterange
. Menggunakannya cukup sederhana. Dimulai dengan data sampel Anda, kami bergerak untuk menggunakan tipe di atas meja.Sekarang untuk query kita membalikkan prosedur, dan menghasilkan seri tanggal tapi di sini menangkap permintaan itu sendiri dapat menggunakan
@>
operator penahanan ( ) untuk memeriksa bahwa tanggal dalam jangkauan, menggunakan indeks.Catatan kami menggunakan
timestamp without time zone
(untuk menghentikan bahaya DST)Yang merupakan daftar tumpang tindih hari pada indeks.
Sebagai bonus tambahan, dengan tipe daterange Anda dapat menghentikan penyisipan rentang yang tumpang tindih dengan yang lain menggunakan
EXCLUDE CONSTRAINT
sumber
JOIN
terlalu banyak kurasa.count(DISTINCT kind)
1
tanggal jenis2018-01-01
adalah dalam dua baris pertama daridates_ranges
, tetapi kueri Anda memberikan8
.count(DISTINCT kind)
apakah Anda menambahkanDISTINCT
kata kunci di sana?DISTINCT
kata kunci itu masih tidak berfungsi seperti yang diharapkan. Itu menghitung jenis yang berbeda untuk setiap tanggal, tetapi saya ingin menghitung semua baris dari setiap jenis untuk setiap tanggal.