Menghasilkan deret waktu antara dua tanggal di PostgreSQL

92

Saya memiliki kueri seperti ini yang dengan baik menghasilkan serangkaian tanggal antara 2 tanggal tertentu:

select date '2004-03-07' + j - i as AllDate 
from generate_series(0, extract(doy from date '2004-03-07')::int - 1) as i,
     generate_series(0, extract(doy from date '2004-08-16')::int - 1) as j

Ini menghasilkan 162 tanggal antara 2004-03-07dan 2004-08-16dan ini yang saya inginkan. Masalah dengan kode ini adalah tidak akan memberikan jawaban yang benar jika kedua tanggal tersebut berasal dari tahun yang berbeda, misalnya ketika saya mencoba 2007-02-01dan 2008-04-01.

Apakah ada solusi yang lebih baik?

f. asouri
sumber

Jawaban:

175

Dapat dilakukan tanpa konversi ke / dari int (tetapi ke / dari stempel waktu sebagai gantinya)

SELECT date_trunc('day', dd):: date
FROM generate_series
        ( '2007-02-01'::timestamp 
        , '2008-04-01'::timestamp
        , '1 day'::interval) dd
        ;
wildplasser
sumber
3
mengapa date_truncdibutuhkan?
Idefixx
2
Itu hanya presentasi. Ini menghilangkan pencetakan bagian waktu stempel waktu yang dalam kasus ini selalu nol.
beemtee
73

Untuk menghasilkan rangkaian tanggal ini adalah cara yang optimal :

SELECT t.day::date 
FROM   generate_series(timestamp '2004-03-07'
                     , timestamp '2004-08-16'
                     , interval  '1 day') AS t(day);
  • Tambahan date_trunc()tidak diperlukan. Cast to date( day::date) melakukan itu secara implisit.

  • Tetapi tidak ada gunanya mentransmisikan literal tanggal datesebagai parameter masukan. Au contraire, timestampadalah pilihan terbaik . Keuntungan dalam performa memang kecil, tetapi tidak ada alasan untuk tidak mengambilnya. Dan Anda tidak perlu melibatkan aturan DST (waktu musim panas) yang digabungkan dengan konversi dari dateke timestamp with time zonedan ke belakang. Lihat di bawah.

Sintaks pendek yang setara dan kurang eksplisit:

SELECT day::date 
FROM   generate_series(timestamp '2004-03-07', '2004-08-16', '1 day') day;

Atau dengan fungsi set-return dalam SELECTdaftar:

SELECT generate_series(timestamp '2004-03-07', '2004-08-16', '1 day')::date AS day;

Kata ASkunci diperlukan dalam varian terakhir, Postgres akan salah menafsirkan alias kolom daysebaliknya. Dan saya tidak akan menyarankan varian itu sebelum Postgres 10 - setidaknya tidak dengan lebih dari satu fungsi set-return dalam SELECTdaftar yang sama :

(Selain itu, varian terakhir biasanya tercepat dengan selisih kecil.)

Kenapa timestamp [without time zone]?

Ada sejumlah varian yang kelebihan muatan generate_series(). Saat ini (Postgres 11):

SELECT oid::regprocedure   AS function_signature
     , prorettype::regtype AS return_type
FROM   pg_proc
where  proname = 'generate_series';
function_signature | return_type                
: ------------------------------------------------- ------------------------------- | : --------------------------
generate_series (integer, integer, integer) | bilangan bulat                    
generate_series (integer, integer) | bilangan bulat                    
generate_series (bigint, bigint, bigint) | bigint                     
generate_series (bigint, bigint) | bigint                     
generate_series (numerik, numerik, numerik) | numerik                    
generate_series (numerik, numerik) | numerik                    
generate_series (timestamp tanpa zona waktu, timestamp tanpa zona waktu, interval) | stempel waktu tanpa zona waktu
generate_series (stempel waktu dengan zona waktu, stempel waktu dengan zona waktu, interval) | stempel waktu dengan zona waktu

( numericvarian ditambahkan dengan Postgres 9.5.) Yang relevan adalah dua yang terakhir dalam pengambilan dan pengembalian timestamp/ timestamptz.

Tidak ada varian yang mengambil atau kembalidate . Pemeran eksplisit diperlukan untuk kembali date. Panggilan dengan timestampargumen menyelesaikan ke varian terbaik secara langsung tanpa turun ke aturan resolusi jenis fungsi dan tanpa cast tambahan untuk input.

timestamp '2004-03-07'sangat valid, btw. Bagian waktu yang dihilangkan secara default 00:00dengan format ISO.

Berkat fungsi jenis resolusi kita masih bisa lewat date. Tapi itu membutuhkan lebih banyak pekerjaan dari Postgres. Ada pemeran implisit dari dateke timestampserta dari dateke timestamptz. Akan ambigu, tetapi timestamptzadalah "disukai" antara "jenis tanggal / waktu". Jadi pertandingan diputuskan pada langkah 4d. :

Jalankan melalui semua kandidat dan pertahankan mereka yang menerima tipe yang disukai (dari kategori tipe tipe data input) di posisi paling banyak di mana konversi tipe akan diperlukan. Pertahankan semua kandidat jika tidak ada yang menerima jenis yang disukai. Jika hanya satu calon yang tersisa, gunakanlah; jika tidak lanjutkan ke langkah berikutnya.

Selain pekerjaan ekstra dalam resolusi jenis fungsi, ini menambahkan pemeran tambahan timestamptz- yang tidak hanya menambah biaya, tetapi juga dapat menyebabkan masalah dengan DST yang mengarah ke hasil yang tidak terduga dalam kasus yang jarang terjadi. (DST adalah konsep tolol, btw, tidak bisa cukup menekankan hal ini.)

Saya menambahkan demo ke biola yang menunjukkan paket kueri yang lebih mahal:

db <> biola di sini

Terkait:

Erwin Brandstetter
sumber
7
Versi yang lebih pendek:SELECT generate_series(timestamp '2004-03-07', '2004-08-16', '1 day') :: DATE AS day;
Václav Kužel
Apa arti sintaks t (hari)?
rendang
@rendang: AS t(day)dalam SELECT * FROM func() AS t(day)alias tabel dan kolom. Kata ASkunci adalah kebisingan opsional dalam konteks ini. Lihat: stackoverflow.com/a/20230716/939860
Erwin Brandstetter
35

Anda dapat membuat serial langsung dengan tanggal. Tidak perlu menggunakan int atau cap waktu:

select date::date 
from generate_series(
  '2004-03-07'::date,
  '2004-08-16'::date,
  '1 day'::interval
) date;
fbonetti
sumber
Bergantung pada zona waktu Anda, ini mungkin memberikan hasil yang tidak terduga. Saya punya masalah ini. Gunakan stempel waktu sebagai gantinya. SET sesi WAKTU zona 'Amerika / Sao_Paulo' SELECT d :: date FROM generate_series ('2019-11-01' :: date, '2019-11-03' :: date, '1 day') d SELECT d :: date FROM generate_series ('2019-11-01' :: date, '2019-11-04' :: date, '1 day') d
palhares
1

Anda juga bisa menggunakan ini.

select generate_series  ( '2012-12-31'::timestamp , '2018-10-31'::timestamp , '1 day'::interval) :: date 
Meyyappan
sumber