我的数据库中有两个表有关系。我使用MySQL。基本上,我创建了一个应用程序来管理“Futsal的Field Order'所以,我们走了:
第一张桌子名为Lapangan,意思是" Field in Indonesian" :
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)
第二张表是预订:,
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 |
+----+---------------+---------------------+---------------------+-------------+
这是问题所在。
例如,所有者从08.00开始到23.00结束。
这意味着,
目标是,我想显示小时字段可用的Lapangan(字段),基于上面不包括的小时。
所以,收银员可以选择它。像这样:
+----+---------------+----------------------+-----------------------+
| 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 |
+----+---------------+----------------------+-----------------------+
请从心底告知。
答案 0 :(得分:1)
这是一个相当棘手的目标,没有窗口函数(MySQL 5.7没有)。这是可行的,即使它不容易理解。
为了方便起见,我使用了额外的表格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') ;
对于给定的lapangan(字段),如果有一天一个预订(start_booking_1,end_booking_1),我们将获得两个可用期间:
start_date_time .. start_booking_1 <- first period
end_booking_1 .. end_date_time <- 2
如果有一天有两个预订(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
因此,我们需要将第一个时期与其他时期区分开来:
可以使用以下方式计算第一个免费分段:
-- 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
注意:搜索start_booking_1
的方法是可怕的子查询。如果它没有返回值,我们将转到end_date_time
。
中间(和最后)句点用:
计算-- 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
您必须将所有内容放在一起,并处理可能的空时段。然后你会得到
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 ;
这将为您提供预期的结果。
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
请注意,我从开始或结束时减去 1分钟或1秒。假设您的期间被考虑:
start <= available_time < end
或,在范围术语
[start, end)
如果你真的需要减去1秒,请在选择的第一行中进行。
您可以在 dbfiddle here
检查所有内容(通过一些逐步的方法来获得解决方案)旁注:如果您使用知道如何使用LEAD
或LAG
窗口函数的数据库,这会更容易。
答案 1 :(得分:1)
这个问题的本质是生成没有存储任何内容的数据行。换句话说,您有预订的小时数,但不是未预订的小时数。所以你需要一种方法来生成这些。虽然有几种替代方法,但一个简单的选择是使用笛卡尔积来做到这一点。
这里我选择将操作时间存储在一个表中,作为一组整数8到23(根据你的意图,可能是8到22)。然后,可以在所有字段的所有小时的任何给定日期(nama_lapangan)中使用CROSS JOIN(以生成笛卡尔积)。一旦生成了这个,我们就可以将所有可用的小时数加入已经预订的那些小时,然后在没有当前相关预订(IS NULL)的情况下,我们确定当天的可用小时数。
数据强>
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 强>
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 |
答案 2 :(得分:1)
在前一个答案中,您将学习如何逐行生成所需的可用小时数。一旦有了这些数据,就可以使用以下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
结果 see 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 |
另请参阅类似主题的this previous answer。