Datumsfelder in MySQL, finden alle Zeilen, die nicht überlappen und nur die Differenz der Rückkehr
Frage
Also das war eine meiner ersten Fragen hier, aber ich habe eine leichte Variation:
So habe ich zwei Menschen, deren Zeitpläne sind in einer Datenbank. Die Zeitpläne einfach notieren Sie die Startzeit, Endzeit, und Beschreibung der verschiedenen Veranstaltungen / Termine für Benutzer.
persona will Termine mit personB handeln. Ich möchte eine MySQL-Abfrage, die alle von ihnen mal zurück, dass personB und Persona tauschen können.
Ursprünglich waren die Parameter der Abfrage waren alle Termine von personB zu werfen, wo es mit persona und personB Ernennung war Überlappung hatte genau die gleiche Länge wie die Ernennung sein Persona Swap will. Ich habe einige gute Ratschläge auf Zeit Arithmetik / Geometrie, die mir die Ergebnisse erhalten, half ich brauchte.
Jetzt möchte ich den Parameter 1-zu-1 ändern, damit die Termine müssen nicht gleich lang sein. Also, wenn persona will seinen Montagmorgen Termin (10.00 bis 11.30 Uhr) tauschen, wird die Abfrage:
- Ausschließen jeder personB die Termine, die während einer Persona Termine sind
- Fügen Sie alle Termine personB ist, die außerhalb von Persona Termine sind
- Fügen Sie die Teile von Terminen personB ist, die während persona frei, sondern nur den freien Teil zeigen.
Also, wenn Persona will den oben Termin tauschen (wieder von Montag 10.00 Uhr bis 11.30 Uhr) und Persona hat einen Termin am Dienstag von 13.00 bis 15.00 Uhr und personB hat einen Termin am Dienstag, 12.00 bis 04.00 Uhr, zurückkehren würde die Abfrage:
Possible_Swaps
==============
userID | Start | End | Description
PersonB | Tuesday, 12:00 PM | Tuesday 1:00 PM | Cooking
PersonB | Tuesday, 4:00 PM | Tuesday 5:00 PM | Cooking
Zusätzlich zu allen anderen Möglichkeiten. Ist das zu viel von der Datenbank zu erwarten? Wenn ja, irgendwelche Vorschläge, wie man zumindest diese Verschiebungen erhalten, die sich überlappen, aber die Zeiten haben über beiden Seiten hängen, so dass ein PHP-Skript mit ihnen umgehen könnte?
pro searlea Wunsch, hier ein bisschen mehr Kontext:
Ich sage immer wieder Verabredungen aber ich denke, ich meine „Jobs“, wie in „Arbeitsschichten“ wirklich. Persona und personB Arbeit im selben Büro. In vcalendar werden Arbeitsschichten in der Regel bezeichnet als „Events“, aber gelegentlich „Termine“ und ich ging mit dem letzteren, da sie weniger klingt wie die beiden Personen auf ein faires werden.
So Persona hat eine Abwasch Verschiebung am Montag von 10.00 bis 11.30 Uhr. PersonB ist 05.00 ab 12:00 am Dienstag, das Kochen. Persona wirklich will, um seinen Bruder sehen, bevor sie die Stadt am Montag verlassen. Er würde lieber alle Montagmorgen bekommen, aber er würde sich zufrieden geben eine Stunde Verschiebung aussteigen.
Also in meinem alten Modell (erzogen in meine erste Frage hier), ich war auf der Suche für alle Schichten, wo es keine Überlappung war und wo die Verschiebungen waren in der Zeit gleich. Aber das hat zwei Probleme:
-
Wenn ich jemand brauche meine 2-Stunden-Schicht am Dienstag und ich für 4 Stunden am Donnerstag, Arbeit zu decken, und Joe arbeitet für 8 Stunden am Donnerstag, kann ich zwei seine Stunden tauschen und er konnte ein wenig früh verlassen und ich kann ein wenig später bleiben.
-
Wenn ich eine Zwei-Stunden-Schicht habe, aber ich würde gerne eine Stunde davon handelt nur es zum Flughafen auf Zeit zu machen, möchte ich, wenn diese kennen und so kommt in einer Stunde früher als ich später in der Woche, damit ich, dass ein Teil seiner Schicht erfolgen kann.
Lange Rede kurzer Sinn (zu spät), ich will, was offenbar als bekannt < strong> relatives Komplement von Persona Verschiebungen zu personB (im Grunde beliebigen Zeiten, dass personB arbeitet und Persona ist nicht, unabhängig davon, ob die Verschiebungen an einem anderen Punkt überlappen.)
Idealerweise würde ich eine Reihe von Ergebnissen, die die Bits enthielten, dass personB arbeitet und Persona war nicht (die zwei 1-Stunden-Schichten wie oben erwähnt), wie auch die gesamte Schicht (mit einem speziellen Tag setzen, um anzuzeigen, ist nicht verfügbar als Ganzes), so dass Persona sehen würde, dass er bedeckte part eine Verschiebung und nicht verwirren lassen und denkt, dass personB nur zufällig zu arbeiten, zwei Ein-Stunden-Schichten.
Das ist alles Start ein wenig kompliziert zu klingen. Grundsätzlich möchte ich personB die Verschiebungen sein blau, Persona Verschiebungen gelb sein, und ich möchte, dass die Datenbank alle Teile zurückzugeben, die nicht grün sind.
Lösung
SELECT *
FROM schedule AS s1
WHERE
s1.user = 'Ondra'
AND
NOT EXISTS (
SELECT * FROM schedule AS s2
WHERE
s2.user = 'Zizka'
AND (
s2.start BETWEEN s1.start AND s1.end
OR
s2.end BETWEEN s1.start AND s1.end
OR
s1.start > s2.start AND s1.end < s2.end
)
)
Dies wählt Ondra der Ereignisse, die in eine Lücke in Zizka Tagebuch passen.
Edited: Ursprünglich war es eine schneiden, aber wenn man das relative Komplement will, das ist genug
.Andere Tipps
Es sei $shift_id
, die ID der Verschiebung, dass Ihre Benutzer tauschen will.
select swappable.shift_id, swappable.user_id, swappable.description,
FROM_UNIXTIME(swappable.shiftstart) as start,
FROM_UNIXTIME(swappable.shiftend) as end,
(swappable.shiftend - swappable.shiftstart) -
sum(coalesce(least(conflict.shiftend, swappable.shiftend) -
greatest(conflict.shiftstart, swappable.shiftstart), 0))
as swaptime,
group_concat(conflict.shift_id) as conflicts,
group_concat(concat(FROM_UNIXTIME(conflict.shiftstart), ' - ',
FROM_UNIXTIME(conflict.shiftend))) as conflict_times
from shifts as problem
join shifts as swappable on swappable.user_id != problem.user_id
left join shifts as conflict on conflict.user_id = problem.user_id
and conflict.shiftstart < swappable.shiftend
and conflict.shiftend > swappable.shiftstart
where problem.shift_id = 1
group by swappable.shift_id
having swaptime > 0;
Getestet mit:
CREATE TABLE `shifts` (
`shift_id` int(10) unsigned NOT NULL auto_increment,
`user_id` varchar(20) NOT NULL,
`shiftstart` int unsigned NOT NULL,
`shiftend` int unsigned NOT NULL,
`description` varchar(32) default NULL,
PRIMARY KEY (`shift_id`)
);
insert into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (1,'april', UNIX_TIMESTAMP('2009-04-04 10:00:00'),UNIX_TIMESTAMP('2009-04-04 12:00:00'),'Needs to be swapped');
insert into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (2,'bill', UNIX_TIMESTAMP('2009-04-04 10:30:00'),UNIX_TIMESTAMP('2009-04-04 11:30:00'),'Inside today');
insert into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (3,'casey', UNIX_TIMESTAMP('2009-04-04 12:00:00'),UNIX_TIMESTAMP('2009-04-04 14:00:00'),'Immediately after today');
insert into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (4,'casey', UNIX_TIMESTAMP('2009-04-04 08:00:00'),UNIX_TIMESTAMP('2009-04-04 10:00:00'),'Immediately before today');
insert into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (5,'david', UNIX_TIMESTAMP('2009-04-04 11:00:00'),UNIX_TIMESTAMP('2009-04-04 15:00:00'),'Partly after today');
insert into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (6,'april', UNIX_TIMESTAMP('2009-04-05 10:00:00'),UNIX_TIMESTAMP('2009-04-05 12:00:00'),'Tommorow');
insert into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (7,'bill', UNIX_TIMESTAMP('2009-04-05 09:00:00'),UNIX_TIMESTAMP('2009-04-05 11:00:00'),'Partly before tomorrow');
insert into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (8,'casey', UNIX_TIMESTAMP('2009-04-05 10:00:00'),UNIX_TIMESTAMP('2009-04-05 12:00:00'),'Equals tomorrow');
insert into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (9,'david', UNIX_TIMESTAMP('2009-04-05 10:30:00'),UNIX_TIMESTAMP('2009-04-05 11:30:00'),'Inside tomorrow');
insert into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (10,'april',UNIX_TIMESTAMP('2009-04-11 10:00:00'),UNIX_TIMESTAMP('2009-04-11 12:00:00'),'Next week');
insert into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (11,'april',UNIX_TIMESTAMP('2009-04-11 12:00:00'),UNIX_TIMESTAMP('2009-04-11 14:00:00'),'Second shift');
insert into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (12,'bill', UNIX_TIMESTAMP('2009-04-11 11:00:00'),UNIX_TIMESTAMP('2009-04-11 13:00:00'),'Overlaps two');
insert into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (13,'casey',UNIX_TIMESTAMP('2009-04-11 17:00:00'),UNIX_TIMESTAMP('2009-04-11 19:00:00'),'No conflict');
insert into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (14,'april',UNIX_TIMESTAMP('2009-05-04 10:00:00'),UNIX_TIMESTAMP('2009-05-04 12:00:00'),'Next month');
insert into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (15,'april',UNIX_TIMESTAMP('2009-05-04 13:00:00'),UNIX_TIMESTAMP('2009-05-04 15:00:00'),'After break');
insert into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (16,'bill', UNIX_TIMESTAMP('2009-05-04 11:00:00'),UNIX_TIMESTAMP('2009-05-04 14:00:00'),'Middle okay');
insert into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (17,'april',UNIX_TIMESTAMP('2010-04-04 10:00:00'),UNIX_TIMESTAMP('2010-04-04 11:00:00'),'Next year');
insert into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (18,'april',UNIX_TIMESTAMP('2010-04-04 11:30:00'),UNIX_TIMESTAMP('2010-04-04 12:00:00'),'After break');
insert into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (19,'april',UNIX_TIMESTAMP('2010-04-04 12:30:00'),UNIX_TIMESTAMP('2010-04-04 13:30:00'),'Third part');
insert into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (20,'bill', UNIX_TIMESTAMP('2010-04-04 10:30:00'),UNIX_TIMESTAMP('2010-04-04 13:00:00'),'Two parts okay');
Ergebnisse:
'shift_id', 'user_id', 'description', 'start', 'end', 'swaptime', 'conflicts', 'conflict_times'
'3', 'casey', 'Immediately after today', '2009-04-04 12:00:00', '2009-04-04 14:00:00', '7200', NULL, NULL
'4', 'casey', 'Immediately before today', '2009-04-04 08:00:00', '2009-04-04 10:00:00', '7200', NULL, NULL
'5', 'david', 'Partly after today', '2009-04-04 11:00:00', '2009-04-04 15:00:00', '10800', '1', '2009-04-04 10:00:00 - 2009-04-04 12:00:00'
'7', 'bill', 'Partly before tomorrow', '2009-04-05 09:00:00', '2009-04-05 11:00:00', '3600', '6', '2009-04-05 10:00:00 - 2009-04-05 12:00:00'
'13', 'casey', 'No conflict', '2009-04-11 17:00:00', '2009-04-11 19:00:00', '7200', NULL, NULL
'16', 'bill', 'Middle okay', '2009-05-04 11:00:00', '2009-05-04 14:00:00', '3600', '15,14', '2009-05-04 13:00:00 - 2009-05-04 15:00:00,2009-05-04 10:00:00 - 2009-05-04 12:00:00'
'20', 'bill', 'Two parts okay', '2010-04-04 10:30:00', '2010-04-04 13:00:00', '3600', '19,18,17', '2010-04-04 12:30:00 - 2010-04-04 13:30:00,2010-04-04 11:30:00 - 2010-04-04 12:00:00,2010-04-04 10:00:00 - 2010-04-04 11:00:00'
Dies zeigt alle Schichten, für die sich ein Teil (e) ausgetauscht werden können, einschließlich, wie
viel Gesamtzeit (in Sekunden) ist austauschbares. Die letzte Spalte, conflict_times
,
zeigt die Zeiten, für die der Austausch der Benutzer bereits an die Arbeit geplant ist.
Es sollte für die Anwendung einfach sein, die zur Verfügung stehenden Zeiten aus, dass zu extrahieren;
es ist möglich, aber sehr schwierig, in MySQL.
Task
Zurück alle Intervalle von zwei verschiedenen Benutzern außer Teilen, in denen sie sich überlappen.
Tabelle und Testdaten
CREATE TABLE IF NOT EXISTS `shifts` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(1) NOT NULL,
`start` datetime NOT NULL,
`end` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=12 ;
INSERT INTO `shifts` (`id`, `name`, `start`, `end`) VALUES
(1, 'a', '2000-01-01 01:00:00', '2000-01-01 03:00:00'),
(2, 'a', '2000-01-01 06:00:00', '2000-01-01 07:30:00'),
(3, 'b', '2000-01-01 02:00:00', '2000-01-01 04:00:00'),
(4, 'b', '2000-01-01 05:00:00', '2000-01-01 07:00:00'),
(5, 'a', '2000-01-01 08:00:00', '2000-01-01 11:00:00'),
(6, 'b', '2000-01-01 09:00:00', '2000-01-01 10:00:00'),
(7, 'a', '2000-01-01 12:00:00', '2000-01-01 13:00:00'),
(8, 'b', '2000-01-01 14:00:00', '2000-01-01 14:30:00'),
(9, 'a', '2000-01-01 16:00:00', '2000-01-01 18:00:00'),
(10, 'a', '2000-01-01 19:00:00', '2000-01-01 21:00:00'),
(11, 'b', '2000-01-01 17:00:00', '2000-01-01 20:00:00');
Ergebnisse Test
id name start end
1 a 2000-01-01 01:00:00 2000-01-01 02:00:00
3 b 2000-01-01 03:00:00 2000-01-01 04:00:00
4 b 2000-01-01 05:00:00 2000-01-01 06:00:00
2 a 2000-01-01 07:00:00 2000-01-01 07:30:00
5 a 2000-01-01 10:00:00 2000-01-01 11:00:00
7 a 2000-01-01 12:00:00 2000-01-01 13:00:00
8 b 2000-01-01 14:00:00 2000-01-01 14:30:00
9 a 2000-01-01 16:00:00 2000-01-01 17:00:00
11 b 2000-01-01 18:00:00 2000-01-01 19:00:00
10 a 2000-01-01 20:00:00 2000-01-01 21:00:00
Lösung
Ich verwenden Funktion von MySQL Benutzerdefinierte Variablen aufgerufen, das Ziel mit der folgenden Abfrage zu erreichen:
SET @inA=0, @inB=0, @lastAstart = 0, @lastBstart = 0, @lastAend = 0, @lastBend = 0;
SELECT id,name,start,end FROM (
SELECT
id,name,
IF(name='a',
IF(UNIX_TIMESTAMP(start) > @lastBend, start, FROM_UNIXTIME(@lastBend)),
IF(UNIX_TIMESTAMP(start) > @lastAend, start, FROM_UNIXTIME(@lastAend))
) as start,
IF(name='a',
IF(@inB,FROM_UNIXTIME(@lastBstart),end),
IF(@inA,FROM_UNIXTIME(@lastAstart),end)
) as end,
IF(name='a',
IF(@inB AND (@lastBstart < @lastAstart), 1, 0),
IF(@inA AND (@lastAstart < @lastBstart), 1, 0)
) as fullyEnclosed,
isStart,
IF(name='a',@inA:=isStart,0),
IF(name='b',@inB:=isStart,0),
IF(name='a',IF(isStart,@lastAstart:=t,@lastAend:=t),0),
IF(name='b',IF(isStart,@lastBstart:=t,@lastBend:=t),0)
FROM (
(SELECT *, UNIX_TIMESTAMP(start) as t, 1 as isStart FROM `shifts` WHERE name IN ('a', 'b'))
UNION ALL
(SELECT *, UNIX_TIMESTAMP(end) as t, 0 as isStart FROM `shifts` WHERE name IN ('a', 'b'))
ORDER BY t
) as sae
) AS final WHERE NOT isStart AND NOT fullyEnclosed;
Grundidee ist die Tabelle nach Zeit sortiert zweimal aufzulisten, so dass jeder Datensatz zweimal erscheinen. Sobald die Startzeit und dann für die Endzeit. Dann bin ich mit benutzerdefinierten Variablen Spur des Staates zu halten, während Aufzeichnungen durchlaufen und das Rück nur ‚Endzeit‘ Aufzeichnungen mit Startzeit und die Endzeit für überlappende Intervalle eingestellt.
Annahmen
Nur Annahme ist, dass kein Intervall von Person x überlappt mit einem anderen Intervall von derselben Person.
Verhalten
Einige wenige Fälle und ihre Ergebnisse:
< ( > )
< > ( )
( < ) ( > )
( ) < > ( )
< ( ) > // for this and similar cases only last part of interval is returned
< >
( < ) ( ) ( ) ( > ) // like so
( ) < > ( )
Caveats
Ich muss Zeitstempel Unix verwendet haben, da es mein MySQL-Server nicht Vergleich zwischen DATETIME- machen könnte in benutzerdefinierten Variablen und etwas anderes gehalten.
Pros & Contras
Es tut seinem Job bei einmaligen Durchgang ohne verbindet so sollte es O (N) Zeit in Anspruch nehmen. Es kann nicht alle Teile des Intervalls von Person A ausgeschnitten von einem geschlossenen Intervallen von Person B. abrufen Es nutzt MySQL spezifische Funktionalität.
Für die Referenz ein Code snipped, die ich vor kurzem verwendet. Es kann für überlappende Datum überprüfen Bereiche verwendet werden. Es ist in Ruby on Rails geschrieben, aber die Idee (die SQL-Anweisung) kann leicht in anderen Sprachen)
übersetzt werden class Absence
named_scope :overlaps, lambda { |start, ende| {
:conditions =>
[" absences.start_date BETWEEN :start AND :end " +
"OR absences.end_date BETWEEN :start AND :end " +
"OR :start BETWEEN absences.start_date AND absences.end_date " +
"OR :end BETWEEN absences.start_date AND absences.end_date ",
{:start => start, :end => ende } ]
}}
end
Wie üblich bei Scopes dieser Bereich kann mit anderen Bereichen in Kombination verwendet werden.
user = User.find(...)
today = Date.today
confirmed_absences = user.absences.confirmed.overlaps(today.beginning_of_month, today.end_of_month).count