PostgreSQL: дні / місяці / роки між двома датами


94

Я шукаю спосіб реалізації функції SQLServer dateiff у PostgreSQL. Це є,

Ця функція повертає підрахунок (у вигляді цілочисельного значення з підписом) зазначених меж частини дати, перетнутих між вказаною датою початку та кінцевою датою.

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

Я знаю, що міг би зробити "дд", просто використовуючи віднімання, але чи маєш уявлення про інші два?


Відповіді:


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;

Це дасть вам повні роки, місяць, дні ... між двома датами:

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

Більш детальна інформація про дату виходу .


8
+1 через вікову функцію, але я думаю, що аргументи в неправильному порядку :)
dcarneiro

2
select date_part('month', age('2010-04-01', '2012-03-05'));дає -11. Це не правильна різниця в місяцях
smac89

1
виберіть date_part ('місяць', вік ('2012-03-05', '2010-04-01'));
Zuko

35
це не враховує місяці + роки і т. д. Він просто перевіряє постійний день - тобто SELECT date_part('day', age('2016-10-05', '2015-10-02'))повертається3
Дон Чідл

Питання полягає в тому, як порахувати, скільки річних перетинів кордонів, і скільки місячних переходів через дві дати. Використовувати age()завжди буде неправильно, якщо робити це роками та місяцями. Між 2014-12-31і 2015-01-01тому дасть 0 для місяця і року, тоді як datediff()дасть 1 і 1. Ви не можете просто додати одиницю до age()результату, тому що age()+1дасте 1, коли між 2015-01-01і 2015-01-02, тоді як datediff()тепер дасть 0.
Андре К. Андерсен

147

Просто відніміть їх:

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

Результат:

 days
------
   11

2
Так, це працює для PostgreSQL, коли вам потрібно знати кількість днів між двома датами.
icl7126

Чудово! FYI Я використовував його для замовлення записів меншим діапазоном між двома датами, напр .:range_end - range_start ASC, id DESC
Едісон Мачадо

1
Майте на увазі, цей метод повертає тип interval, який не можна просто передати як int. Як перетворити інтервал на кількість годин за допомогою postgres? . Використання комбінації функцій date_part/ age, як згадано у відповіді @IgorRomanchenko, поверне типdouble precision
WebWanderer

2
Якщо добре подумати, це правильний шлях. Використання цього методу для отримання кількості днів між двома датами буде враховувати дні між місяцями та роками, тоді як date_part/ ageвідповідь, як прийнято, буде надавати дні як різницю в днях частини двох дат. date_part('day', age('2016-9-05', '2015-10-02'))повертається 3.
WebWanderer

1
Питання полягало не в днях, а в кількості місяця та року, які повинні бути перетнуті між двома датами. Аскер вже знав, як робити дні.
Андре К. Андерсен,

41

Я витратив якийсь час на пошук найкращої відповіді, і, думаю, у мене є.

Цей sql дасть вам кількість днів між двома датами як integer:

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

..котре при запуску сьогодні ( 2017-3-28) надає мені:

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

Помилка щодо прийнятої відповіді:

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

.. полягає в тому, що ви отримаєте буквальну різницю між частинами рядків дат, а не кількість часу між двома датами.

IE:

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

Years(double precision)=-1;

Months(double precision)=-11;

Days(double precision)=-4;


7
Це має бути прийнятою відповіддю. Єдиний твік, який я пропоную, це використовувати ceil () замість трансляції до int (для округлення часткових днів, замість усічення).
Роб

2
Хороший момент @Rob. Читачі повинні це помітити. Я бачу, що це більше вподобання. Я думаю, що в більшості своїх випадків я хотів би скоротити своє значення як буквальну кількість днів між датами, але я бачу, де також слід використовувати округлення кількості днів.
WebWanderer

2
Цей підхід може схибити перехід на літній час у Великобританії - буде один день на рік лише 23 години, а інший
qcode peter,

Вау @qcodepeter! Хороший улов! Ви абсолютно праві! Як це виправити?
WebWanderer

1
Це насправді не відповідає на питання. Питання полягає в тому, як порахувати, скільки річних перетинів кордонів, і скільки місячних переходів через дві дати. Ця відповідь лише відповідає, скільки днів є, і не враховує високосних років.
Андре К. Андерсен,

9

Майже та сама функція, яка вам потрібна (на основі відповіді Атіруза, скорочена версія UDF звідси )

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

Використання:

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

Ця відповідь неправильна. DATEDIFF()Функції в MS SQL повертає 1для datediff(year, '2015-02-14', '2016-01-03'). Це пов’язано з тим, що між цими датами вам доведеться перейти межу року один раз: docs.microsoft.com/en-us/sql/t-sql/functions/…
Андре К. Андерсен,

Відповідь правильна, оскільки вихідне питання стосувалось PostgreSQL, а не MS SQL.
Riki_tiki_tavi

Я розумію, що це важко підібрати, але початкове питання полягало в тому, щоб знайти "спосіб реалізації функції SQLServer dateiff у PostgreSQL". Користувачі MS SQL Server, як правило, просто називають його SQL Server. Спробуйте погуглити "SQL Server": i.imgur.com/USCHdLS.png Користувач хотів перенести функцію з однієї конкретної технології на antoher.
Андре К. Андерсен,

Вибачте, ви мали рацію. Здається, я поспішав і не зрозумів значення вашого першого коментаря. Відповідь оновлено.
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

Результат: 6


1

Це питання сповнене непорозумінь. Спочатку давайте повністю розберемось у питанні. Аскер хоче отримати той же результат, що і для при виконанні функції MS SQL Server DATEDIFF ( datepart , startdate , enddate ) , де datepartприймає dd, mmабо yy.

Ця функція визначається:

Ця функція повертає підрахунок (у вигляді цілочисельного значення з підписом) зазначених меж частини дати, перетнутих між вказаною датою початку та кінцевою датою.

Це означає, скільки меж дня, меж місяця чи року перетнуто. Не скільки днів, місяців чи років між ними. Ось чому datediff(yy, '2010-04-01', '2012-03-05')дорівнює 2, а не 1. Між цими датами менше 2 років, тобто минув лише 1 рік, але межі 2 років переступили з 2010 по 2011 рік і з 2011 по 2012 рік.

Далі - моя найкраща спроба правильно відтворити логіку.

-- 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 і отримати дані там. Я створив функцію dateiff, яка робить майже все, що робить сервер sql. Таким чином, ми можемо врахувати будь-яку одиницю.

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 дуже близька до DateDiff за допомогою сервера SQL, але неточна. Це через використання функції age ().

наприклад, дні між '2019-07-29' та '2020-06-25' повинні повернути 332, однак, використовуючи функцію age (), вона поверне 327. Оскільки age () повертає '10 mons 27 днів ", і це обробляє щомісяця як 30 днів, що є неправильним.

Ви повинні використовувати відмітку часу, щоб отримати точний результат. напр

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


0

Ось повний приклад з вихідними даними. psql (10.1, сервер 9.5.10).

Ви отримуєте 58, а не якесь значення менше 30.
Видалення функції age (), вирішило проблему, про яку згадував попередній пост.

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
Ця відповідь не відповідає на питання про те, як відтворити dateiff (yy, '2010-04-01', '2012-03-05') = 2, і місячну версію.
Андре К. Андерсен,

0

Ще одне рішення, версія для різниці "років":

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

    2

(1 ряд)

І той же фокус протягом місяців:

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

   23

(1 ряд)

У реальному життєвому запиті може бути кілька послідовностей міток часу, згрупованих за годиною / днем ​​/ тижнем / тощо, замість create_series.

Це 'count(distinct(date_trunc('month', ts)))'можна використовувати прямо в лівій частині елемента вибору:

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

Я використовував тут create_series () лише для стислості.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.