PostgreSQL: วัน / เดือน / ปีระหว่างวันที่สองวัน


94

ฉันกำลังมองหาวิธีใช้ฟังก์ชัน SQLServer ลงวันที่ใน 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

ฉันรู้ว่าฉันสามารถทำ 'dd' ได้โดยใช้การแยกส่วน แต่มีความคิดเกี่ยวกับอีกสองอย่างหรือไม่


คำตอบ:


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;

สิ่งนี้จะให้คุณเต็มปีเดือนวัน ... ระหว่างวันที่สองวัน:

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

รายละเอียดเพิ่มเติมข้อมูล DateDiff


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

คำถามคือจะนับได้อย่างไรว่ามีการข้ามเขตแดนกี่ปีและการข้ามเขตแดนระหว่างวันที่สองวันมีกี่เดือน การใช้age()จะผิดเสมอหากทำเช่นนี้เป็นปีและเดือน ระหว่าง2014-12-31และ2015-01-01ที่ผ่านมาจะให้ 0 สำหรับเดือนและปีในขณะที่datediff()จะให้ 1 และ 1 คุณไม่สามารถเพิ่มหนึ่งในage()ผลลัพธ์ได้เนื่องจากage()+1จะให้ 1 เมื่อระหว่าง2015-01-01และ2015-01-02ในขณะที่datediff()ตอนนี้จะให้ 0
André C. Andersen

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

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
คำถามไม่ได้เกี่ยวกับวัน แต่เป็นเรื่องของขอบเขตของเดือนและปีที่ต้องข้ามระหว่างวันที่สองวัน ผู้ถามรู้แล้วว่าต้องทำวันอย่างไร
André C. Andersen

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 (เพื่อปัดเศษวันบางส่วนแทนการตัดทอน)
Rob

2
จุดดี @Rob. ผู้อ่านควรสังเกตสิ่งนี้ ฉันเห็นว่านี่เป็นความชอบมากกว่า ฉันคิดว่าในกรณีส่วนใหญ่ของฉันฉันต้องการตัดทอนค่าของฉันเป็นจำนวนวันที่แท้จริงระหว่างวันที่ แต่ฉันสามารถดูว่าควรใช้การปัดเศษจำนวนวันที่ใดด้วย
WebWanderer

2
แนวทางนี้สามารถลดลงได้ตามเวลาออมแสงของสหราชอาณาจักร - จะมีหนึ่งวันต่อปีโดยมีเพียง 23 ชั่วโมงและอีกวันหนึ่งคือ 25
qcode peter

ว้าว @qcodepeter! จับดี! คุณถูกต้อง! เราจะแก้ไขอย่างไร?
WebWanderer

1
นี่ตอบคำถามไม่ได้จริงๆ คำถามคือจะนับได้อย่างไรว่ามีการข้ามเขตแดนกี่ปีและการข้ามเขตแดนระหว่างวันที่สองวันมีกี่เดือน คำตอบนี้จะตอบเฉพาะจำนวนวันและไม่นับเป็นปีอธิกสุรทิน
André C. Andersen

9

เกือบจะเป็นฟังก์ชั่นเดียวกับที่คุณต้องการ (อ้างอิงจากคำตอบของ atiruz เวอร์ชันย่อของ 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/…
André C. Andersen

คำตอบถูกต้องเนื่องจากคำถามเดิมเกี่ยวกับ PostgreSQL ไม่ใช่ MS SQL
Riki_tiki_tavi

ฉันเข้าใจว่าอาจเป็นเรื่องยาก แต่คำถามเดิมคือการค้นหา "วิธีใช้ฟังก์ชัน SQLServer ลงวันที่ใน PostgreSQL" ผู้ใช้ MS SQL Server มักจะเรียกมันว่า SQL Server ลองใช้ googling "SQL Server": i.imgur.com/USCHdLS.pngผู้ใช้ต้องการพอร์ตฟังก์ชันจากเทคโนโลยีเฉพาะหนึ่งไปยัง antoher
André C. Andersen

ขออภัยคุณพูดถูก ดูเหมือนว่าฉันจะรีบร้อนและไม่เข้าใจความหมายของความคิดเห็นแรกของคุณ อัปเดตคำตอบแล้ว
Riki_tiki_tavi


2

คำตอบของ @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))


ดูเหมือนคำตอบที่ดีที่สุด
urmurmur

1

คำถามนี้เต็มไปด้วยความเข้าใจผิด ก่อนอื่นให้ทำความเข้าใจคำถามอย่างถ่องแท้ ถามต้องการที่จะได้รับผลเช่นเดียวกับเมื่อรันฟังก์ชั่น MS SQL Server DATEDIFF ( datepart , startdate , enddate )ที่datepartใช้เวลาdd, หรือmmyy

ฟังก์ชันนี้กำหนดโดย:

ฟังก์ชันนี้จะคืนค่าจำนวน (เป็นค่าจำนวนเต็มที่ลงนาม) ของขอบเขตวันที่ที่ระบุที่ข้ามระหว่างวันที่เริ่มต้นและวันที่สิ้นสุดที่ระบุ

นั่นหมายถึงว่ามีการข้ามขอบเขตวันขอบเขตเดือนหรือปี ไม่ว่าจะเป็นกี่วันเดือนหรือปี นั่นเป็นเหตุผลว่าทำไมจึง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;
$$;

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
คำตอบนี้ไม่ได้ตอบคำถามเกี่ยวกับวิธีการจำลองวันที่ (yy, '2010-04-01', '2012-03-05') = 2 และเวอร์ชันเดือน
André C. Andersen

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 แถว)

ในการสืบค้นในชีวิตจริงอาจมีลำดับการประทับเวลาบางส่วนที่จัดกลุ่มตามชั่วโมง / วัน / สัปดาห์ / ฯลฯ แทนการสร้างซีรีส์

นี้'count(distinct(date_trunc('month', ts)))'สามารถใช้สิทธิใน 'ซ้าย' ด้านข้างของเลือก:

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

ฉันใช้ create_series () ที่นี่เพื่อความกะทัดรัด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.