Überprüfen Sie die Überlappung von Datumsbereichen in MySQL

81

In dieser Tabelle werden Sitzungen (Ereignisse) gespeichert:

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

Wir wollen keinen Konflikt zwischen Bereichen haben.
Angenommen, wir müssen eine neue Sitzung vom 05.01.2010 bis zum 25.01.2010 einfügen .
Wir möchten die widersprüchlichen Sitzungen kennen.

Hier ist meine Frage:

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
;

Hier ist das Ergebnis:

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

Gibt es einen besseren Weg, um das zu bekommen?


Geige

Pierre de LESPINAY
quelle
1
Ihre dritte Bedingung ist falsch. Es soll sein "2010-01-05" <= start_date AND "2010-01-25" >= end_date. Visualisierung finden Sie unter stackoverflow.com/a/28802972/632951 . Ihre aktuelle dritte Bedingung wird niemals ausgewertet, da die erste (und zweite) Bedingung sie bereits abdeckt.
Pacerier

Antworten:

155

Ich hatte eine solche Anfrage mit einer Kalenderanwendung, die ich einmal geschrieben habe. Ich glaube, ich habe so etwas benutzt:

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

UPDATE Dies sollte auf jeden Fall funktionieren ((ns, ne, es, ee) = (neuer_Start, neues_Ende, vorhandener_Start, vorhandenes_Ende)):

  1. ns - ne - es - ee: überlappt sich nicht und stimmt nicht überein (weil ne <es)
  2. ns - es - ne - ee: Überlappungen und Übereinstimmungen
  3. es - ns - ee - ne: Überlappungen und Übereinstimmungen
  4. es - ee - ns - ne: überlappt sich nicht und stimmt nicht überein (weil ns> ee)
  5. es - ns - ne - ee: Überlappungen und Übereinstimmungen
  6. ns - es - ee - ne: Überlappungen und Übereinstimmungen

Hier ist eine Geige

Soulmerge
quelle
7
Funktioniert großartig!, Aber ich denke, @Pierre de LESPINAY sucht in seiner Abfrage nach inklusiven Bereichen: WHERE new_start <= existierendes_ende UND neues_ende> = existierendes_start;
Osvaldo Mercado
14
@OsvaldoM. Wenn er es wirklich wäre, hätte er sich vor ungefähr 2 Jahren beschwert…
soulmerge
Wenn eine Veranstaltung heute endet und eine andere heute beginnt, überschneiden sie sich? Meiner Meinung nach werden sie sich überschneiden. Aber die SQL-Abfrage wird es nicht sagen: sqlfiddle.com/#!2/0a6fd/1/0
AL
2
@soulmerge, Eigentlich hätte er das nur =in seinen eigentlichen Code eingefügt, anstatt sich darüber zu beschweren.
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')

Diese 3 Zeilen mit SQL-Klauseln decken die 4 erforderlichen Überlappungsfälle ab.

Yasen
quelle
6
Auch wenn das OP anscheinend nicht nach dieser überlappenden Definition gesucht hat, ist diese Antwort die beste Lösung für das durch den Fragennamen beschriebene Problem. Ich habe nach dieser Überlappung gesucht, die die wahre Überlappung ist.
Cec
1
Ich bin mir nicht sicher, was Sie unter "echter Überlappung" verstehen, aber die Top-Antwort von @soulmerge entspricht dieser Antwort von Lamy. Ich konnte den Z3 Theorem Solver verwenden, um die Äquivalenz zu beweisen: grantjenks.com/projects/equivalent-inequalities
GrantJ
16

Lamys Antwort ist gut, aber Sie können sie ein wenig weiter optimieren.

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

Dadurch werden alle vier Szenarien erfasst, in denen sich die Bereiche überschneiden, und die beiden ausgeschlossen, in denen dies nicht der Fall ist.

Lord Javac
quelle
Gibt es außer dieser und den beiden anderen oben noch andere Lösungen?
Pacerier
4

Ich hatte mich dem ähnlichen Problem gestellt. Mein Problem war, die Buchung zwischen einer Reihe blockierter Daten zu beenden. Beispielsweise ist die Buchung für eine Unterkunft zwischen dem 2. und 7. Mai gesperrt. Ich musste ein überlappendes Datum finden, um die Buchung zu erkennen und zu stoppen. Meine Lösung ähnelt 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

Lassen Sie mich wissen, wenn es nicht funktioniert.

Niraj Kumar
quelle
2

Bei zwei Intervallen wie (s1, e1) und (s2, e2) mit s1 <e1 und s2 <e2 können
Sie Überlappungen wie folgt berechnen:

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 

Funktioniert auch, wenn ein Intervall innerhalb des anderen liegt.

Mackraken
quelle
0

Vor kurzem hatte ich mit dem gleichen Problem zu kämpfen und endete mit diesem einfachen Schritt (dies ist möglicherweise kein guter Ansatz oder speicherintensiv) -

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

Dies hat mir geholfen, Einträge mit überlappenden Datumsbereichen zu finden.

Hoffe das hilft jemandem.

Sahu V Kumar
quelle
0

Sie können alle datumsüberlappenden Fälle abdecken, auch wenn das aktuelle Datum in der Datenbank möglicherweise wie folgt null sein kann:

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

Dadurch werden ohnehin alle Datensätze zurückgegeben, die sich mit den neuen Start- / Enddaten überschneiden.

Hussein Akar
quelle
0

Die obige Antwort von Mackraken ist aus Sicht der Leistung besser, da nicht mehrere OPs erforderlich sind, um zu bewerten, ob sich zwei Daten überschneiden. Schöne Lösung!

Ich habe jedoch festgestellt, dass Sie in MySQL DATEDIFF anstelle des Minusoperators verwenden müssen -

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;
yg-dba
quelle