【问题标题】:Date fields in MySQL, finding all rows that don't overlap and returning only the differenceMySQL中的日期字段,查找所有不重叠的行并仅返回差异
【发布时间】:2009-08-26 09:04:19
【问题描述】:

所以这是我在这里的第一个问题之一,但我有一点不同:

所以我有两个人的日程安排在数据库中。时间表只是记录了两个用户的各种事件/约会的开始时间、结束时间和描述。

PersonA 想与 PersonB 交换约会。我想要一个 MySQL 查询,它将返回 PersonB 和 PersonA 可以交换的所有时间。

最初查询的参数是丢弃与 PersonA 重叠的 PersonB 的任何约会,并且 PersonB 的约会必须与 PersonA 想要交换的约会的长度完全相同。我得到了一些关于时间算术/几何的很好的建议,帮助我得到了我需要的结果。

现在我想更改 1 对 1 参数,以便约会的长度不必相等。因此,如果 PersonA 想要交换他周一早上的约会(上午 10:00 - 上午 11:30),查询将:

  • 排除在 PersonA 约会期间的任何 PersonB 约会
  • 包括 PersonB 与 PersonA 的约会之外的任何约会
  • 包括 PersonA 空闲时 PersonB 的约会部分,但仅显示空闲部分。

因此,如果 PersonA 想要交换上述约会(同样是周一上午 10:00 - 11:30),并且 PersonA 有一个周二下午 1:00 到下午 3:00 的约会,而 PersonB 有一个周二的约会从中午 12:00 到下午 4: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 到下午 5:00 做饭。 PersonA 真的很想在周一离开城镇之前见到他的兄弟。他宁愿整个星期一早上都休息,但他宁愿休息一个小时。

所以在我的旧模型中(在my very first question 中提出),我一直在寻找没有重叠且时间相等的任何班次。但这有两个问题:

  1. 如果我需要有人来代替我周二的 2 小时轮班,而我周四工作 4 小时,而乔周四工作 8 小时,我可以交换他的两个小时,他可以提前下班我可以待一会儿。

  2. 如果我有两个小时的班次,但我很乐意换一个小时以便准时到达机场,我想知道某某是否比我晚一个小时来一周后,我就可以参加他的轮班。

长话短说(为时已晚),我想要显然被称为 relative complement 的 PersonA 转移到 PersonB(基本上任何时候 PersonB 正在工作而 PersonA 不是,无论这些班次是否在其他点重叠.)

理想情况下,我会得到一组结果,其中包括 PersonB 正在工作而 PersonA 没有工作的位(上面提到的两个 1 小时轮班),以及整个轮班(带有一个特殊标签来表明它不是整体可用),这样 PersonA 会看到他正在轮班的一部分,而不会感到困惑,并认为 PersonB 恰好在工作两个一小时的班次。

这听起来有点复杂。基本上我希望 PersonB 的班次是蓝色的,PersonA 的班次是黄色的,我希望数据库返回所有不是绿色的部分。

【问题讨论】:

  • 您的要求并不完全清楚。 PersonA 可以将“牙医”预约换成 PersonB 的“烹饪”吗? PersonA 可以与 PersonB 交换所有约会 - 如果不能,为什么不(一次交换一次?)最后,如果它提供相关的背景信息,你能挖掘出你提到的前面问题的链接吗?
  • 为什么周二 12:00-13:00(1 小时时段)适合于周一 10:00-11:30(1.5 小时时段)的交换?另外,为什么 16:00-17:00 的时间段停在 17:00 而不是 24:00 (23:59)?
  • 一小时的时间段很合适,因为时间不与personA的班次重叠。他宁愿在 personB 的轮班中工作 1 小时,并在他的 1.5 小时中得到一个小时的保障,然后什么都不做。它直到午夜才结束,因为他专门寻找其他人正在工作的时间,而不仅仅是他不在的时间。不过,好问题。我发现它变得非常棘手。

标签: mysql datetime time calendar scheduler


【解决方案1】:
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 
    )
)

这会选择 Ondra 的事件,这些事件可以填入 Zizka 日记的空白处。

已编辑:本来是相交,但如果要相对补,这就够了。

【讨论】:

    【解决方案2】:

    $shift_id 成为您的用户想要交换的班次的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 中这是可能的,但非常棘手。

    【讨论】:

      【解决方案3】:

      任务

      返回两个不同用户的所有区间,除了他们重叠的部分。

      表格和测试数据

      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) 时间。 它无法检索出被人 B 的封闭区间截取的人 A 的区间的所有部分。 它使用 MySQL 特定的功能。

      【讨论】:

        【解决方案4】:

        作为参考,我最近使用了一段代码。它可用于检查重叠的日期范围。它是用 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
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多