Kiểm tra sự chồng chéo của các phạm vi ngày trong MySQL


81

Bảng này được sử dụng để lưu trữ các phiên (sự kiện):

CREATE TABLE session (
  id int(11) NOT NULL AUTO_INCREMENT
, start_date date
, end_date date
);

INSERT INTO session
  (start_date, end_date)
VALUES
  ("2010-01-01", "2010-01-10")
, ("2010-01-20", "2010-01-30")
, ("2010-02-01", "2010-02-15")
;

Chúng tôi không muốn có xung đột giữa các phạm vi.
Giả sử chúng ta cần chèn một phiên mới từ 2010-01-05 đến 2010-01-25 .
Chúng tôi muốn biết (các) phiên xung đột.

Đây là truy vấn của tôi:

SELECT *
FROM session
WHERE "2010-01-05" BETWEEN start_date AND end_date
   OR "2010-01-25" BETWEEN start_date AND end_date
   OR "2010-01-05" >= start_date AND "2010-01-25" <= end_date
;

Đây là kết quả:

+----+------------+------------+
| id | start_date | end_date   |
+----+------------+------------+
|  1 | 2010-01-01 | 2010-01-10 |
|  2 | 2010-01-20 | 2010-01-30 |
+----+------------+------------+

Có cách nào tốt hơn để đạt được điều đó không?


vĩ cầm


1
Điều kiện thứ ba của bạn là sai. Nó được cho là như vậy "2010-01-05" <= start_date AND "2010-01-25" >= end_date. Xem stackoverflow.com/a/28802972/632951 để hình dung. Điều kiện thứ ba hiện tại của bạn sẽ không bao giờ đánh giá được, bởi vì điều kiện đầu tiên (và thứ hai) đã bao hàm nó.
Pacerier

Câu trả lời:


155

Tôi đã có một truy vấn như vậy với một ứng dụng lịch mà tôi đã từng viết. Tôi nghĩ rằng tôi đã sử dụng một cái gì đó như thế này:

... WHERE new_start < existing_end
      AND new_end   > existing_start;

CẬP NHẬT Điều này chắc chắn sẽ hoạt động ((ns, ne, es, ee) = (new_start, new_end, current_start, current_end)):

  1. ns - ne - es - ee: không trùng lặp và không khớp (vì ne <es)
  2. ns - es - ne - ee: chồng chéo và khớp
  3. es - ns - ee - ne: chồng chéo và khớp
  4. es - ee - ns - ne: không trùng lặp và không khớp (vì ns> ee)
  5. es - ns - ne - ee: chồng chéo và khớp
  6. ns - es - ee - ne: chồng chéo và khớp

Đây là một trò đùa


7
Hoạt động tốt !, nhưng tôi nghĩ @Pierre de LESPINAY đang tìm kiếm các phạm vi bao gồm trong truy vấn của anh ấy: WHERE new_start <= current_end VÀ new_end> = current_start;
Osvaldo Mercado

14
@OsvaldoM. Nếu anh ấy thực sự là, ông đã có thể phàn nàn về 2 năm trước ...
soulmerge

Tôi một sự kiện kết thúc ngày hôm nay và một sự kiện khác bắt đầu ngày hôm nay, chúng có trùng nhau không? Theo tôi, chúng sẽ chồng chéo lên nhau. Nhưng truy vấn SQL sẽ không nói điều đó: sqlfiddle.com/#!2/0a6fd/1/0
AL

2
@soulmerge, Trên thực tế, anh ấy sẽ chỉ thêm =mã vào mã thực của mình thay vì bận tâm phàn nàn về nó.
Pacerier

32
SELECT * FROM tbl WHERE
existing_start BETWEEN $newStart AND $newEnd OR 
existing_end BETWEEN $newStart AND $newEnd OR
$newStart BETWEEN existing_start AND existing_end

if (!empty($result))
throw new Exception('We have overlapping')

3 dòng mệnh đề sql này bao gồm 4 trường hợp chồng chéo bắt buộc.


6
Ngay cả khi OP dường như không tìm kiếm định nghĩa trùng lặp này, câu trả lời này là giải pháp tốt nhất cho vấn đề được mô tả bởi tên câu hỏi. Tôi đã tìm kiếm sự chồng chéo này, đó là sự chồng chéo thực sự.
Cec

1
Tôi không chắc ý của bạn về "sự trùng lặp thực sự" nhưng câu trả lời hàng đầu của @soulmerge tương đương với câu trả lời này của Lamy. Tôi đã có thể sử dụng Bộ giải định lý Z3 để chứng minh tính tương đương: Grantjenks.com/projects/equivalent-inequalities
GrantJ

16

Câu trả lời của Lamy là tốt, nhưng bạn có thể tối ưu hóa nó nhiều hơn một chút.

SELECT * FROM tbl WHERE
existing_start BETWEEN $newSTart AND $newEnd OR
$newStart BETWEEN existing_start AND existing_end

Điều này sẽ bắt tất cả bốn trường hợp trong đó các phạm vi trùng lặp và loại trừ hai trường hợp chúng không trùng lặp.


Có giải pháp nào khác ngoài giải pháp này và hai giải pháp khác ở trên không?
Pacerier

4

Tôi đã phải đối mặt với vấn đề tương tự. Vấn đề của tôi là dừng đặt phòng giữa một loạt các ngày bị chặn. Ví dụ đặt phòng bị chặn đối với một tài sản từ ngày 2 tháng 5 đến ngày 7 tháng 5. Tôi cần tìm bất kỳ loại ngày trùng lặp nào để phát hiện và ngăn chặn việc đặt phòng. Giải pháp của tôi tương tự như LordJavac.

SELECT * FROM ib_master_blocked_dates WHERE venue_id=$venue_id AND 
(
    (mbd_from_date BETWEEN '$from_date' AND '$to_date') 
    OR
    (mbd_to_date BETWEEN  '$from_date' AND '$to_date')
    OR
    ('$from_date' BETWEEN mbd_from_date AND mbd_to_date)
    OR      
    ('$to_date' BETWEEN mbd_from_date AND mbd_to_date)      
)
*mbd=master_blocked_dates

Hãy cho tôi biết nếu nó không hoạt động.


2

Cho hai khoảng như (s1, e1) và (s2, e2) với s1 <e1 và s2 <e2
Bạn có thể tính chồng chéo như sau:

SELECT 
     s1, e1, s2, e2,
     ABS(e1-s1) as len1,
     ABS(e2-s2) as len2,
     GREATEST(LEAST(e1, e2) - GREATEST(s1, s2), 0)>0 as overlaps,
     GREATEST(LEAST(e1, e2) - GREATEST(s1, s2), 0) as overlap_length
FROM test_intervals 

Cũng sẽ hoạt động nếu một khoảng nằm trong khoảng thời gian kia.


0

Gần đây, tôi đang vật lộn với cùng một vấn đề và đã kết thúc với một bước đơn giản này (Đây có thể không phải là một cách tiếp cận tốt hoặc tốn bộ nhớ) -

SELECT * FROM duty_register WHERE employee = '2' AND (
(
duty_start_date BETWEEN {$start_date} AND {$end_date}
OR
duty_end_date BETWEEN {$start_date} AND {$end_date}
)
OR
(
{$start_date} BETWEEN duty_start_date AND duty_end_date
OR
{$end_date} BETWEEN duty_start_date AND duty_end_date)
);

Điều này đã giúp tôi tìm thấy các mục nhập có phạm vi ngày trùng lặp.

Hy vọng điều này sẽ giúp ai đó.


0

Bạn có thể bao gồm tất cả các trường hợp trùng lặp ngày tháng ngay cả khi cập nhật trong cơ sở dữ liệu có thể có giá trị rỗng như sau:

SELECT * FROM `tableName` t
WHERE t.`startDate` <= $toDate
AND (t.`endDate` IS NULL OR t.`endDate` >= $startDate);

Điều này sẽ trả về tất cả các bản ghi trùng lặp với ngày bắt đầu / ngày kết thúc mới.


0

Câu trả lời của Mackraken ở trên tốt hơn từ góc độ hiệu suất vì nó không yêu cầu một số OR để đánh giá xem hai ngày có trùng nhau hay không. Giải pháp tốt!

Tuy nhiên, tôi thấy rằng trong MySQL, bạn cần sử dụng DATEDIFF thay vì toán tử trừ -

SELECT o.orderStart, o.orderEnd, s.startDate, s.endDate
, GREATEST(LEAST(orderEnd, endDate) - GREATEST(orderStart, startDate), 0)>0 as overlaps
, DATEDIFF(LEAST(orderEnd, endDate), GREATEST(orderStart, startDate)) as overlap_length
FROM orders o
JOIN dates s USING (customerId)
WHERE 1
AND DATEDIFF(LEAST(orderEnd, endDate),GREATEST(orderStart, startDate)) > 0;
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.