PostgreSQL: hari / bulan / tahun antara dua tanggal

94

Saya mencari cara untuk menerapkan fungsi-SQLServer tanggaliff di PostgreSQL. Itu adalah,

Fungsi ini mengembalikan hitungan (sebagai nilai integer bertanda) dari batas bagian tanggal yang disilangkan antara tanggal mulai dan tanggal akhir yang ditentukan.

datediff(dd, '2010-04-01', '2012-03-05') = 704 // 704 changes of day in this interval
datediff(mm, '2010-04-01', '2012-03-05') = 23  // 23 changes of month
datediff(yy, '2010-04-01', '2012-03-05') = 2   // 2 changes of year

Saya tahu saya bisa melakukan 'dd' hanya dengan menggunakan substraksi, tetapi ada ide tentang dua lainnya?

gefei
sumber
4
Periksa di sini
revoua

Jawaban:

117
SELECT
  AGE('2012-03-05', '2010-04-01'),
  DATE_PART('year', AGE('2012-03-05', '2010-04-01')) AS years,
  DATE_PART('month', AGE('2012-03-05', '2010-04-01')) AS months,
  DATE_PART('day', AGE('2012-03-05', '2010-04-01')) AS days;

Ini akan memberi Anda tahun penuh , bulan, hari ... antara dua tanggal:

          age          | years | months | days
-----------------------+-------+--------+------
 1 year 11 mons 4 days |     1 |     11 |    4

Informasi dateiff lebih rinci .

Igor Romanchenko
sumber
8
+1 karena fungsi usia, tetapi saya pikir argumennya salah urutan :)
dcarneiro
2
select date_part('month', age('2010-04-01', '2012-03-05'));memberi -11. Itu bukan perbedaan bulan yang tepat
smac89
1
pilih tanggal_part ('bulan', usia ('2012-03-05', '2010-04-01'));
Zuko
35
ini tidak memperhitungkan bulan + tahun dll. Ini hanya melakukan pemeriksaan hari bergulir - yaitu SELECT date_part('day', age('2016-10-05', '2015-10-02'))pengembalian3
Don Cheadle
Pertanyaannya adalah bagaimana cara menghitung berapa tahun perlintasan batas, dan berapa bulan yang ada di antara dua tanggal tersebut. Menggunakan age()akan selalu salah untuk melakukan ini selama bertahun-tahun dan berbulan-bulan. Antara 2014-12-31dan 2015-01-01lalu akan memberi 0 untuk bulan dan tahun, sementara datediff()akan memberi 1 dan 1. Anda tidak bisa hanya menambahkan satu ke age()hasil karena age()+1akan memberi 1 saat antara 2015-01-01dan 2015-01-02, sedangkan datediff()sekarang akan memberi 0.
André C. Andersen
147

Kurangi saja:

SELECT ('2015-01-12'::date - '2015-01-01'::date) AS days;

Hasil:

 days
------
   11
mehdi
sumber
2
Ya, ini berfungsi untuk PostgreSQL ketika Anda perlu mengetahui jumlah hari antara dua tanggal.
icl7126
Bagus! FYI saya menggunakannya untuk memesan rekaman dengan rentang yang lebih kecil antara dua tanggal, mis .:range_end - range_start ASC, id DESC
Edison Machado
1
Perhatikan, metode ini mengembalikan tipe interval, yang tidak bisa begitu saja dilemparkan sebagai int. Bagaimana cara mengubah interval menjadi beberapa jam dengan postgres? . Menggunakan date_part/ agefunction combo, seperti yang disebutkan dalam jawaban @IgorRomanchenko akan mengembalikan tipedouble precision
WebWanderer
2
Setelah dipikir-pikir, ini adalah cara yang benar. Menggunakan metode ini untuk mendapatkan jumlah hari antara dua tanggal akan menghitung hari antara bulan dan tahun, sedangkan jawaban date_part/ age, seperti yang diterima, akan memberikan hari sebagai perbedaan pada bagian hari dari dua tanggal. date_part('day', age('2016-9-05', '2015-10-02'))mengembalikan 3.
WebWanderer
1
Pertanyaannya bukan tentang hari, ini tentang jumlah bulan dan tahun batas yang harus dilintasi antara dua tanggal. Penanya sudah tahu bagaimana melakukan hari.
André C. Andersen
41

Saya menghabiskan beberapa waktu mencari jawaban terbaik, dan saya rasa saya memilikinya.

Sql ini akan memberi Anda jumlah hari antara dua tanggal sebagai integer:

SELECT
    (EXTRACT(epoch from age('2017-6-15', now())) / 86400)::int

..yang, ketika dijalankan hari ini ( 2017-3-28), memberi saya:

?column?
------------
77

Kesalahpahaman tentang jawaban yang diterima:

select age('2010-04-01', '2012-03-05'),
   date_part('year',age('2010-04-01', '2012-03-05')),
   date_part('month',age('2010-04-01', '2012-03-05')),
   date_part('day',age('2010-04-01', '2012-03-05'));

..adalah Anda akan mendapatkan perbedaan literal antara bagian-bagian string tanggal, bukan jumlah waktu antara dua tanggal.

YAITU:

Age(interval)=-1 years -11 mons -4 days;

Years(double precision)=-1;

Months(double precision)=-11;

Days(double precision)=-4;

WebWanderer
sumber
7
Ini harus menjadi jawaban yang diterima. Satu-satunya perubahan yang saya sarankan adalah menggunakan ceil () alih-alih casting ke int (untuk mengumpulkan hari parsial, alih-alih memotong).
Rob
2
Poin bagus @Rob. Pembaca harus mencatat ini. Saya bisa melihat ini lebih sebagai preferensi. Saya pikir dalam sebagian besar kasus saya, saya ingin memotong nilai saya sebagai jumlah literal hari di antara tanggal, tetapi saya juga dapat melihat di mana pembulatan jumlah hari harus digunakan.
WebWanderer
2
Pendekatan ini dapat tersandung oleh penghematan waktu siang hari Inggris - akan ada satu hari dalam setahun dengan hanya 23 jam, dan satu hari lagi dengan 25.
qcode peter
Wow @qcodepeter! Tangkapan yang bagus! Anda benar sekali! Bagaimana kita memperbaikinya?
WebWanderer
1
Ini tidak benar-benar menjawab pertanyaan itu. Pertanyaannya adalah bagaimana cara menghitung berapa tahun perlintasan batas, dan berapa bulan yang ada di antara dua tanggal tersebut. Jawaban ini hanya menjawab berapa hari yang ada, dan tidak memperhitungkan tahun kabisat.
André C. Andersen
9

Fungsi yang hampir sama dengan yang Anda butuhkan (berdasarkan jawaban atiruz, versi singkat UDF dari sini )

CREATE OR REPLACE FUNCTION datediff(type VARCHAR, date_from DATE, date_to DATE) RETURNS INTEGER LANGUAGE plpgsql
AS
$$
DECLARE age INTERVAL;
BEGIN
    CASE type
        WHEN 'year' THEN
            RETURN date_part('year', date_to) - date_part('year', date_from);
        WHEN 'month' THEN
            age := age(date_to, date_from);
            RETURN date_part('year', age) * 12 + date_part('month', age);
        ELSE
            RETURN (date_to - date_from)::int;
    END CASE;
END;
$$;

Pemakaian:

/* Get months count between two dates */
SELECT datediff('month', '2015-02-14'::date, '2016-01-03'::date);
/* Result: 10 */

/* Get years count between two dates */
SELECT datediff('year', '2015-02-14'::date, '2016-01-03'::date);
/* Result: 1 */

/* Get days count between two dates */
SELECT datediff('day', '2015-02-14'::date, '2016-01-03'::date);
/* Result: 323 */

/* Get months count between specified and current date */
SELECT datediff('month', '2015-02-14'::date, NOW()::date);
/* Result: 47 */
Riki_tiki_tavi
sumber
Jawaban ini salah. The DATEDIFF()fungsi dalam MS SQL kembali 1untuk datediff(year, '2015-02-14', '2016-01-03'). Ini karena Anda harus melewati batas tahun satu kali di antara tanggal-tanggal tersebut: docs.microsoft.com/en-us/sql/t-sql/functions/…
André C. Andersen
Jawabannya benar karena pertanyaan aslinya adalah tentang PostgreSQL, bukan MS SQL.
Riki_tiki_tavi
Saya mengerti itu bisa sulit dipahami, tetapi pertanyaan aslinya adalah menemukan "cara untuk mengimplementasikan dateiff fungsi SQLServer di PostgreSQL". Pengguna MS SQL Server cenderung menyebutnya SQL Server. Coba googling "SQL Server": i.imgur.com/USCHdLS.png Pengguna ingin memindahkan fungsi dari satu teknologi tertentu ke yang lain.
André C. Andersen
Maaf, kamu benar. Sepertinya saya terburu-buru dan tidak mengerti arti dari komentar pertama Anda. Jawabannya telah diperbarui.
Riki_tiki_tavi
7
SELECT date_part ('year', f) * 12
     + date_part ('month', f)
FROM age ('2015-06-12'::DATE, '2014-12-01'::DATE) f

Hasil: 6

atiruz.dll
sumber
1

Pertanyaan ini penuh dengan kesalahpahaman. Pertama mari kita pahami pertanyaannya sepenuhnya. Penanya ingin mendapatkan hasil yang sama seperti ketika menjalankan fungsi MS SQL Server DATEDIFF ( datepart , startdate , enddate ) di mana datepartdibutuhkan dd, mmatau yy.

Fungsi ini ditentukan oleh:

Fungsi ini mengembalikan hitungan (sebagai nilai integer bertanda) dari batas bagian tanggal yang disilangkan antara tanggal mulai dan tanggal akhir yang ditentukan.

Itu berarti berapa banyak batas hari, bulan, atau tahun, dilintasi. Bukan berapa hari, bulan, atau tahun di antara mereka. Itu sebabnya datediff(yy, '2010-04-01', '2012-03-05')adalah 2, dan bukan 1. Ada waktu kurang dari 2 tahun di antara tanggal-tanggal tersebut, artinya hanya 1 tahun penuh yang telah berlalu, tetapi batas 2 tahun telah dilintasi, dari 2010 ke 2011, dan dari 2011 ke 2012.

Berikut ini adalah upaya terbaik saya untuk mereplikasi logika dengan benar.

-- datediff(dd`, '2010-04-01', '2012-03-05') = 704 // 704 changes of day in this interval
select ('2012-03-05'::date - '2010-04-01'::date );
-- 704 changes of day

-- datediff(mm, '2010-04-01', '2012-03-05') = 23  // 23 changes of month
select (date_part('year', '2012-03-05'::date) - date_part('year', '2010-04-01'::date)) * 12 + date_part('month', '2012-03-05'::date) - date_part('month', '2010-04-01'::date)
-- 23 changes of month

-- datediff(yy, '2010-04-01', '2012-03-05') = 2   // 2 changes of year
select date_part('year', '2012-03-05'::date) - date_part('year', '2010-04-01'::date);
-- 2 changes of year
André C. Andersen
sumber
1

Saya ingin memperluas jawaban Riki_tiki_tavi dan mendapatkan datanya di luar sana. Saya telah membuat fungsi Dateiff yang melakukan hampir semua yang dilakukan sql server. Dengan begitu kita bisa memperhitungkan unit apa saja.

create function datediff(units character varying, start_t timestamp without time zone, end_t timestamp without time zone) returns integer
language plpgsql
 as
 $$
DECLARE
 diff_interval INTERVAL; 
 diff INT = 0;
 years_diff INT = 0;
BEGIN
 IF units IN ('yy', 'yyyy', 'year', 'mm', 'm', 'month') THEN
   years_diff = DATE_PART('year', end_t) - DATE_PART('year', start_t);

   IF units IN ('yy', 'yyyy', 'year') THEN
     -- SQL Server does not count full years passed (only difference between year parts)
     RETURN years_diff;
   ELSE
     -- If end month is less than start month it will subtracted
     RETURN years_diff * 12 + (DATE_PART('month', end_t) - DATE_PART('month', start_t)); 
   END IF;
 END IF;

 -- Minus operator returns interval 'DDD days HH:MI:SS'  
 diff_interval = end_t - start_t;

 diff = diff + DATE_PART('day', diff_interval);

 IF units IN ('wk', 'ww', 'week') THEN
   diff = diff/7;
   RETURN diff;
 END IF;

 IF units IN ('dd', 'd', 'day') THEN
   RETURN diff;
 END IF;

 diff = diff * 24 + DATE_PART('hour', diff_interval); 

 IF units IN ('hh', 'hour') THEN
    RETURN diff;
 END IF;

 diff = diff * 60 + DATE_PART('minute', diff_interval);

 IF units IN ('mi', 'n', 'minute') THEN
    RETURN diff;
 END IF;

 diff = diff * 60 + DATE_PART('second', diff_interval);

 RETURN diff;
END;
$$;
Daniel L. VanDenBosch
sumber
1

Jawaban @WebWanderer sangat mirip dengan DateDiff menggunakan SQL server, tetapi tidak akurat. Itu karena penggunaan fungsi age ().

misalnya hari antara '2019-07-29' dan '2020-06-25' harus menghasilkan 332, namun, menggunakan fungsi age () akan mengembalikan 327. Karena age () mengembalikan '10 mons 27 hari "dan memperlakukannya setiap bulan sebagai 30 hari yang salah.

Anda harus menggunakan stempel waktu untuk mendapatkan hasil yang akurat. misalnya

ceil((select extract(epoch from (current_date::timestamp - <your_date>::timestamp)) / 86400))

Shengfeng Li
sumber
0

Berikut adalah contoh lengkap dengan keluaran. psql (10.1, server 9.5.10).

Anda mendapatkan 58, bukan nilai kurang dari 30.
Hapus fungsi age (), memecahkan masalah yang disebutkan posting sebelumnya.

drop table t;
create table t(
    d1 date
);

insert into t values(current_date - interval '58 day');

select d1
, current_timestamp - d1::timestamp date_diff
, date_part('day', current_timestamp - d1::timestamp)
from t;

     d1     |        date_diff        | date_part
------------+-------------------------+-----------
 2018-05-21 | 58 days 21:41:07.992731 |        58
Charlie 木匠
sumber
1
Jawaban ini tidak menjawab pertanyaan tentang cara mereplikasi tanggalDiff (yy, '2010-04-01', '2012-03-05') = 2, dan versi bulan.
André C. Andersen
0

Satu solusi lagi, versi untuk perbedaan 'tahun':

SELECT count(*) - 1 FROM (SELECT distinct(date_trunc('year', generate_series('2010-04-01'::timestamp, '2012-03-05', '1 week')))) x

    2

(1 baris)

Dan trik yang sama selama berbulan-bulan:

SELECT count(*) - 1 FROM (SELECT distinct(date_trunc('month', generate_series('2010-04-01'::timestamp, '2012-03-05', '1 week')))) x

   23

(1 baris)

Dalam kueri kehidupan nyata mungkin ada beberapa urutan timestamp yang dikelompokkan berdasarkan jam / hari / minggu / dll, bukan generate_series.

Ini 'count(distinct(date_trunc('month', ts)))'dapat digunakan tepat di sisi 'kiri' pilihan:

SELECT sum(a - b)/count(distinct(date_trunc('month', c))) FROM d

Saya menggunakan generate_series () di sini hanya untuk singkatnya.

Vladimir Kunschikov
sumber