Les champs de date dans MySQL, trouver toutes les lignes qui ne se chevauchent pas et qui reviennent seulement la différence

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

Question

Alors ce fut une de mes premières questions ici, mais j'ai une légère variation:

J'ai donc deux personnes dont les horaires sont dans une base de données. Les horaires enregistrent tout simplement l'heure de début, heure de fin, et la description des différents événements / rendez-vous pour les utilisateurs.

PERSONA veut faire du commerce avec des rendez-vous PersonB. Je veux une requête MySQL qui renverra toutes les fois que PersonB et PERSONA peuvent échanger.

A l'origine les paramètres de la requête devaient jeter les rendez-vous de PersonB où il y avait chevauchement avec la nomination de PERSONA et PersonB ont dû être exactement la même longueur que la nomination PERSONA veut échanger. Je suis arrivé quelques bons conseils sur l'arithmétique de temps / géométrie qui m'a aidé à obtenir les résultats que je avais besoin.

Maintenant, je veux changer le paramètre 1 à 1, de sorte que les nominations ne doivent pas être la même longueur. Donc, si PERSONA veut échanger sa nomination lundi matin (10:00 AM - 11:30 AM), la requête:

  • Exclure l'un des rendez-vous de PersonB qui sont au cours d'une des rendez-vous PERSONA
  • Inclure l'un des rendez-vous de PersonB qui sont en dehors des rendez-vous PERSONA
  • Inclure les parties des rendez-vous PersonB qui sont tout en PERSONA est libre, mais seulement montrer la partie libre.

Donc, si PERSONA veut échanger la nomination ci-dessus (encore une fois, lundi 10:00-11h30), et PERSONA a rendez-vous le mardi 13h00-15h00 et PersonB a rendez-vous mardi 12h00-16h00, la requête retournerait:

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

En plus de toutes les autres possibilités. Est-ce trop attendre de la base de données? Dans ce cas, des suggestions sur la façon d'obtenir au moins les quarts de travail qui se chevauchent, mais ont des temps suspendus au-dessus de chaque côté de sorte qu'un script PHP peut traiter avec eux?


par la demande de searlea, voici un peu plus de contexte:

Je répétais des rendez-vous, mais je pense que je voulais vraiment dire « emplois » comme dans « les quarts de travail ». travail et PERSONA PersonB dans le même bureau. En vcalendar, les quarts de travail sont généralement appelés « événements », mais à l'occasion « Rendez-vous » et je suis allé avec ce dernier que cela puisse paraître moins comme les deux personnes vont à un procès équitable.

PERSONA a un changement lave-vaisselle, le lundi 10 heures 00-11h30. PersonB est la cuisine le mardi 12h00-17h00. PERSONA veut vraiment voir son frère avant de quitter la ville lundi. Il préfère obtenir tous hors du lundi matin, mais il me contenterais pour obtenir une heure de déplacement hors route.

Donc, dans mon ancien modèle (élevé dans ma première question ici), je cherchais pour tout changement où il n'y avait pas de chevauchement et où les changements étaient égaux dans le temps. Mais cela a deux problèmes:

  1. Si je besoin de quelqu'un pour me couvrir deux heures quart de travail mardi et je travaille pendant 4 heures le jeudi, et Joe travaille pendant 8 heures le jeudi, je pourrais échanger deux de ses heures et il pouvait laisser un peu tôt et je peux rester un peu plus tard.

  2. Si j'ai un quart de travail de deux heures, mais j'échangerais volontiers une heure de juste pour se rendre à l'aéroport à temps, je veux savoir si tel ou tel vient une heure plus tôt que moi plus tard dans la semaine pour que je puisse prendre cette partie de son quart de travail.

Longue histoire courte (trop tard), je veux ce qui est apparemment connu comme le < strong> complément relatif des changements de PERSONA à PersonB (essentiellement des fois supérieur à celui PersonB travaille et n'est pas PERSONA, que les changements se chevauchent à un autre point.)

Idéalement, je voudrais obtenir un ensemble de résultats qui comprenait les bits qui PersonB travaillait et PERSONA n'a pas été (les deux quarts de travail 1 heure mentionnée ci-dessus), ainsi que le quart de travail (avec une étiquette spéciale pour indiquer qu'il est pas disponible dans son ensemble), de sorte que PERSONA verrait qu'il couvrait part d'un changement et non confus et pensent que PersonB vient de se passer à travailler deux heures un quarts de travail.

Ceci est tout commence à parler un peu compliqué. Fondamentalement, je veux les quarts de PersonB d'être en bleu, les quarts de PERSONA d'être jaune, et je veux la base de données pour retourner toutes les pièces qui ne sont pas vert.

Était-ce utile?

La solution

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

Ceci permet de sélectionner les événements de Ondra qui peuvent entrer dans un espace dans le journal de Zizka.

Modifié:. A l'origine, il était Intersection, mais si vous voulez que le complément relatif, cela suffit

Autres conseils

Que $shift_id soit l'identifiant du changement que votre utilisateur souhaite échanger.

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;

Testé avec:

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

Résultats:

'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'

Cela montre tous les quarts de travail pour lequel peut être échangé une partie (s), y compris la façon beaucoup de temps total (en secondes) est swappable. La dernière colonne, conflict_times, montre les temps pour lesquels l'utilisateur swapping est déjà programmée pour fonctionner. Il devrait être facile pour l'application pour extraire les temps disponibles à partir de cela; il est possible, mais très difficile, en MySQL.

Tâche

Retour tous les intervalles de deux utilisateurs différents, sauf les parties où ils se chevauchent.

Tableau et données test

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

Les résultats des tests

        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

Solution

je fonction de MySQL appelé variables définies par l'utilisateur pour atteindre l'objectif avec la requête suivante:

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;

L'idée de base est de lister la table deux fois triés par temps pour que tous les enregistrements apparaissent deux fois. Une fois le temps de démarrage et pour l'heure de fin. Ensuite, je suis en utilisant des variables définies par l'utilisateur de garder une trace de l'état en parcourant les enregistrements et retourner uniquement les enregistrements « heure de fin » avec le temps de début et de fin ajusté pour tenir compte des intervalles qui se chevauchent.

Hypothèses

Seule hypothèse est que aucun intervalle de personne x ne se chevauchent avec un autre intervalle de la même personne.

Comportement

Peu de cas, et leurs résultats:

<  (   >   )
<  >   (   )

( < )  ( > )
( ) <  > ( )

<  (   )   >    // for this and similar cases only last part of interval is returned
       <   >

(   <  )   (   )  (  )  (   >   )  // like so
(   )                <  >   (   )

Avertissements

Je dois avoir utilisé timestamp unix puisqu'il mon serveur MySQL ne pouvait pas faire la comparaison entre DATETIME conservée dans la variable définie par l'utilisateur et autre chose.

Pour & Contre

Il fait son travail en une seule passe, sans joint, il devrait prendre O (n). Il ne peut pas récupérer toutes les parties d'intervalle de personne une découpe par des intervalles fermés de personne B. Il utilise des fonctionnalités spécifiques MySQL.

Pour la référence un code qui je ciselée récemment. Il peut être utilisé pour vérifier les plages de dates qui se chevauchent. Il est écrit en Ruby on Rails, mais l'idée (l'instruction SQL) peut facilement être traduit dans d'autres langues)

  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

Comme d'habitude avec les named scopes ce champ peut être réutilisé en combinaison avec d'autres champs d'application.

user = User.find(...)
today = Date.today
confirmed_absences = user.absences.confirmed.overlaps(today.beginning_of_month, today.end_of_month).count
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top