我正在使用如下所示的数据库模式,并且正在努力编写对该模式的查询。
酒店签订有有效期的合同。它也有许多季节,每个季节都有许多时期。任务是返回合同有效期中季节性期间不可用的所有日期。
这是我的架构:
理想情况下,我想获得不包括酒店期间的所有可能的时间间隔。但如果不可能,那么至少有一个日期列表。
示例(所有日期均采用 DD.MM.YYYY 格式):
在这种情况下,我损失了一个月 1.07.2018 – 31.07.2018。我想得到这个时期,或者几个时期,如果碰巧有几个。所以,像这样:
开始日期 结束日期 --------- ---------- 1.07.2018 31.07.2018
如果无法获得期间列表,那么我想获取所有缺失的日期,并将它们分组到服务器端的期间。
如果酒店永远不会有重叠的季节性时段,则可以通过使用以下方法直接匹配间隔来获得作为间隔列表的结果。
首先,您需要反转季节性周期列表,这意味着获取列表项目之间的差距列表,并用另外两个间隔表示第一个项目之前的周期和最后一个项目之后的周期。换句话说,像这样的列表:
date_from date_to ---------- --------- date_from 1 date_to 1 date_from 2 date_to 2 . . . date_from N date_to N
将转换为这样的列表:
date_from date_to ----------------- ------------------ 0001-01-01 date_from 1 - 1 天 date_to 1 + 1 天 date_from 2 - 1 天 date_to 2 + 1 天 date_from 3 - 1 天 . . . date_to N-1 + 1 天 date_from N - 1 天 date_to N + 1 天 9999-12-31
第二个列表中的和指的是第一个列表中的相应值。一天的调整是为了说明输入间隔和输出间隔都包含在内的事实。date_fromi
date_toi
这是根据您的架构生成 SQL 中的差距列表的方式:
SELECT
IFNULL(
(
SELECT
hs_last.date_to + INTERVAL 1 DAY
FROM
hotel_season AS hs_last
INNER JOIN hotel_season_period AS hsp_last
ON hs_last.id = hsp_last.hotel_season_id
WHERE
hs_last.hotel_id = hs_this.hotel_id
AND hsp_last.date_from < hsp_this.date_from
ORDER BY
hsp_last.date_from DESC
LIMIT
0, 1
),
CAST('0001-01-01' AS date)
) AS date_from,
hs_this.date_from - INTERVAL 1 DAY AS date_to
FROM
hotel_season AS hs_this
INNER JOIN hotel_season_period AS hsp_this
ON hs_this.id = hsp_this.hotel_season_id
WHERE
hs_this.hotel_id = @param_hotel_id
UNION ALL
SELECT
MAX(hs_this.date_to) + INTERVAL 1 DAY AS date_from,
CAST('9999-12-31' AS date) AS date_to
FROM
hotel_season AS hs_this
INNER JOIN hotel_season_period AS hsp_this
ON hs_this.id = hsp_this.hotel_season_id
WHERE
hs_this.hotel_id = @param_hotel_id
;
Run Code Online (Sandbox Code Playgroud)
请注意,相邻的季节性周期将产生“间隙”,其中date_from > date_to
. 当然,这些范围是无效的,您需要在进一步处理之前将它们过滤掉。
进一步处理将涉及找到与酒店合同期相交的差距。与它相交的那些实际上将是您想要返回的间隔,除非您可能需要调整第一个匹配间隙的开始以及最后一个匹配间隙的结束,因为它们可能超出合同期限范围.
这是匹配条件,它考虑到合同期和缺口都是包含区间:
gap.date_to >= contract.date_from AND gap.date_from <= contract.date_to
Run Code Online (Sandbox Code Playgroud)
也就是说,当其结束在合同开始之后或完全匹配而同时间隙的开始在合同结束之前或完全匹配时,将间隙视为与合同期相交。
正如我已经说过的,第一个和最后一个匹配的差距可能会部分超出合同期限,即像这样:
合同: [ ] 第一个差距:[ ] 最后一个差距:[ ]
对于输出,您需要像这样修改它们:
合同: [ ] 第一个差距:[ ] 最后一个差距:[ ]
这意味着你将需要同时计算开始和每个间隙结束:采取的最新gap.date_from
和contract.date_from
和起点之间的最早gap.date_to
和contract.date_to
作为结束:
GREATEST(gap.date_from, contract.date_from) AS date_from,
LEAST (gap.date_to , contract.date_to ) AS date_to
Run Code Online (Sandbox Code Playgroud)
把所有东西放在一起,这就是你得到的:
SELECT
GREATEST(gap.date_from, contract.date_from) AS date_from,
LEAST (gap.date_to , contract.date_to ) AS date_to
FROM
list_of_gaps AS gap
CROSS JOIN contract
WHERE
contract.company_id = @param_hotel_id
AND gap.date_to >= contract.date_from
AND gap.date_from <= contract.date_to
;
Run Code Online (Sandbox Code Playgroud)
对于list_of_gaps
您可以直接使用第一个查询作为派生表:
...
FROM
(
SELECT
IFNULL(
.
.
.
) AS gap
...
Run Code Online (Sandbox Code Playgroud)
或者您可以先将该查询的结果存储在临时表中:
CREATE TEMPORARY TABLE tmp_gap_list
AS
SELECT
IFNULL(
.
.
.
Run Code Online (Sandbox Code Playgroud)
并使用临时表作为list_of_gaps
替代:
SELECT
GREATEST(gap.date_from, contract.date_from) AS date_from,
LEAST (gap.date_to , contract.date_to ) AS date_to
FROM
tmp_gap_list AS gap
.
.
.
Run Code Online (Sandbox Code Playgroud)
后一种选择——分两个不同的步骤完成工作——可能更快,这取决于 MySQL 是否会发现单查询解决方案太复杂而无法提出有效的计划,还取决于表中的数据量。为自己测试这两种选择,以选择最适合您的一种。
归档时间: |
|
查看次数: |
739 次 |
最近记录: |