Поля даты в MySQL, поиск всех строк, которые не перекрываются, и возвращение только разницы - PullRequest
0 голосов
/ 26 августа 2009

Итак, это был один из моих первых вопросов, но у меня есть небольшое отклонение:

Итак, у меня есть два человека, чьи расписания находятся в базе данных. Расписания просто записывают время начала, время окончания и описание различных событий / встреч для обоих пользователей.

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 смена посуды. PersonB готовит во вторник с 12:00 до 17:00. PersonA очень хочет увидеть своего брата, прежде чем они уедут из города в понедельник. Он предпочел бы уйти весь понедельник утром, но согласился бы получить час смены.

Итак, в моей старой модели (поднятой в мой самый первый вопрос здесь) я искал любые сдвиги, в которых не было перекрытия и где сдвиги были равны во времени. Но это имеет две проблемы:

  1. Если мне нужен кто-то, чтобы покрыть мою 2-часовую смену во вторник, и я работаю в течение 4 часов в четверг, а Джо работает в течение 8 часов в четверг, я могу поменять два его часа, и он может уйти немного раньше и я могу остаться чуть позже.

  2. Если у меня есть двухчасовая смена, но я бы с удовольствием обменял час этого, чтобы успеть в аэропорт вовремя, я хочу знать, произойдет ли то или иное на час раньше, чем я позже в неделю, чтобы я мог принять эту часть его смены.

Короче говоря (слишком поздно), я хочу то, что, по-видимому, известно как относительное дополнение изменений PersonA к PersonB (в основном, в любое время, когда PersonB работает, а PersonA не работает). независимо от того, перекрываются ли сдвиги в какой-то другой точке.)

В идеале я хотел бы получить набор результатов, включающий биты, которые PersonB работал, а PersonA - нет (две упомянутые выше смены в 1 час), а также всю смену (со специальным тегом, указывающим, что это не так). в целом), так что PersonA увидит, что он покрывает часть смены, и не запутается и подумает, что PersonB только что работал две смены по одному часу.

Все это начинает казаться немного сложным. По сути, я хочу, чтобы сдвиги PersonB были синего цвета, сдвиги PersonA были желтыми, и я хочу, чтобы база данных возвращала все не зеленые части.

Ответы [ 4 ]

1 голос
/ 01 сентября 2009
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 
    )
)

Это выбирает события Ондры, которые могут вписаться в пробел в дневнике Жижки.

Отредактировано: Первоначально это было пересечение, но если вы хотите относительное дополнение, этого достаточно.

0 голосов
/ 13 октября 2009

Для справки взломан код, который я недавно использовал. Может использоваться для проверки перекрывающихся диапазонов дат. Он написан на 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
0 голосов
/ 03 сентября 2009

Задача

Вернуть все интервалы двух разных пользователей, кроме частей, где они перекрываются.

Таблица и данные испытаний

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.

0 голосов
/ 02 сентября 2009

Пусть $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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...