การเปรียบเทียบช่วงวันที่


117

ใน MySQL หากฉันมีรายการช่วงวันที่ (ช่วงเริ่มต้นและช่วงสิ้นสุด) เช่น

10/06/1983 to 14/06/1983
15/07/1983 to 16/07/1983
18/07/1983 to 18/07/1983

และฉันต้องการตรวจสอบว่าช่วงวันที่อื่นมีช่วงใด ๆ ในรายการอยู่แล้วหรือไม่ฉันจะทำอย่างไร

เช่น

06/06/1983 to 18/06/1983 = IN LIST
10/06/1983 to 11/06/1983 = IN LIST
14/07/1983 to 14/07/1983 = NOT IN LIST

1
ความเป็นไปได้ที่ซ้ำกันของการกำหนดว่าสองช่วงวันที่ทับซ้อนกันหรือไม่
Salman A

คำตอบ:


440

นี่เป็นปัญหาคลาสสิกและง่ายกว่าถ้าคุณย้อนกลับตรรกะ

ผมขอยกตัวอย่าง

ฉันจะโพสต์ช่วงเวลาหนึ่งไว้ที่นี่และรูปแบบต่างๆของช่วงเวลาอื่น ๆ ที่ทับซ้อนกันในบางลักษณะ

           |-------------------|          compare to this one
               |---------|                contained within
           |----------|                   contained within, equal start
                   |-----------|          contained within, equal end
           |-------------------|          contained within, equal start+end
     |------------|                       not fully contained, overlaps start
                   |---------------|      not fully contained, overlaps end
     |-------------------------|          overlaps start, bigger
           |-----------------------|      overlaps end, bigger
     |------------------------------|     overlaps entire period

ในทางกลับกันให้ฉันโพสต์สิ่งที่ไม่ทับซ้อนกัน:

           |-------------------|          compare to this one
     |---|                                ends before
                                 |---|    starts after

ดังนั้นหากคุณลดการเปรียบเทียบเป็น:

starts after end
ends before start

จากนั้นคุณจะพบสิ่งที่ไม่ทับซ้อนกันจากนั้นคุณจะพบช่วงเวลาที่ไม่ตรงกันทั้งหมด

สำหรับตัวอย่างสุดท้ายไม่อยู่ในรายการคุณจะเห็นว่าตรงกับกฎทั้งสองนี้

คุณจะต้องตัดสินใจว่าช่วงเวลาต่อไปนี้อยู่ในหรือนอกช่วงของคุณ:

           |-------------|
   |-------|                       equal end with start of comparison period
                         |-----|   equal start with end of comparison period

หากตารางของคุณมีคอลัมน์ที่เรียกว่า range_end และ range_start ต่อไปนี้เป็น SQL ง่ายๆในการดึงข้อมูลแถวที่ตรงกันทั้งหมด:

SELECT *
FROM periods
WHERE NOT (range_start > @check_period_end
           OR range_end < @check_period_start)

สังเกตสิ่งที่ไม่อยู่ในนั้น เนื่องจากทั้งสองกฎง่ายๆพบว่าทั้งหมดไม่ตรงแถวที่เรียบง่ายไม่ได้จะกลับมันจะพูดว่า: ถ้ามันไม่ได้เป็นหนึ่งในแถวที่ไม่ตรงกันก็จะต้องมีคนหนึ่งที่ตรงกัน

ใช้ตรรกะการย้อนกลับอย่างง่ายที่นี่เพื่อกำจัด NOT และคุณจะได้รับ:

SELECT *
FROM periods
WHERE range_start <= @check_period_end
      AND range_end >= @check_period_start

45
เราต้องการธง "มีไดอะแกรม ACII" สำหรับคำตอบซึ่งช่วยให้คุณโหวตได้มากกว่าหนึ่งครั้ง
จอนนี่บูคานัน

29
อาจเป็นหนึ่งใน 5 คำตอบที่ดีที่สุดที่ฉันเคยเห็นใน SO คำอธิบายที่ดีเกี่ยวกับปัญหาคำแนะนำที่ดีของการแก้ปัญหาและ ... รูปภาพ!
davidavr

10
ถ้าฉันสามารถโหวตได้มากกว่าหนึ่งครั้งฉันจะทำ คำอธิบายที่ยอดเยี่ยมชัดเจนและกระชับเกี่ยวกับปัญหาทั่วไปที่เกิดขึ้นวิธีแก้ปัญหาที่ฉันไม่ค่อยได้เห็นอธิบายได้ดี!
ConroyP

2
ตอบโจทย์มาก! สิ่งเดียวที่ฉันจะเพิ่ม - อ้างอิงถึงการตัดสินใจว่าจะรวมจุดสิ้นสุดหรือไม่ - ทุกอย่างจะทำงานได้ดีขึ้นหากคุณใช้ช่วงเวลาปิดที่ด้านหนึ่งและช่วงเวลาเปิดที่อีกด้านหนึ่ง เช่นจุดเริ่มต้นของช่วงรวมอยู่ในจุดและจุดสิ้นสุดของช่วงไม่มี โดยเฉพาะอย่างยิ่งเมื่อคุณต้องจัดการกับวันที่และเวลาของความละเอียดที่หลากหลายทุกอย่างจะง่ายขึ้น
คราส

1
คำตอบที่ดี. นี้จะอธิบายว่าอัลเลนช่วงพีชคณิต ฉันมีคำตอบที่คล้ายกันและได้เข้าสู่การต่อสู้ที่ดุเดือดเกี่ยวกับการเปรียบเทียบที่แตกต่างกันกับผู้บรรยายคนหนึ่ง
Jonathan Leffler

8

ยกตัวอย่างช่วง 06/06/1983 ถึง 18/06/1983 และสมมติว่าคุณมีคอลัมน์ที่เรียกว่าstartและendสำหรับ range ของคุณคุณสามารถใช้ประโยคแบบนี้ได้

where ('1983-06-06' <= end) and ('1983-06-18' >= start)

เช่นตรวจสอบจุดเริ่มต้นของช่วงการทดสอบของคุณอยู่ก่อนสิ้นสุดช่วงฐานข้อมูลและจุดสิ้นสุดของช่วงการทดสอบของคุณอยู่หลังหรือในช่วงเริ่มต้นของช่วงฐานข้อมูล


4

หาก RDBMS ของคุณรองรับฟังก์ชัน OVERLAP () สิ่งนี้จะกลายเป็นเรื่องเล็กน้อย - ไม่จำเป็นต้องใช้โซลูชันพื้นบ้าน (ใน Oracle ใช้งานได้อย่างเป็นทางการ แต่ไม่มีเอกสารประกอบ)


1
โซลูชันมหากาพย์ ใช้งานได้ดี นี่คือไวยากรณ์สำหรับ 2 ช่วงวันที่ (s1, e1) และ (s2, e2) ใน Oracle: เลือก 1 จากคู่โดยที่ (s1, e1) ทับซ้อนกัน (s2, e2);
ihebiheb

0

ในผลลัพธ์ที่คุณคาดหวังคุณพูด

06/06/1983 ถึง 18/06/1983 = IN LIST

อย่างไรก็ตามช่วงเวลานี้ไม่มีหรืออยู่ในช่วงเวลาใด ๆ ในตารางของคุณ (ไม่ใช่รายการ!) อย่างไรก็ตามมันคาบเกี่ยวกับช่วงเวลา 10/06/1983 ถึง 14/06/1983

คุณอาจพบว่าหนังสือ Snodgrass ( http://www.cs.arizona.edu/people/rts/tdbbook.pdf ) มีประโยชน์: เป็น mysql ล่วงหน้า แต่แนวคิดเรื่องเวลาไม่ได้เปลี่ยนไป ;-)


0

ฉันสร้างฟังก์ชันเพื่อจัดการกับปัญหานี้ใน MySQL เพียงแค่แปลงวันที่เป็นวินาทีก่อนใช้งาน

DELIMITER ;;

CREATE FUNCTION overlap_interval(x INT,y INT,a INT,b INT)
RETURNS INTEGER DETERMINISTIC
BEGIN
DECLARE
    overlap_amount INTEGER;
    IF (((x <= a) AND (a < y)) OR ((x < b) AND (b <= y)) OR (a < x AND y < b)) THEN
        IF (x < a) THEN
            IF (y < b) THEN
                SET overlap_amount = y - a;
            ELSE
                SET overlap_amount = b - a;
            END IF;
        ELSE
            IF (y < b) THEN
                SET overlap_amount = y - x;
            ELSE
                SET overlap_amount = b - x;
            END IF;
        END IF;
    ELSE
        SET overlap_amount = 0;
    END IF;
    RETURN overlap_amount;
END ;;

DELIMITER ;

0

ดูตัวอย่างต่อไปนี้ จะเป็นประโยชน์สำหรับคุณ

    SELECT  DISTINCT RelatedTo,CAST(NotificationContent as nvarchar(max)) as NotificationContent,
                ID,
                Url,
                NotificationPrefix,
                NotificationDate
                FROM NotificationMaster as nfm
                inner join NotificationSettingsSubscriptionLog as nfl on nfm.NotificationDate between nfl.LastSubscribedDate and isnull(nfl.LastUnSubscribedDate,GETDATE())
  where ID not in(SELECT NotificationID from removednotificationsmaster where Userid=@userid) and  nfl.UserId = @userid and nfl.RelatedSettingColumn = RelatedTo

0

ลองใช้ MS SQL


WITH date_range (calc_date) AS (
SELECT DATEADD(DAY, DATEDIFF(DAY, 0, [ending date]) - DATEDIFF(DAY, [start date], [ending date]), 0)
UNION ALL SELECT DATEADD(DAY, 1, calc_date)
FROM date_range 
WHERE DATEADD(DAY, 1, calc_date) <= [ending date])
SELECT  P.[fieldstartdate], P.[fieldenddate]
FROM date_range R JOIN [yourBaseTable] P on Convert(date, R.calc_date) BETWEEN convert(date, P.[fieldstartdate]) and convert(date, P.[fieldenddate]) 
GROUP BY  P.[fieldstartdate],  P.[fieldenddate];


0

อีกวิธีหนึ่งโดยใช้คำสั่ง BETWEEN sql

รวมระยะเวลา:

SELECT *
FROM periods
WHERE @check_period_start BETWEEN range_start AND range_end
  AND @check_period_end BETWEEN range_start AND range_end

ไม่รวมช่วงเวลา:

SELECT *
FROM periods
WHERE (@check_period_start NOT BETWEEN range_start AND range_end
  OR @check_period_end NOT BETWEEN range_start AND range_end)

-2
SELECT * 
FROM tabla a 
WHERE ( @Fini <= a.dFechaFin AND @Ffin >= a.dFechaIni )
  AND ( (@Fini >= a.dFechaIni AND @Ffin <= a.dFechaFin) OR (@Fini >= a.dFechaIni AND @Ffin >= a.dFechaFin) OR (a.dFechaIni>=@Fini AND a.dFechaFin <=@Ffin) OR
(a.dFechaIni>=@Fini AND a.dFechaFin >=@Ffin) )

ยินดีต้อนรับสู่ Stack Overflow! ขอบคุณสำหรับข้อมูลโค้ดนี้ซึ่งอาจให้ความช่วยเหลือได้ทันที คำอธิบายที่เหมาะสมจะช่วยเพิ่มคุณค่าทางการศึกษาได้อย่างมากโดยแสดงให้เห็นว่าเหตุใดจึงเป็นวิธีแก้ปัญหาที่ดีและจะทำให้มีประโยชน์มากขึ้นสำหรับผู้อ่านในอนาคตที่มีคำถามที่คล้ายกัน แต่ไม่เหมือนกัน โปรดแก้ไขคำตอบของคุณเพื่อเพิ่มคำอธิบายและระบุข้อ จำกัด และสมมติฐานที่ใช้
Toby Speight
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.