Поля даты в MySQL, поиск всех строк, которые не перекрываются, и возврат только разницы
Вопрос
Итак, это был один из моих самых первых вопросов здесь, но у меня есть небольшое изменение:
Итак, у меня есть два человека, чьи расписания есть в базе данных.В расписаниях просто записывается время начала, окончания и описание различных событий / встреч для обоих пользователей.
PersonA хочет поменяться встречами с PersonB.Мне нужен запрос MySQL, который вернет все эти значения, которые PersonB и PersonA могут поменять местами.
Первоначально параметры запроса должны были исключать любые встречи PersonB, где было перекрытие с PersonA, и встреча PersonB должна была быть точно такой же длины, как встреча, которую PersonA хочет поменять местами.Я получил несколько отличных советов по временной арифметике / геометрии, которые помогли мне получить нужные результаты.
Теперь я хочу изменить параметр 1 к 1, чтобы встречи не обязательно были одинаковой длины.Таким образом, если PersonA захочет поменять свою встречу в понедельник утром (с 10: 00 до 11: 30), запрос будет:
- Исключить любые встречи PersonB, которые проводятся во время одной из встреч PersonA
- Включите любые встречи PersonB, которые выходят за рамки встреч PersonA
- Включите те части встреч PersonB, которые проводятся, пока PersonA бесплатна, но покажите только бесплатную часть.
Итак, если PersonA захочет поменять местами указанную выше встречу (опять же, в понедельник с 10: 00 до 11: 30), и у PersonA назначена встреча во вторник с 13:00 до 15:00, а у PersonB назначена встреча во вторник с 12: 00 до 16: 00, запрос вернет:
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
В дополнение к любым другим возможностям.Не слишком ли многого можно ожидать от базы данных?Если да, то есть какие-либо предложения о том, как, по крайней мере, получить те сдвиги, которые перекрываются, но имеют время, зависающее с обеих сторон, чтобы PHP-скрипт мог справиться с ними?
по запросу searlea, вот немного больше контекста:
Я продолжал говорить о встречах, но, думаю, на самом деле я имел в виду "работу", то есть "рабочие смены".PersonA и PersonB работают в одном офисе.В vcalendar рабочие смены обычно называются "Мероприятиями", но иногда "Встречами", и я выбрал последнее, поскольку это меньше похоже на то, что два человека собираются на ярмарку.
Итак, у PersonA смена по мытью посуды в понедельник с 10:00 до 11:30 утра.ПерсонБ готовит по вторникам с 12:00 до 17:00.ПерсонА действительно хочет повидаться со своим братом, прежде чем они уедут из города в понедельник.Он предпочел бы взять отгул на все утро понедельника, но согласился бы и на часовую смену .
Так что в моей старой модели (воспитанной в мой самый первый вопрос здесь), я искал любые сдвиги, где не было перекрытия и где сдвиги были равны по времени.Но здесь есть две проблемы:
Если мне нужен кто-то для моей 2-часовой смены во вторник, а я работаю 4 часа в четверг, а Джо работает 8 часов в четверг, я мог бы поменять два его часа, и он мог бы уйти немного пораньше, а я мог бы остаться немного позже.
Если у меня двухчасовая смена, но я бы с радостью поменял ее на час, чтобы успеть в аэропорт вовремя, я хочу знать, приедет ли такой-то на час раньше меня позже на неделе, чтобы я мог взять на себя эту часть его смены.
Короче говоря (слишком поздно), я хочу то, что, по-видимому, известно как относительное дополнение из сдвигов PersonA в PersonB (в основном в любое время, когда PersonB работает, а PersonA - нет, независимо от того, перекрываются ли сдвиги в какой-то другой момент.)
В идеале я бы получил набор результатов, который включал бы те фрагменты, которые PersonB выполнял, а PersonA - нет (упомянутые выше две смены по 1 часу), а также всю смену (со специальным тегом, указывающим, что она недоступна в целом), чтобы PersonA увидела, что он выполняет часть смены, а не запуталась и не подумала, что PersonB просто случайно отработал две смены по одному часу.
Все это начинает казаться немного сложным.По сути, я хочу, чтобы смены PersonB были синими, смены PersonA - желтыми, и я хочу, чтобы база данных возвращала все части, которые не являются зелеными.
Решение
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
)
)
При этом выбираются события Ондры, которые могут заполнить пробел в дневнике Жижки.
Отредактировано:Изначально это было пересечение, но если вам нужно относительное дополнение, этого достаточно.
Другие советы
Пусть $shift_id
это идентификатор смены, которую ваш пользователь хочет поменять местами.
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;
Протестировано с помощью:
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');
Результаты:
'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'
Здесь показаны все смены, для которых можно поменять местами любую часть (части), в том числе
сколько общего времени (в секундах) можно поменять местами.Заключительная колонка, conflict_times
,
показывает время, на которое уже запланирована работа заменяющего пользователя.Приложению должно быть легко извлечь из этого доступное время;это возможно, но очень сложно, в MySQL.
Задача
Возвращает все интервалы двух разных пользователей, за исключением тех частей, где они перекрываются.
Таблица и данные испытаний
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');
Результаты теста
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
Решение
Я использовал функцию MySQL под названием «Пользовательские переменные» для достижения цели с помощью следующего запроса:
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;
Основная идея состоит в том, чтобы дважды отсортировать таблицу по времени, чтобы каждая запись появлялась дважды.Один раз для времени начала, а затем для времени окончания.Затем я использую определяемые пользователем переменные, чтобы отслеживать состояние при просмотре записей и возвращать только записи «время окончания» со временем начала и временем окончания, скорректированными с учетом перекрывающихся интервалов.
Предположения
Единственное предположение состоит в том, что ни один интервал человека x не перекрывается с другим интервалом того же человека.
Поведение
Несколько случаев и их результаты:
< ( > )
< > ( )
( < ) ( > )
( ) < > ( )
< ( ) > // for this and similar cases only last part of interval is returned
< >
( < ) ( ) ( ) ( > ) // like so
( ) < > ( )
Предостережения
Должно быть, я использовал временную метку unix, поскольку мой сервер MySQL не мог сравнить DATETIME, хранящийся в пользовательской переменной, и что-то еще.
За и против
Он выполняет свою работу за один проход без каких-либо соединений, поэтому это должно занять время O(N).Он не может получить все части интервала человека А, вырезанные из вложенных интервалов человека Б.Он использует специфические функции MySQL.
Для справки — фрагмент кода, который я недавно использовал.Его можно использовать для проверки перекрывающихся диапазонов дат.Он написан на Ruby on Rails, но идею (оператор SQL) можно легко перевести на другие языки)
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
Как обычно с именованными областями, эту область можно повторно использовать в сочетании с любыми другими областями.
user = User.find(...)
today = Date.today
confirmed_absences = user.absences.confirmed.overlaps(today.beginning_of_month, today.end_of_month).count