So sánh phạm vi ngày


116

Trong MySQL, Nếu tôi có danh sách phạm vi ngày (phạm vi bắt đầu và kết thúc phạm vi). ví dụ

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

Và tôi muốn kiểm tra xem một phạm vi ngày khác có chứa BẤT KỲ phạm vi nào đã có trong danh sách hay không, tôi sẽ làm điều đó như thế nào?

ví dụ

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
bản sao có thể có của Xác định xem hai phạm vi ngày
Salman A

Câu trả lời:


439

Đây là một bài toán cổ điển và nó thực sự dễ dàng hơn nếu bạn đảo ngược logic.

Tôi sẽ cho bạn một ví dụ.

Tôi sẽ đăng một khoảng thời gian ở đây và tất cả các biến thể khác nhau của các khoảng thời gian khác trùng lặp theo một cách nào đó.

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

mặt khác, hãy để tôi đăng tất cả những thứ không trùng lặp:

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

Vì vậy, nếu bạn đơn giản giảm so sánh thành:

starts after end
ends before start

sau đó bạn sẽ tìm thấy tất cả những khoảng thời gian không trùng nhau và sau đó bạn sẽ tìm thấy tất cả các khoảng thời gian không trùng khớp.

Đối với ví dụ NOT IN LIST cuối cùng của bạn, bạn có thể thấy rằng nó phù hợp với hai quy tắc đó.

Bạn sẽ cần phải quyết định thời kỳ sau ở TRONG hay NGOÀI phạm vi của bạn:

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

Nếu bảng của bạn có các cột được gọi là range_end và range_start, thì đây là một số SQL đơn giản để truy xuất tất cả các hàng phù hợp:

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

Lưu ý KHÔNG ở đó. Vì hai quy tắc đơn giản tìm thấy tất cả các hàng không phù hợp , một KHÔNG đơn giản sẽ đảo ngược nó để nói: nếu nó không phải là một trong những hàng không khớp, nó phải là một trong những hàng phù hợp .

Áp dụng logic đảo ngược đơn giản ở đây để loại bỏ KHÔNG và bạn sẽ kết thúc với:

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

45
Chúng tôi cần cờ "chứa sơ đồ ACII" cho các câu trả lời cho phép bạn tán thành chúng nhiều lần
Jonny Buchanan

29
Có lẽ là một trong 5 câu trả lời hay nhất mà tôi đã thấy trên SO. Lời giải thích tuyệt vời về vấn đề, hướng dẫn giải pháp hay và ... hình ảnh!
davidavr 27/09/08

10
Nếu tôi có thể bỏ phiếu cho điều này nhiều hơn một lần, tôi sẽ. Lời giải thích tuyệt vời, rõ ràng và ngắn gọn về một vấn đề phổ biến được đưa ra, một giải pháp mà tôi hiếm khi thấy được giải thích rõ ràng như vậy!
ConroyP 27/09/08

2
Câu trả lời chính xác! Điều duy nhất tôi muốn thêm vào - liên quan đến việc quyết định xem các điểm cuối có được bao gồm hay không - mọi thứ hoạt động rõ ràng hơn nếu bạn đi với khoảng đóng ở một bên và khoảng mở ở bên kia. Ví dụ: đầu của một phạm vi bao gồm trong điểm và cuối phạm vi thì không. Đặc biệt là khi bạn đang xử lý sự kết hợp ngày và giờ của các độ phân giải khác nhau, mọi thứ trở nên đơn giản hơn.
Nhật thực

1
Câu trả lời tốt. Đây cũng được mô tả là Đại số khoảng cách của Allen . Tôi có một câu trả lời tương tự và đã tham gia vào một cuộc chiến gay gắt về bao nhiêu so sánh khác nhau giữa một nhà bình luận.
Jonathan Leffler

8

Lấy ví dụ về phạm vi từ 06/06/1983 đến 18/06/1983 và giả sử bạn có các cột được gọi là bắt đầukết thúc cho phạm vi của mình, bạn có thể sử dụng mệnh đề như sau

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

tức là kiểm tra điểm bắt đầu của phạm vi thử nghiệm của bạn là trước khi kết thúc phạm vi cơ sở dữ liệu và kết thúc phạm vi thử nghiệm của bạn là sau hoặc trên đầu của phạm vi cơ sở dữ liệu.


4

Nếu RDBMS của bạn hỗ trợ hàm OVERLAP () thì điều này trở nên tầm thường - không cần các giải pháp cây nhà lá vườn. (Trong Oracle, nó thực sự hoạt động nhưng không có giấy tờ).


1
Giải pháp sử thi. Hoạt động tốt. Đây là cú pháp cho 2 phạm vi ngày (s1, e1) và (s2, e2) trong Oracle: chọn 1 từ kép trong đó (s1, e1) chồng lên (s2, e2);
ihebiheb

0

Trong kết quả mong đợi của bạn, bạn nói

06/06/1983 đến 18/06/1983 = TRONG DANH SÁCH

Tuy nhiên, khoảng thời gian này không chứa cũng như không được chứa bởi bất kỳ dấu chấm nào trong bảng của bạn (không phải danh sách!) Các dấu chấm. Tuy nhiên, nó trùng lặp với giai đoạn 10/06/1983 đến 14/06/1983.

Bạn có thể thấy cuốn sách Snodgrass ( http://www.cs.arizona.edu/people/rts/tdbbook.pdf ) hữu ích: nó có từ trước mysql nhưng khái niệm thời gian không thay đổi ;-)


0

Tôi đã tạo hàm để giải quyết vấn đề này trong MySQL. Chỉ cần chuyển đổi ngày thành giây trước khi sử dụng.

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

Hãy xem ví dụ sau. Nó sẽ hữu ích cho bạn.

    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

Hãy thử điều này trên 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
CREATE FUNCTION overlap_date(s DATE, e DATE, a DATE, b DATE)
RETURNS BOOLEAN DETERMINISTIC
RETURN s BETWEEN a AND b or e BETWEEN a and b or  a BETWEEN s and e;

0

Một phương pháp khác bằng cách sử dụng câu lệnh BETWEEN sql

Các khoảng thời gian bao gồm:

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

Các khoảng thời gian bị loại trừ:

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

Chào mừng bạn đến với Stack Overflow! Cảm ơn bạn vì đoạn mã này, đoạn mã này có thể cung cấp một số trợ giúp ngay lập tức. Một lời giải thích phù hợp sẽ cải thiện đáng kể giá trị giáo dục của nó bằng cách chỉ ra lý do tại sao đây là một giải pháp tốt cho vấn đề và sẽ làm cho nó hữu ích hơn cho những độc giả trong tương lai với những câu hỏi tương tự, nhưng không giống hệt nhau. Vui lòng chỉnh sửa câu trả lời của bạn để thêm giải thích và đưa ra dấu hiệu về những giới hạn và giả định nào được áp dụng.
Toby Speight
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.