PostgreSQL: ngày / tháng / năm giữa hai ngày


94

Tôi đang tìm cách triển khai hàm SQLServer-dateiff trong PostgreSQL. Đó là,

Hàm này trả về số lượng (dưới dạng giá trị số nguyên có dấu) của các ranh giới ngày tháng cụ thể được giao giữa ngày bắt đầu và ngày kết thúc được chỉ định.

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

Tôi biết tôi có thể làm 'dd' chỉ bằng cách sử dụng phân số, nhưng bạn có ý kiến ​​gì về hai phép còn lại không?


Câu trả lời:


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;

Điều này sẽ cung cấp cho bạn đầy đủ năm, tháng, ngày ... giữa hai ngày:

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

Nhiều chi tiết datediff thông tin .


8
1 vì chức năng tuổi, nhưng tôi nghĩ rằng đối số của nó là theo thứ tự sai :)
dcarneiro

2
select date_part('month', age('2010-04-01', '2012-03-05'));cho -11. Đó không phải là sự khác biệt đúng trong những tháng
smac89

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

35
điều này không giải thích cho tháng năm + vv Nó chỉ làm một tấm séc lăn ngày - tức là SELECT date_part('day', age('2016-10-05', '2015-10-02'))lợi nhuận3
Don Cheadle

Câu hỏi đặt ra là làm thế nào để đếm xem có bao nhiêu giao cắt ranh giới năm và bao nhiêu giao cắt ranh giới tháng giữa hai ngày. Việc sử dụng age()sẽ luôn luôn là sai lầm trong nhiều năm và nhiều tháng. Giữa 2014-12-312015-01-01trước sẽ cho 0 cho tháng và năm, trong khi datediff()sẽ cho 1 và 1. Bạn không thể chỉ thêm một vào age()kết quả vì age()+1sẽ cho 1 khi giữa 2015-01-012015-01-02, trong khi datediff()bây giờ sẽ cho 0.
André C. Andersen 16/01

147

Đơn giản chỉ cần trừ chúng:

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

Kết quả:

 days
------
   11

2
Có, điều này hoạt động cho PostgreSQL khi bạn cần biết số ngày giữa hai ngày.
icl7126

Tuyệt quá! FYI Tôi đã sử dụng nó để sắp xếp các bản ghi theo phạm vi nhỏ hơn giữa hai ngày, ví dụ:range_end - range_start ASC, id DESC
Edison Machado

1
Hãy lưu ý, phương thức này trả về kiểu interval, không thể đơn giản được ép kiểu int. Làm cách nào để chuyển đổi khoảng thời gian thành một số giờ với postgres? . Sử dụng tổ hợp chức năng date_part/ age, như được đề cập trong câu trả lời của @IgorRomanchenko sẽ trả về loạidouble precision
WebWanderer

2
Theo suy nghĩ thứ hai, đây là cách chính xác. Sử dụng phương pháp này để lấy số ngày giữa hai ngày sẽ tính số ngày giữa tháng và năm, trong khi câu trả lời date_part/ age, như được chấp nhận, sẽ cung cấp số ngày là sự khác biệt về số ngày của hai ngày. date_part('day', age('2016-9-05', '2015-10-02'))trả về 3.
WebWanderer

1
Câu hỏi không phải về ngày, mà là về số lượng ranh giới tháng và năm phải vượt qua giữa hai ngày. Người hỏi đã biết cách làm ngày.
André C. Andersen

41

Tôi đã dành một chút thời gian để tìm kiếm câu trả lời tốt nhất và tôi nghĩ rằng tôi đã có nó.

Sql này sẽ cung cấp cho bạn số ngày giữa hai ngày là integer:

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

.. mà, khi chạy hôm nay ( 2017-3-28), cung cấp cho tôi:

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

Quan niệm sai lầm về câu trả lời được chấp nhận:

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

.. là bạn sẽ nhận được sự khác biệt theo nghĩa đen giữa các phần của chuỗi ngày, không phải lượng thời gian giữa hai ngày.

I E:

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

Years(double precision)=-1;

Months(double precision)=-11;

Days(double precision)=-4;


7
Đây phải là câu trả lời được chấp nhận. Tinh chỉnh duy nhất tôi đề xuất là sử dụng ceil () thay vì truyền sang int (để làm tròn số ngày một phần, thay vì cắt bớt).
Rob

2
Điểm tốt @Rob. Bạn đọc cần lưu ý điều này. Tôi có thể xem đây là một sở thích hơn. Tôi nghĩ rằng trong hầu hết các trường hợp của mình, tôi muốn cắt bớt giá trị của mình dưới dạng số ngày theo nghĩa đen giữa các ngày, nhưng tôi có thể thấy nơi làm tròn số ngày cũng nên được sử dụng.
WebWanderer

2
Cách tiếp cận này có thể vấp lên bởi Anh giờ mùa hè - sẽ có một ngày nào đó một năm chỉ có 23 tiếng đồng hồ, và khác với 25.
qcode peter

Chà @qcodepeter! Nắm bắt tốt! Bạn hoàn toàn chính xác! Làm thế nào để chúng tôi sửa chữa điều đó?
WebWanderer

1
Điều này không thực sự trả lời câu hỏi. Câu hỏi đặt ra là làm thế nào để đếm xem có bao nhiêu giao cắt ranh giới năm và bao nhiêu giao cắt ranh giới tháng giữa hai ngày. Câu trả lời này chỉ trả lời có bao nhiêu ngày, và không tính đến năm nhuận.
André C. Andersen,

9

Gần giống chức năng bạn cần (dựa trên câu trả lời của atiruz, phiên bản rút gọn của UDF từ đây )

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

Sử dụng:

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

Câu trả lời này không chính xác. Các DATEDIFF()chức năng trong MS SQL trả về 1cho datediff(year, '2015-02-14', '2016-01-03'). Điều này là do bạn phải vượt qua ranh giới năm một lần giữa những ngày đó: docs.microsoft.com/en-us/sql/t-sql/functions/…
André C. Andersen

Câu trả lời là đúng vì câu hỏi ban đầu là về PostgreSQL, không phải MS SQL.
Riki_tiki_tavi

Tôi hiểu nó có thể khó tiếp thu, nhưng câu hỏi ban đầu là tìm "cách triển khai SQLServer-function dateiff trong PostgreSQL". Người dùng MS SQL Server có xu hướng chỉ gọi nó là SQL Server. Thử googling "SQL Server": i.imgur.com/USCHdLS.png Người dùng muốn chuyển một hàm từ một công nghệ cụ thể sang antoher.
André C. Andersen

Xin lỗi, bạn đã đúng. Có vẻ như tôi đã vội vàng và không hiểu ý nghĩa của nhận xét đầu tiên của bạn. Câu trả lời đã được cập nhật.
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

Kết quả: 6


2

Câu trả lời của @WebWanderer rất gần với DateDiff sử dụng máy chủ SQL, nhưng không chính xác. Đó là do cách sử dụng hàm age ().

ví dụ: các ngày giữa '2019-07-29' và '2020-06-25' sẽ trả về 332, tuy nhiên, sử dụng hàm age (), nó sẽ trả về 327. Vì age () trả về '10 gió 27 ngày "và nó xử lý mỗi tháng là 30 ngày, điều này không chính xác.

Bạn sử dụng dấu thời gian để có kết quả chính xác. ví dụ

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


trông giống như câu trả lời hay nhất
urmurmur

1

Câu hỏi này đầy sự hiểu lầm. Đầu tiên hãy hiểu câu hỏi một cách đầy đủ. Người hỏi muốn để có được kết quả tương tự như đối với khi chạy máy chủ chức năng MS SQL DATEDIFF ( datepart , startdate , enddate ) nơi datepartmất dd, mmhoặc yy.

Chức năng này được xác định bởi:

Hàm này trả về số lượng (dưới dạng giá trị số nguyên có dấu) của các ranh giới ngày tháng cụ thể được giao giữa ngày bắt đầu và ngày kết thúc được chỉ định.

Điều đó có nghĩa là có bao nhiêu ranh giới ngày, ranh giới tháng hoặc ranh giới năm, được vượt qua. Không phải là bao nhiêu ngày, tháng, hay năm giữa chúng. Đó là lý do tại sao lại datediff(yy, '2010-04-01', '2012-03-05')là 2, chứ không phải 1. Có ít hơn 2 năm giữa những ngày đó, nghĩa là chỉ 1 năm đã trôi qua, nhưng ranh giới 2 năm đã vượt qua, từ 2010 đến 2011 và từ 2011 đến 2012.

Sau đây là nỗ lực tốt nhất của tôi trong việc tái tạo logic một cách chính xác.

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

Tôi muốn mở rộng câu trả lời của Riki_tiki_tavi và lấy dữ liệu ra khỏi đó. Tôi đã tạo một hàm dateiff thực hiện hầu hết mọi thứ mà máy chủ sql làm. Vì vậy, theo cách đó chúng ta có thể tính đến bất kỳ đơn vị nào.

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

0

Đây là một ví dụ đầy đủ với đầu ra. psql (10.1, máy chủ 9.5.10).

Bạn nhận được 58, không phải một số giá trị nhỏ hơn 30.
Loại bỏ hàm age (), giải quyết vấn đề mà bài trước đã đề cập.

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
Câu trả lời này không trả lời câu hỏi về cách sao chép dateiff (yy, '2010-04-01', '2012-03-05') = 2 và phiên bản tháng.
André C. Andersen

0

Một giải pháp khác, phiên bản cho sự khác biệt 'năm':

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

    2

(1 hàng)

Và thủ thuật tương tự trong các tháng:

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

   23

(1 hàng)

Trong truy vấn đời thực, có thể có một số chuỗi dấu thời gian được nhóm theo giờ / ngày / tuần / v.v. thay vì create_series.

Điều này 'count(distinct(date_trunc('month', ts)))'có thể được sử dụng ngay ở bên trái của lựa chọn:

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

Tôi đã sử dụng create_series () ở đây chỉ cho ngắn gọn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.