PostgreSQL: iki tarih arasındaki günler / aylar / yıllar


94

PostgreSQL'deki SQLServer-dateiff işlevini uygulamak için bir yol arıyorum . Yani,

Bu işlev, belirtilen başlangıç ​​tarihi ve bitiş tarihi arasında geçen belirtilen tarih bölümü sınırlarının sayısını (işaretli bir tamsayı değeri olarak) döndürür.

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

Basitçe çıkarma kullanarak 'dd' yapabileceğimi biliyorum, ama diğer ikisi hakkında herhangi bir fikriniz var mı?


4

Yanıtlar:


116
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;

Bu size iki tarih arasında tam yıllar, ay, günler verecektir :

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

Daha ayrıntılı tarihli bilgiler .


8
Yaş işlevi nedeniyle +1, ancak argümanların yanlış sırada olduğunu düşünüyorum :)
dcarneiro

2
select date_part('month', age('2010-04-01', '2012-03-05'));verir -11. Bu aylar arasındaki doğru bir fark değil
smac89

1
tarih_bölümü seçin ('ay', yaş ('2012-03-05', '2010-04-01'));
Zuko

35
bu aylar + yılları vb. hesaba katmaz. Sadece sürekli bir gün kontrolü yapar - yani SELECT date_part('day', age('2016-10-05', '2015-10-02'))geri gelir3
Don Cheadle

Soru, kaç yıllık sınır geçişlerinin ve iki tarih arasında kaç aylık sınır geçişlerinin nasıl hesaplanacağıdır. Kullanımı age()her zaman yanlış olacak yıllar ve aylar için yapmak. Arası 2014-12-31ve 2015-01-01öncesi ay ve yıl için 0 verirken datediff(), 1 ve 1 verir. age()Sonuca bir tane ekleyemezsiniz çünkü ve age()+1arasında 1 verir , oysa şimdi 0 verir.2015-01-012015-01-02datediff()
André C. Andersen

147

Çıkarın:

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

Sonuç:

 days
------
   11

2
Evet, iki tarih arasındaki gün sayısını bilmeniz gerektiğinde bu PostgreSQL için işe yarar.
icl7126

Harika! Bilginize, kayıtları iki tarih arasında daha küçük aralıklarla sipariş etmek için kullandım, örn .:range_end - range_start ASC, id DESC
Edison Machado

1
Unutmayın, bu yöntem intervalbasitçe dönüştürülemeyen tür döndürür int. Postgres ile bir aralığı saat sayısına nasıl dönüştürebilirim? . @IgorRomanchenko'nun cevabında belirtildiği gibi date_part/ ageişlev kombinasyonunu kullanmak, türü döndürürdouble precision
WebWanderer

2
İkinci olarak düşündüğümüzde, bu doğru yoldur. İki tarih arasındaki günlerin sayısını elde etmek için bu yöntemi kullanmak, aylar ve yıllar arasındaki günleri sayarken date_part/ ageyanıt, kabul edildiği şekilde, iki tarihin günler bölümünde günler sağlayacaktır. date_part('day', age('2016-9-05', '2015-10-02'))3 döndürür
WebWanderer

1
Soru günlerle ilgili değildi, iki tarih arasında aşılması gereken ay ve yıl sınırlarının sayısı hakkındaydı. Soru soran, günlerin nasıl yapılacağını zaten biliyordu.
André

41

En iyi cevabı bulmak için biraz zaman harcadım ve sanırım buldum.

Bu sql size iki tarih arasındaki günlerin sayısını şu şekilde verecektir integer:

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

..bugün çalıştırıldığında ( 2017-3-28) bana şunu sağlar:

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

Kabul edilen cevap hakkındaki yanlış kanı:

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'));

.. iki tarih arasındaki zaman miktarını değil, tarih dizelerinin bölümleri arasındaki gerçek farkı elde edeceğinizdir.

IE:

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

Years(double precision)=-1;

Months(double precision)=-11;

Days(double precision)=-4;


7
Kabul edilen cevap bu olmalıdır. Önerdiğim tek ince ayar int'e çevrim yerine ceil () kullanmaktır (kesmek yerine kısmi günleri yuvarlamak için).
Rob

2
İyi nokta @Rob. Okuyucular bunu not etmelidir. Bunu daha çok bir tercih olarak görebiliyorum. Sanırım çoğu durumda, değerimi tarihler arasındaki gerçek gün sayısı olarak kısaltmak isterdim, ancak gün sayısını yuvarlamanın nerede kullanılması gerektiğini de görebiliyorum.
WebWanderer

2
Bu yaklaşım, Birleşik Krallık yaz saati uygulamasıyla
aşılabilir

Vay @qcodepeter! İyi yakalama! Kesinlikle haklısın! Bunu nasıl düzelteceğiz?
WebWanderer

1
Bu soruya gerçekten cevap vermiyor. Soru, kaç yıllık sınır geçişlerinin ve iki tarih arasında kaç aylık sınır geçişlerinin nasıl hesaplanacağıdır. Bu cevap yalnızca kaç gün olduğunu yanıtlar ve artık yılları hesaba katmaz.
André

9

Neredeyse ihtiyacınız olan işleve sahip (atiruz'un cevabına göre, buradan UDF'nin kısaltılmış versiyonu )

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;
$$;

Kullanım:

/* 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 */

Bu cevap yanlıştır. DATEDIFF()MS SQL işlevin döndürdüğü 1için datediff(year, '2015-02-14', '2016-01-03'). Bunun nedeni, bu tarihler arasında yıl sınırını bir kez geçmeniz gerektiğidir
André C. Andersen

Cevap doğrudur çünkü asıl soru MS SQL değil, PostgreSQL hakkındaydı.
Riki_tiki_tavi

Anladığım kadarıyla bunu anlamak zor olabilir, ancak asıl soru "PostgreSQL'de tarihli SQLServer işlevini uygulamanın bir yolunu" bulmaktı. MS SQL Server kullanıcıları buna sadece SQL Server adını verme eğilimindedir. Googling "SQL Server" ı deneyin: i.imgur.com/USCHdLS.png Kullanıcı, bir işlevi belirli bir teknolojiden diğerine taşımak istedi.
André C. Andersen

Üzgünüm haklıydın. Görünüşe göre acele ediyordum ve ilk yorumun anlamını anlamadım. Cevap güncellendi.
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

Sonuç: 6


1

Bu soru yanlış anlamalarla dolu. Önce soruyu tam olarak anlayalım. Asker çalıştırırken için aynı sonucu almak istiyor MS SQL Server işlevini alır , ya da .DATEDIFF ( datepart , startdate , enddate )datepartddmmyy

Bu işlev şu şekilde tanımlanır:

Bu işlev, belirtilen başlangıç ​​tarihi ve bitiş tarihi arasında geçen belirtilen tarih bölümü sınırlarının sayısını (işaretli bir tamsayı değeri olarak) döndürür.

Bu, kaç gün sınırının, ay sınırının veya yıl sınırının aşıldığı anlamına gelir. Aralarında kaç gün, ay veya yıl olduğu değil. Bu yüzden datediff(yy, '2010-04-01', '2012-03-05')2'dir, 1 değil. Bu tarihler arasında 2 yıldan az bir süre var, yani sadece 1 tam yıl geçti, ancak 2010'dan 2011'e ve 2011'den 2012'ye kadar 2 yıllık sınırlar aşıldı.

Aşağıdakiler, mantığı doğru şekilde kopyalamak için en iyi girişimimdir.

-- 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

1

Riki_tiki_tavi'nin cevabını genişletmek ve verileri oraya götürmek istiyorum. Sql sunucusunun yaptığı hemen hemen her şeyi yapan bir dateiff işlevi oluşturdum. Böylece herhangi bir birimi hesaba katabiliriz.

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;
$$;

1

@WebWanderer'ın yanıtı SQL sunucusu kullanan DateDiff'e çok yakın, ancak yanlış. Bunun nedeni age () işlevinin kullanılmasıdır.

Örneğin, '2019-07-29' ve '2020-06-25' arasındaki günler 332'yi döndürmelidir, ancak, age () işlevini kullanarak 327'yi döndürür. age (), '10 mons 27 gün "döndürdüğünden her ay 30 gün olarak yanlıştır.

Doğru sonucu almak için zaman damgasını kullanmanız gerekir. Örneğin

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


0

İşte çıktı ile tam bir örnek. psql (10.1, sunucu 9.5.10).

30'dan az değil, 58 alırsınız.
Age () fonksiyonunu kaldırın, önceki yazıda bahsedilen problemi çözdü.

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

1
Bu cevap, dateiff (yy, '2010-04-01', '2012-03-05') = 2 ve aylık versiyonun nasıl kopyalanacağı sorusuna cevap vermez.
André C. Andersen

0

'Yıllar' farkı için bir çözüm daha:

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

    2

(1 sıra)

Ve aylar için aynı numara:

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

   23

(1 sıra)

Gerçek hayattaki sorguda, generate_series yerine saat / gün / hafta / vb. İle gruplanmış bazı zaman damgası dizileri olabilir.

Bu 'count(distinct(date_trunc('month', ts)))', seçimin 'sol' tarafında sağda kullanılabilir:

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

Burada kısalık olması için generate_series () kullandım.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.