Les champs de date dans MySQL, trouver toutes les lignes qui ne se chevauchent pas et qui reviennent seulement la différence
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:
-
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.
-
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.
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