[英]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, 它的意思是,
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. 附注:如果您使用知道如何使用
LEAD
或LAG
窗口函数的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.