Wenn ich in MySQL eine Liste von Datumsbereichen habe (Bereichsbeginn und -ende). z.B.
10/06/1983 to 14/06/1983
15/07/1983 to 16/07/1983
18/07/1983 to 18/07/1983
Und ich möchte überprüfen, ob ein anderer Datumsbereich einen der Bereiche enthält, die bereits in der Liste enthalten sind. Wie würde ich das tun?
z.B.
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
Dies ist ein klassisches Problem, und es ist tatsächlich einfacher, wenn Sie die Logik umkehren.
Lassen Sie mich Ihnen ein Beispiel geben.
Ich werde hier einen Zeitraum veröffentlichen und all die verschiedenen Variationen anderer Zeiträume, die sich auf irgendeine Weise überschneiden.
|-------------------| 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
lassen Sie mich auf der anderen Seite all jene posten, die sich nicht überschneiden:
|-------------------| compare to this one
|---| ends before
|---| starts after
Reduzieren Sie den Vergleich also einfach auf:
starts after end
ends before start
dann finden Sie alle diejenigen, die sich nicht überlappen, und dann finden Sie alle nicht übereinstimmenden Zeiträume.
In Ihrem letzten NOT IN LIST-Beispiel können Sie sehen, dass es diesen beiden Regeln entspricht.
Sie müssen entscheiden, ob die folgenden Zeiträume IN oder AUSSERHALB Ihrer Bereiche liegen:
|-------------|
|-------| equal end with start of comparison period
|-----| equal start with end of comparison period
Wenn Ihre Tabelle Spalten mit den Namen range_end und range_start enthält, finden Sie hier einige einfache SQL-Anweisungen zum Abrufen aller übereinstimmenden Zeilen:
SELECT *
FROM periods
WHERE NOT (range_start > @check_period_end
OR range_end < @check_period_start)
Beachten Sie das NICHT darin. Da die zwei einfachen Regeln alle nicht übereinstimmenden Zeilen finden, kehrt ein einfaches NOT diese um und sagt: wenn es nicht eine der nicht übereinstimmenden Zeilen ist, muss es eine von sein die passenden.
Wenn Sie hier eine einfache Umkehrlogik anwenden, um das NOT zu beseitigen, erhalten Sie:
SELECT *
FROM periods
WHERE range_start <= @check_period_end
AND range_end >= @check_period_start
Nehmen Sie den Beispielbereich 06/06/1983 bis 18/06/1983 und setzen Sie voraus, dass Sie Spalten mit den Namen start und end für Ihre Bereiche haben, und verwenden Sie eine Klausel wie diese
where ('1983-06-06' <= end) and ('1983-06-18' >= start)
d.h. überprüfen Sie, ob der Beginn Ihres Testbereichs vor dem Ende des Datenbankbereichs liegt und ob das Ende Ihres Testbereichs nach oder am Anfang des Datenbankbereichs liegt.
Wenn Ihr RDBMS die OVERLAP () -Funktion unterstützt, ist dies trivial - es sind keine eigenen Lösungen erforderlich. (In Oracle funktioniert es anscheinend, ist aber nicht dokumentiert).
Ich habe eine Funktion erstellt, um dieses Problem in MySQL zu lösen. Konvertieren Sie einfach die Daten vor der Verwendung in Sekunden.
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 ;
Schauen Sie sich das folgende Beispiel an. Es wird für Sie hilfreich sein.
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 [email protected]) and nfl.UserId = @userid and nfl.RelatedSettingColumn = RelatedTo
Eine andere Methode unter Verwendung der SQL-Anweisung BETWEEN
Zeiträume enthalten:
SELECT *
FROM periods
WHERE @check_period_start BETWEEN range_start AND range_end
AND @check_period_end BETWEEN range_start AND range_end
Ausgeschlossene Fristen:
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)
Versuchen Sie dies auf 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];
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;
In Ihren erwarteten Ergebnissen sagen Sie
06/06/1983 bis 18/06/1983 = IN LIST
Diese Periode enthält jedoch keine der Perioden in Ihrer Tabelle (nicht in der Liste!). Es überlappt jedoch den Zeitraum 10/06/1983 bis 14/06/1983.
Möglicherweise finden Sie das Snodgrass-Buch ( http://www.cs.arizona.edu/people/rts/tdbbook.pdf ) nützlich: Es datiert vor MySQL, aber das Konzept der Zeit hat sich nicht geändert ;-)