Datumsfelder in MySQL, finden alle Zeilen, die nicht überlappen und nur die Differenz der Rückkehr

StackOverflow https://stackoverflow.com/questions/1333340

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:

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

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

War es hilfreich?

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
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top