在两个日期之间查找元素并将它们分组

时间:2018-01-03 02:50:54

标签: mysql

我有这张桌子:

___Bookings

|--------|------------|--------------|------------|------------|
| BOO_Id | BOO_RoomId | BOO_ClientId | BOO_DateCI | BOO_DateCO |
|--------|------------|--------------|------------|------------|
| 1      | 9          | 45           | 2018-01-02 | 2018-01-03 |
| 2      | 4          | 46           | 2017-12-30 | 2018-01-07 |
| 3      | 3          | 2            | 2018-12-31 | 2018-01-01 |
| 4      | 9          | 98           | 2018-01-05 | 2018-01-10 |
|--------|------------|--------------|------------|------------|

我希望每天在出发,抵达或内部展示预订。

所需的输出应该是这样的:

2017-12-30  =  Booking #2 in arrival.
2017-12-31  =  Booking #3 in arrival.
            =  Booking #1 in arrival.
            =  Booking #2 in house.
2018-01-01  =  Booking #3 in departure.
            =  Booking #2 in house.
2018-01-02  =  Booking #1 in arrival.
            =  Booking #2 in house.
2018-01-03  =  Booking #1 in departure.
            =  Booking #2 in house.
2018-01-04  =  Booking #2 in house.
2018-01-05  =  Booking #4 in arrival.
            =  Booking #2 in house.
2018-01-06  =  Booking #2 in house.
            =  Booking #4 in house.
2018-01-07  =  Booking #2 in departure.
            =  Booking #4 in house.
2018-01-08  =  Booking #4 in house.
2018-01-09  =  Booking #4 in house.
2018-01-10  =  Booking #4 in departure.

我已经尝试过:

SELECT *,
    CASE 
      WHEN BOO_DateCI = '2017-12-31' THEN 'In Arrival'
      WHEN BOO_DateCO = '2018-01-10' THEN 'In Departure'
      WHEN '2017-12-31' > BOO_DateCI AND '2018-01-10' < BOO_DateCO THEN 'In House'
    END
FROM ___Bookings
WHERE BOO_DateCI = '2017-12-31'
OR BOO_DateCO = '2018-01-10'
OR :today BETWEEN BOO_DateCI AND BOO_DateCO;

但我无法获得in house预订,也无法根据状态arrivaldeparturein-house对预订进行分组。

2 个答案:

答案 0 :(得分:3)

原则

这是我的解决方案。一般的想法是生成一个您感兴趣的日期列表,然后JOIN此列表与您的预订一起,条件是特定客人访问期间的特定日期。

我假设这里有连续的日期。如果要查询多个非连续范围,可以为每个连续部分重复该过程,或者仍然可以查询整个范围,然后过滤掉您不感兴趣的日期。

实施

首先,您需要设置要为其生成该列表的日期范围。为方便起见,我将它们存储在两个变量中。

SELECT MIN(BOO_DateCI)
FROM Bookings 
INTO @first_date;

SELECT MAX(BOO_DateCO)
FROM Bookings 
INTO @last_day;

这将为您提供第一次登记入住和最后一次退房(包括两个日期)之间的整个范围。当然,您可以通过将这些变量设置为某个特定日期来轻松选择日期:

SET @first_day := '2018-01-02';
SET @last_day := '2018-01-06';

下一步是设置一个新变量,作为生成日期列表的计数器:

SELECT DATEDIFF(@last_day,@first_day) + 1 INTO @i;

现在一切都准备就绪了:

SELECT 
  Datelist.date, 
  Bookings.BOO_Id,
  CASE
    WHEN Bookings.BOO_DateCI = Datelist.date THEN 'In Arrival'
    WHEN Bookings.BOO_DateCO = Datelist.date THEN 'In Departure'
    WHEN Datelist.date > Bookings.BOO_DateCI AND Datelist.date < Bookings.BOO_DateCO THEN 'In House'
  END AS status
FROM 
  (SELECT DATE(@first_day + INTERVAL @i:=@i-1 DAY) AS date
    FROM (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) dummy1,
         (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) dummy2
    HAVING @i > 0) Datelist
INNER JOIN Bookings
ON Datelist.date BETWEEN Bookings.BOO_DateCI AND Bookings.BOO_DateCO
ORDER BY Datelist.date, BOO_Id;

如果您希望看到该野兽在行动,请检查 SQL Fiddle

生成日期列表

现在,该查询有一部分,我觉得需要更多解释,这就是Datelist的生成方式:

SELECT DATE(@first_day + INTERVAL @i:=@i-1 DAY) AS date
FROM AnyTable
HAVING @i > 0

这将生成一个日期列表,从@last_day开始到first_day(因为@i正在倒计时,而不是向上)。 AnyTable这里只是数据库中的任何表。这里需要注意的是,您不能省略FROM子句,因为它需要行,因此在每一行上都执行@i:=@i-1。但这意味着,您的日期列表最多包含与AnyTable相同的行数。所以,如果你只有短表(如你的例子),这将失败。为了解决这个问题,我们可以自己生成一些行:

(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) dummy1,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) dummy2

作为AnyTable的替代,这将生成两个派生表,每行4行并交叉连接,生成16行的结果,因为2个表,每行4行交叉连接,为您提供4x4 = 16行。如果您需要更多,可以使用更多UNION个,更多交叉连接或两者,例如:

(SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) dummy1,
(SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3) dummy2,
(SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) dummy3

将为您提供4x3x5 = 60行。

请注意:这并不意味着您的最终结果只能包含60行,这意味着您生成的日期列表最多可包含60个日期。

关于表现的一个词

正如您在 Fiddle 中看到的,生成16行的查询显然比生成100000行的查询要快。性能上的差异并不是很大,我多次运行该测试,16行的时间在2ms到8ms之间,10万次的时间在16ms到60ms之间。但是,如果您需要大量行并且您的代码经常执行,那么您可能会感觉更好,创建一个只包含100000个不同数字的虚拟表,并在每次运行查询时使用它而不是生成派生表(旁注:100000天差不多是274年,所以我很确定在你的特殊情况下你不需要那么多,但仍然值得注意的是,如果性能问题还有其他选择)

另一方面,如果您完全确定Bookings(在实际情况下)的行数多于您需要的行数,则可以使用Bookings代替AnyTable

答案 1 :(得分:0)

您好DamDam您需要使用存储过程来满足此要求,因为我们肯定需要使用循环来查找每天到达,离开和内部状态的数量。检查下面的代码。

这里我最初计算了check in date column (BOO_DateCI)的MIN日期作为循环的开始日期。 Loop将一直运行到今天的日期。

在if循环中,我正在做一个select语句 1.第一列是循环中的日期 2.第二栏是您需要的预订#+预订ID。 3.第三列是状态计算

  

如果BOO_DateCI == dateFromTheLoop =&gt;这意味着当天到达。

     

如果BOO_DateCO == dateFromTheLoop =&gt;这意味着当天离开。

     

如果BOO_DateCI&lt; dateFromTheLoop和BOO_DateCO&gt; dateFromTheLoop =&gt;   这意味着当时还在家里

然后按日期排序这些结果。

完成后,我将在循环日期中添加1天,以便在下一次迭代中使用。

CREATE PROCEDURE findBookings(d1 DATE) 
begin 
  LABEL1: 
  LOOP 
    SET d1 = Select MIN(BOO_DateCI) from ___Bookings; 

    IF d1 <= Date(NOW()) THEN 

      SELECT d1 as date, 
             Concat('Booking #', boo_id), 
             CASE 
                    WHEN BOO_DateCI = d1 THEN 'In Arrival' 
                    WHEN BOO_DateCO = d1 THEN 'In Departure' 
                    WHEN BOO_DateCI < d1 AND    BOO_DateCO > d1 THEN 'In House' 
             end 
      FROM ___Bookings Order by date;

      d1 = DATE_ADD(d1, INTERVAL 1 DAY);

    ITERATE label1; 
    end IF; 
    LEAVE label1; 
  end LOOP label1; 
end;

希望这有帮助。