PostgreSQL: Tage / Monate / Jahre zwischen zwei Daten

87

Ich suche nach einer Möglichkeit, die SQLServer-Funktion dateiff in PostgreSQL zu implementieren . Das ist,

Diese Funktion gibt die Anzahl (als vorzeichenbehafteten ganzzahligen Wert) der angegebenen Datumsteilgrenzen zurück, die zwischen dem angegebenen Start- und Enddatum überschritten wurden.

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

Ich weiß, ich könnte 'dd' machen, indem ich einfach Subtraktion benutze, aber eine Idee über die anderen beiden?

gefei
quelle

Antworten:

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

Dies gibt Ihnen volle Jahre, Monate, Tage ... zwischen zwei Daten:

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

Detailliertere Informationen zum Datum .

Igor Romanchenko
quelle
8
+1 wegen der
Altersfunktion
2
select date_part('month', age('2010-04-01', '2012-03-05'));gibt -11. Das ist kein korrekter Unterschied in Monaten
smac89
1
Wählen Sie date_part ('Monat', Alter ('2012-03-05', '2010-04-01'));
Olu Smith
33
Dies berücksichtigt nicht Monate + Jahre usw. Es wird nur ein rollierender Tag überprüft - dh SELECT date_part('day', age('2016-10-05', '2015-10-02'))zurückgegeben3
Don Cheadle
Die Frage ist, wie man zählt, wie viele Jahresgrenzüberschreitungen und wie viele Monatsgrenzüberschreitungen zwischen zwei Daten liegen. Die Verwendung age()wird immer falsch sein, um dies für Jahre und Monate zu tun. Zwischen 2014-12-31und 2015-01-01vor wird 0 für Monat und Jahr geben, während datediff()1 und 1 gegeben werden. Sie können nicht einfach eins zum age()Ergebnis hinzufügen, da age()+1zwischen 2015-01-01und 1 1 ergibt 2015-01-02, während datediff()jetzt 0 ergibt.
André C. Andersen
143

Subtrahieren Sie sie einfach:

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

Das Ergebnis:

 days
------
   11
mehdi
quelle
2
Ja, dies funktioniert für PostgreSQL, wenn Sie die Anzahl der Tage zwischen zwei Daten kennen müssen.
icl7126
Toll! Zu Ihrer Information Ich habe es verwendet, um Datensätze in einem kleineren Bereich zwischen zwei Daten zu bestellen, z. B.:range_end - range_start ASC, id DESC
Edison Machado
1
Beachten Sie, dass diese Methode den Typ zurückgibt interval, der nicht einfach als umgewandelt werden kann int. Wie konvertiere ich ein Intervall mit Postgres in mehrere Stunden? . Die Verwendung von date_part/ ageFunktion kombiniert, wie in @IgorRomanchenko ‚s Antwort erwähnt wird Typen zurückgebendouble precision
WebWanderer
2
Beim zweiten Gedanken ist dies der richtige Weg. Wenn Sie diese Methode verwenden, um die Anzahl der Tage zwischen zwei Daten zu ermitteln, werden die Tage zwischen Monaten und Jahren gezählt, während die date_part/ ageAntwort, wie akzeptiert, Tage als Differenz zwischen den Tagen der beiden Daten angibt. date_part('day', age('2016-9-05', '2015-10-02'))kehrt zurück 3.
WebWanderer
1
Bei der Frage ging es nicht um Tage, sondern um die Anzahl der Monats- und Jahresgrenzen, die zwischen zwei Daten überschritten werden müssen. Der Fragesteller wusste bereits, wie man Tage macht.
André C. Andersen
38

Ich habe einige Zeit damit verbracht, nach der besten Antwort zu suchen, und ich glaube, ich habe sie.

Diese SQL gibt Ihnen die Anzahl der Tage zwischen zwei Daten als integer:

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

..was, wenn heute ( 2017-3-28) ausgeführt, bietet mir:

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

Das Missverständnis über die akzeptierte Antwort:

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

..ist, dass Sie den wörtlichen Unterschied zwischen den Teilen der Datumszeichenfolgen erhalten, nicht die Zeitspanne zwischen den beiden Datumsangaben.

IE:

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

Years(double precision)=-1;

Months(double precision)=-11;

Days(double precision)=-4;

WebWanderer
quelle
6
Dies sollte die akzeptierte Antwort sein. Die einzige Optimierung, die ich vorschlage, ist die Verwendung von Ceil () anstelle von Casting in Int (um Teiltage aufzurunden, anstatt abzuschneiden).
Rob
2
Guter Punkt @Rob. Leser sollten dies zur Kenntnis nehmen. Ich kann das eher als Präferenz ansehen. Ich denke, dass ich in den meisten Fällen meinen Wert als wörtliche Anzahl von Tagen zwischen Daten abschneiden möchte, aber ich kann sehen, wo auch die Rundung der Anzahl von Tagen verwendet werden sollte.
WebWanderer
1
Dieser Ansatz kann durch die Sommerzeit in Großbritannien ausgelöst werden - es wird einen Tag im Jahr mit nur 23 Stunden und einen anderen mit 25 Stunden geben.
qcode peter
Wow @qcodepeter! Guter Fang! Sie sind absolut richtig! Wie beheben wir das?
WebWanderer
1
Dies beantwortet die Frage nicht wirklich. Die Frage ist, wie man zählt, wie viele Jahresgrenzüberschreitungen und wie viele Monatsgrenzüberschreitungen zwischen zwei Daten liegen. Diese Antwort gibt nur Auskunft über die Anzahl der Tage und berücksichtigt keine Schaltjahre.
André C. Andersen
9

Fast die gleiche Funktion, die Sie benötigt haben (basierend auf der Antwort von atiruz, verkürzte Version von UDF von hier )

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

Verwendung:

/* 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
quelle
Diese Antwort ist falsch. Die DATEDIFF()Funktion in MS SQL gibt 1für zurück datediff(year, '2015-02-14', '2016-01-03'). Dies liegt daran, dass Sie die Jahresgrenze
André C. Andersen
Die Antwort ist richtig, da die ursprüngliche Frage PostgreSQL und nicht MS SQL betraf.
Riki_tiki_tavi
Ich verstehe, dass es schwierig sein kann, es zu verstehen, aber die ursprüngliche Frage war, "einen Weg zu finden, um die SQLServer-Funktion dateiff in PostgreSQL zu implementieren". Benutzer von MS SQL Server nennen es normalerweise nur SQL Server. Versuchen Sie, "SQL Server" zu googeln: i.imgur.com/USCHdLS.png Der Benutzer wollte eine Funktion von einer bestimmten Technologie auf eine andere portieren.
André C. Andersen
Entschuldigung, du hattest recht. Es scheint, dass ich mich beeilte und die Bedeutung Ihres ersten Kommentars nicht verstand. Die Antwort wurde aktualisiert.
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

Ergebnis: 6

atiruz
quelle
1

Diese Frage ist voller Missverständnisse. Lassen Sie uns zunächst die Frage vollständig verstehen. Der Fragesteller hat will das gleiche Ergebnis wie bekommen , wenn die Lauf MS SQL Server - Funktion DATEDIFF ( datepart , startdate , enddate ) , wo datepartnimmt dd, mmoder yy.

Diese Funktion ist definiert durch:

Diese Funktion gibt die Anzahl (als vorzeichenbehafteten ganzzahligen Wert) der angegebenen Datumsteilgrenzen zurück, die zwischen dem angegebenen Start- und Enddatum überschritten wurden.

Das heißt, wie viele Tages-, Monats- oder Jahresgrenzen überschritten werden. Nicht wie viele Tage, Monate oder Jahre es zwischen ihnen ist. Deshalb datediff(yy, '2010-04-01', '2012-03-05')ist 2 und nicht 1. Zwischen diesen Daten liegen weniger als 2 Jahre, was bedeutet, dass nur 1 ganzes Jahr vergangen ist, aber 2 Jahre Grenzen überschritten haben, von 2010 bis 2011 und von 2011 bis 2012.

Das Folgende ist mein bester Versuch, die Logik korrekt zu replizieren.

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

Ich möchte die Antwort von Riki_tiki_tavi erweitern und die Daten dort herausbringen. Ich habe eine datierte Tiff-Funktion erstellt, die fast alles macht, was SQL Server macht. Auf diese Weise können wir jede Einheit berücksichtigen.

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

Hier ist ein vollständiges Beispiel mit Ausgabe. psql (10.1, Server 9.5.10).

Sie erhalten 58, nicht einen Wert unter 30.
Entfernen Sie die Funktion age (), um das im vorherigen Beitrag erwähnte Problem zu lösen.

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 木匠
quelle
1
Diese Antwort beantwortet nicht die Frage, wie datiertes Datum (JJ, '2010-04-01', '2012-03-05') = 2 und Monatsversion repliziert werden soll.
André C. Andersen
0

Eine weitere Lösung, Version für den Unterschied der Jahre:

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

    2

(1 Reihe)

Und der gleiche Trick für die Monate:

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

   23

(1 Reihe)

In der realen Abfrage kann es einige Zeitstempelsequenzen geben, die nach Stunde / Tag / Woche / usw. anstelle von generate_series gruppiert sind.

Dies 'count(distinct(date_trunc('month', ts)))'kann direkt auf der linken Seite der Auswahl verwendet werden:

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

Ich habe hier nur der Kürze halber generate_series () verwendet.

Vladimir Kunschikov
quelle
0

Die Antwort von @WebWanderer kommt dem DateDiff mit SQL Server sehr nahe, ist aber ungenau. Dies liegt an der Verwendung der Funktion age ().

zB Tage zwischen '2019-07-29' und '2020-06-25' sollten 332 zurückgeben. Bei Verwendung der Funktion age () werden jedoch 327 zurückgegeben. Da age () '10 mons 27 Tage 'zurückgibt und behandelt wird jeden Monat als 30 Tage, was falsch ist.

Sie sollten den Zeitstempel verwenden, um das genaue Ergebnis zu erhalten. z.B

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

Shengfeng Li
quelle