简体   繁体   English

Mysql - 选择一天内不使用的小时

[英]Mysql - Select hour that not use in a day

I have two tables in my database that have a relation. 我的数据库中有两个表有关系。 I use MySQL. 我使用MySQL。 Basically, I created an app to manage 'Futsal's Field Order' So, here we go : 基本上,我创建了一个应用程序来管理'五人制足球场的顺序'所以,我们在这里:

The first table is named Lapangan means "Field in Indonesian" : 第一张桌子名为Lapangan,意为“印尼的田野”:

mysql> SELECT id,nama_lapangan FROM lapangan;
+----+---------------+
| id | nama_lapangan |
+----+---------------+
|  1 | Lap 01        |
|  2 | Lap 02        |
|  3 | Lap 03        |
+----+---------------+
3 rows in set (0.00 sec)

And The Second Table is Booking : , 第二张表是预订:

mysql> SELECT id, nomor_booking, date_booking, date_end_booking, lapangan_id FROM `yfutsal`.`booking` LIMIT 1000;
+----+---------------+---------------------+---------------------+-------------+
| id | nomor_booking | date_booking        | date_end_booking    | lapangan_id |
+----+---------------+---------------------+---------------------+-------------+
|  1 |             1 | 2017-07-16 10:00:00 | 2017-07-16 12:00:00 |           1 |
|  2 |             2 | 2017-07-16 15:00:00 | 2017-07-16 16:00:00 |           3 |
+----+---------------+---------------------+---------------------+-------------+

Here is the problem. 这是问题所在。

For example, The Owner start at 08.00 AND end in 23.00. 例如,所有者从08.00开始到23.00结束。

It means, 它的意思是,

  1. lap 1 is not available on 10.00 - 12.00 第1圈在10.00 - 12.00不可用
  2. lap 3 is not available on 15.00 - 16.00. 第3圈不在15.00 - 16.00。

The goal is, I want to display the Lapangan (field) that available with hour field, based hour that not include from case above. 目标是,我想显示可用于小时字段的Lapangan(字段),基于小时不包括上述情况。

So, the cashier can choice it. 所以,收银员可以选择它。 Something like this : 像这样的东西:

+----+---------------+----------------------+-----------------------+
| id | nama_lapangan |   Available Start    |    Available End      |
+----+---------------+----------------------+-----------------------+
|  1 | Lap 01        |  2017-07-16 08:00:00 |   2017-07-16 09:59:00 |
|  1 | Lap 01        |  2017-07-16 12:01:00 |   2017-07-16 23:00:00 |
|  2 | Lap 02        |  2017-07-16 08:00:00 |   2017-07-16 23:00:00 |
|  3 | Lap 03        |  2017-07-16 08:00:00 |   2017-07-16 14:59:00 |
|  3 | Lap 03        |  2017-07-16 16:01:00 |   2017-07-16 23:00:00 |
+----+---------------+----------------------+-----------------------+

From bottom of my heart, please advise. 从内心深处,请指教。

This is a rather tricky goal without window functions (which MySQL 5.7 doesn't have). 这是一个相当棘手的目标,没有窗口函数(MySQL 5.7没有)。 It is feasible, even if it isn't easy to understand. 这是可行的,即使它不容易理解。

For the sake of ease, I've used an extra table I've called parameters : 为了方便起见,我使用了一个额外的表,我称之为parameters

CREATE TABLE parameters
(
    start_date_time DATETIME,
    end_date_time DATETIME
) ;
INSERT INTO parameters 
VALUES ('2017-07-16 08:00', '2017-07-16 23:00') ;

The idea is, for a given lapangan (field), if one day there's just one booking (start_booking_1,end_booking_1), we will get two available periods: 这个想法是,对于给定的lapangan(字段),如果有一天只有一个预订(start_booking_1,end_booking_1),我们将获得两个可用期间:

start_date_time .. start_booking_1      <- first period
end_booking_1   .. end_date_time        <- 2

If one day there are two bookings (start_booking_1, end_booking_1) and (start_booking_2, end_booking_2), sorted, we'll have: 如果有一天有两个预订(start_booking_1,end_booking_1)和(start_booking_2,end_booking_2),已排序,我们将:

start_date_time .. start_booking_1      <- first period
end_booking_1   .. start_booking_2      <- 2
end_booking_2   .. end_date_time        <- 3

So, we need to distinguish the first period from the rest: 所以,我们需要将第一个时期与其他时期区分开来:

First free segment can be computed with: 可以使用以下公式计算第一个免费段

-- first free period
SELECT
    lapangan.id, parameters.start_date_time AS available_start, 
    coalesce( (SELECT date_start_booking
                 FROM booking b
                WHERE b.lapangan_id = lapangan.id
              ORDER BY b.date_start_booking ASC
                LIMIT 1
               ), parameters.end_date_time) AS available_end
FROM
    lapangan, parameters

NOTE: the way to search for start_booking_1 is the awful subquery. 注意:搜索start_booking_1是可怕的子查询。 If it doesn't return a value, we'll go to the end_date_time . 如果它没有返回值,我们将转到end_date_time

The intermediate (and last) periods, are computed with: 中间(和最后)期间计算如下:

-- 2..n free periods
SELECT
    lapangan.id, 
    b1.date_end_booking AS available_start, 
    coalesce ( (SELECT date_start_booking
                  FROM booking b2
                 WHERE b2.lapangan_id = b1.lapangan_id 
                     AND b2.date_start_booking >= b1.date_end_booking
              ORDER BY b2.date_start_booking ASC
                 LIMIT 1), 
               (SELECT parameters.end_date_time 
                  FROM parameters)
    ) AS avilable_end
FROM
    lapangan
    JOIN booking b1 ON b1.lapangan_id = lapangan.id

You'll have to put everything together, and take care of possible empty periods. 你必须将所有东西放在一起,并照顾可能的空白时期。 Then you'll get 然后你会得到

SELECT DISTINCT
    lapangan.id, lapangan.nama_lapangan, av.available_start, av.available_end AS available_end
FROM
    (    
    -- first free period
    SELECT
        lapangan.id, parameters.start_date_time AS available_start, 
        coalesce( (SELECT date_start_booking
                     FROM booking b
                    WHERE b.lapangan_id = lapangan.id
                  ORDER BY b.date_start_booking ASC
                    LIMIT 1
                   ), parameters.end_date_time) AS available_end
    FROM
        lapangan, parameters

    UNION  
    -- 2..n free periods
    SELECT
        lapangan.id, 
        b1.date_end_booking AS available_start, 
        coalesce ( (SELECT date_start_booking
                      FROM booking b2
                     WHERE b2.lapangan_id = b1.lapangan_id 
                         AND b2.date_start_booking >= b1.date_end_booking
                  ORDER BY b2.date_start_booking ASC
                     LIMIT 1), 
                   (SELECT parameters.end_date_time 
                      FROM parameters)
        ) AS avilable_end
    FROM
        lapangan
        JOIN booking b1 ON b1.lapangan_id = lapangan.id
    ) AS av
    JOIN lapangan ON lapangan.id = av.id
WHERE
    -- Ignore empty segments
    av.available_start < av.available_end
ORDER BY
    lapangan.id, available_start ;

Which will give you the intended result. 哪个会给你预期的结果。

id | nama_lapangan | available_start     | available_end      
-: | :------------ | :------------------ | :------------------
 1 | Lap 01        | 2017-07-16 08:00:00 | 2017-07-16 10:00:00
 1 | Lap 01        | 2017-07-16 12:00:00 | 2017-07-16 23:00:00
 2 | Lap 02        | 2017-07-16 08:00:00 | 2017-07-16 23:00:00
 3 | Lap 03        | 2017-07-16 08:00:00 | 2017-07-16 15:00:00
 3 | Lap 03        | 2017-07-16 16:00:00 | 2017-07-16 23:00:00

Note that I have not subtracted 1 minute or 1 second from the start or end. 请注意,我没有从开始或结束中减去1分钟或1秒。 Assume that your periods are considered: 假设您的期间被考虑:

start <= available_time < end 

or, in range terminology 或者,在范围术语中

[start, end)

If you really need to subtract 1 second, do it in the first line of the select. 如果您确实需要减去1秒,请在选择的第一行中进行。

You can check everything (with some step-by-step approach to reaching the solution) at dbfiddle here 您可以检查一切(有一些一步一步的方法来达到溶液)dbfiddle 这里


Side Note: this is much easier if you use a DB that knows how to use the LEAD or LAG window functions. 附注:如果您使用知道如何使用LEADLAG窗口函数的DB,则会更容易。

The essence of this problem is to generate rows of data where there isn't anything stored. 这个问题的本质是生成没有存储任何内容的数据行。 In other words you have the hours that are booked stored, but not the hours which remain un-booked. 换句话说,您有预订的小时数,但不是未预订的小时数。 So you need a way to generate these. 所以你需要一种方法来生成这些。 While there are several alternatives to this a simple option is to use a Cartesian product to do this. 虽然有几种替代方法,但一个简单的选择是使用笛卡尔积来做到这一点。

Here I have opted to store the hours of operation in a table as a set of integers 8 to 23 (maybe 8 to 22 depending on your intention). 在这里,我选择将操作时间存储在一个表中,作为一组整数8到23(根据你的意图,可能是8到22)。 This can then be used in a CROSS JOIN (to produce a Cartesian product) on any given date of all hours for all fields (nama_lapangan). 然后,可以在所有字段的所有小时的任何给定日期(nama_lapangan)中使用CROSS JOIN(以生成笛卡尔积)。 Once this is generated we can LEFT JOIN all the available hours to these which are already booked, and then where there is no current related booking (IS NULL) we determine the available hours for that day. 一旦生成了这个,我们就可以将所有可用的小时数加入已经预订的那些小时,然后在没有当前相关预订(IS NULL)的情况下,我们确定当天的可用小时数。

DATA 数据

CREATE TABLE OpenHours
    (`StartAt` int)
;

INSERT INTO OpenHours     (`StartAt`)
VALUES
    (8),    (9),    (10),    (11),    (12),    (13),    (14),    (15),
    (16),    (17),    (18),    (19),    (20),    (21),    (22),    (23)
;

CREATE TABLE lapangan
    (`id` int, `nama_lapangan` varchar(6))
;

INSERT INTO lapangan     (`id`, `nama_lapangan`)
VALUES
    (1, 'Lap 01'), (2, 'Lap 02'), (3, 'Lap 03')
;


CREATE TABLE yfutsal
    (`id` int, `nomor_booking` int, `date_booking` datetime, `date_end_booking` datetime, `lapangan_id` int)
;

INSERT INTO yfutsal
    (`id`, `nomor_booking`, `date_booking`, `date_end_booking`, `lapangan_id`)
VALUES
    (1, 1, '2017-07-16 10:00:00', '2017-07-16 12:00:00', 1),
    (2, 2, '2017-07-16 15:00:00', '2017-07-16 16:00:00', 3)
;

QUERY QUERY

set @dt := str_to_date('2017-07-16','%Y-%m-%d');

select
    l.id
  , l.nama_lapangan
  , date_add(@dt,INTERVAL h.StartAt HOUR) AvailStartHr
  , date_add(@dt,INTERVAL h.StartAt+1 HOUR) AvailEndHr
from lapangan l
cross join OpenHours h
left join yfutsal y on l.id = y.lapangan_id
                   and date_add(@dt,INTERVAL h.StartAt HOUR) between date_booking and date_end_booking
where y.date_booking IS NULL
order by l.nama_lapangan, AvailStartHr
;

RESULT 结果

| id | nama_lapangan |        AvailStartHr |          AvailEndHr |
|----|---------------|---------------------|---------------------|
|  1 |        Lap 01 | 2017-07-16 08:00:00 | 2017-07-16 09:00:00 |
|  1 |        Lap 01 | 2017-07-16 09:00:00 | 2017-07-16 10:00:00 |
|  1 |        Lap 01 | 2017-07-16 13:00:00 | 2017-07-16 14:00:00 |
|  1 |        Lap 01 | 2017-07-16 14:00:00 | 2017-07-16 15:00:00 |
|  1 |        Lap 01 | 2017-07-16 15:00:00 | 2017-07-16 16:00:00 |
|  1 |        Lap 01 | 2017-07-16 16:00:00 | 2017-07-16 17:00:00 |
|  1 |        Lap 01 | 2017-07-16 17:00:00 | 2017-07-16 18:00:00 |
|  1 |        Lap 01 | 2017-07-16 18:00:00 | 2017-07-16 19:00:00 |
|  1 |        Lap 01 | 2017-07-16 19:00:00 | 2017-07-16 20:00:00 |
|  1 |        Lap 01 | 2017-07-16 20:00:00 | 2017-07-16 21:00:00 |
|  1 |        Lap 01 | 2017-07-16 21:00:00 | 2017-07-16 22:00:00 |
|  1 |        Lap 01 | 2017-07-16 22:00:00 | 2017-07-16 23:00:00 |
|  1 |        Lap 01 | 2017-07-16 23:00:00 | 2017-07-17 00:00:00 |
|  2 |        Lap 02 | 2017-07-16 08:00:00 | 2017-07-16 09:00:00 |
|  2 |        Lap 02 | 2017-07-16 09:00:00 | 2017-07-16 10:00:00 |
|  2 |        Lap 02 | 2017-07-16 10:00:00 | 2017-07-16 11:00:00 |
|  2 |        Lap 02 | 2017-07-16 11:00:00 | 2017-07-16 12:00:00 |
|  2 |        Lap 02 | 2017-07-16 12:00:00 | 2017-07-16 13:00:00 |
|  2 |        Lap 02 | 2017-07-16 13:00:00 | 2017-07-16 14:00:00 |
|  2 |        Lap 02 | 2017-07-16 14:00:00 | 2017-07-16 15:00:00 |
|  2 |        Lap 02 | 2017-07-16 15:00:00 | 2017-07-16 16:00:00 |
|  2 |        Lap 02 | 2017-07-16 16:00:00 | 2017-07-16 17:00:00 |
|  2 |        Lap 02 | 2017-07-16 17:00:00 | 2017-07-16 18:00:00 |
|  2 |        Lap 02 | 2017-07-16 18:00:00 | 2017-07-16 19:00:00 |
|  2 |        Lap 02 | 2017-07-16 19:00:00 | 2017-07-16 20:00:00 |
|  2 |        Lap 02 | 2017-07-16 20:00:00 | 2017-07-16 21:00:00 |
|  2 |        Lap 02 | 2017-07-16 21:00:00 | 2017-07-16 22:00:00 |
|  2 |        Lap 02 | 2017-07-16 22:00:00 | 2017-07-16 23:00:00 |
|  2 |        Lap 02 | 2017-07-16 23:00:00 | 2017-07-17 00:00:00 |
|  3 |        Lap 03 | 2017-07-16 08:00:00 | 2017-07-16 09:00:00 |
|  3 |        Lap 03 | 2017-07-16 09:00:00 | 2017-07-16 10:00:00 |
|  3 |        Lap 03 | 2017-07-16 10:00:00 | 2017-07-16 11:00:00 |
|  3 |        Lap 03 | 2017-07-16 11:00:00 | 2017-07-16 12:00:00 |
|  3 |        Lap 03 | 2017-07-16 12:00:00 | 2017-07-16 13:00:00 |
|  3 |        Lap 03 | 2017-07-16 13:00:00 | 2017-07-16 14:00:00 |
|  3 |        Lap 03 | 2017-07-16 14:00:00 | 2017-07-16 15:00:00 |
|  3 |        Lap 03 | 2017-07-16 17:00:00 | 2017-07-16 18:00:00 |
|  3 |        Lap 03 | 2017-07-16 18:00:00 | 2017-07-16 19:00:00 |
|  3 |        Lap 03 | 2017-07-16 19:00:00 | 2017-07-16 20:00:00 |
|  3 |        Lap 03 | 2017-07-16 20:00:00 | 2017-07-16 21:00:00 |
|  3 |        Lap 03 | 2017-07-16 21:00:00 | 2017-07-16 22:00:00 |
|  3 |        Lap 03 | 2017-07-16 22:00:00 | 2017-07-16 23:00:00 |
|  3 |        Lap 03 | 2017-07-16 23:00:00 | 2017-07-17 00:00:00 |

see: http://sqlfiddle.com/#!9/775f36/4 请参阅: http//sqlfiddle.com/#!9/775f36 / 4

In the former answer you learn how to generate the required available hours row by row. 在前一个答案中,您将学习如何逐行生成所需的可用小时数。 Once you have that data available, then it can be summarized into ranges using the following technique for MySQL: 一旦有了这些数据,就可以使用以下MySQL技术将其汇总到范围内:

set @dt := str_to_date('2017-07-16','%Y-%m-%d')

SELECT id, nama_lapangan, AvailStart, MAX(AvailEndHr) AvailEndHr
FROM (
  SELECT
         mytable.*
       , @fin := IF(@lid<=>id AND TIMESTAMPDIFF(HOUR, @d, AvailStartHr)=1, @fin, AvailStartHr) AS AvailStart
       , @lid := id
       , @d := AvailStartHr
  FROM (
      select
          l.id
        , l.nama_lapangan
        , date_add(@dt,INTERVAL h.StartAt HOUR) AvailStartHr
        , date_add(@dt,INTERVAL h.StartAt+1 HOUR) AvailEndHr
      from lapangan l
      cross join OpenHours h
      left join yfutsal y on l.id = y.lapangan_id
                         and date_add(@dt,INTERVAL h.StartAt HOUR) between date_booking and date_end_booking
      where y.date_booking IS NULL
     ) mytable
  CROSS JOIN (SELECT @lid:=NULL, @d:=NULL, @fin:= NOW()) AS init
  ORDER BY id, AvailStartHr
  ) d
GROUP BY  id, nama_lapangan, AvailStart

Results see sqlfiddle : 结果 见sqlfiddle

| id | nama_lapangan |          AvailStart |          AvailEndHr |
|----|---------------|---------------------|---------------------|
|  1 |        Lap 01 | 2017-07-16 08:00:00 | 2017-07-16 10:00:00 |
|  1 |        Lap 01 | 2017-07-16 13:00:00 | 2017-07-17 00:00:00 |
|  2 |        Lap 02 | 2017-07-16 08:00:00 | 2017-07-17 00:00:00 |
|  3 |        Lap 03 | 2017-07-16 08:00:00 | 2017-07-16 15:00:00 |
|  3 |        Lap 03 | 2017-07-16 17:00:00 | 2017-07-17 00:00:00 |

Also see this previous answer on a similar theme. 另请参阅此前关于类似主题的答案

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM