我必须拥有实体:postes
和bookings
。 poste
和booking
之间存在oneToMany关系:一个邮件可能有很多预订(在不同的日期)。
bookings
由4列定义:
booking_id
:id poste_id
:联合postes
表start_datetime
:开始日期预订number_day
:天数(整数) postes
由4列定义:
poste_id
:发布ID pattern
(字符串):定义了允许的日期(允许1天,不允许0天)。第八天定义为模式的第一天(模7)start
:发布开始日期(预订中的所有日期都包括在开始和结束之间)end
:发布结束日期 目标:我想定义一个查询,该查询选择未完全保留的所有postes
(例如,可能会有一些新的保留)。我被困住了,因为我无法选择免费日期范围内的任何数据,因为仅存储预订信息。
示例
Booking table
| booking_id | poste_id | start_datetime | number_day |
|------------|----------|----------------------|------------|
| 1 | 1 | 2019-07-10T00:00:00Z | 4 |
| 4 | 1 | 2019-07-14T00:00:00Z | 1 |
| 7 | 1 | 2019-07-16T00:00:00Z | 4 |
| 2 | 2 | 2019-07-10T00:00:00Z | 2 |
| 9 | 2 | 2019-07-13T00:00:00Z | 2 |
| 5 | 3 | 2019-07-15T00:00:00Z | 2 |
| 8 | 3 | 2019-07-21T00:00:00Z | 3 |
| 11 | 3 | 2019-07-28T00:00:00Z | 1 |
| 12 | 3 | 2019-07-29T00:00:00Z | 1 |
| 3 | 4 | 2019-07-15T00:00:00Z | 1 |
| 13 | 4 | 2019-07-21T00:00:00Z | 2 |
Postes table:
| poste_id | pattern | start | end |
|----------|---------|----------------------|----------------------|
| 1 | 1111101 | 2019-07-10T00:00:00Z | 2019-07-20T00:00:00Z |
| 2 | 1101101 | 2019-07-10T00:00:00Z | 2019-07-20T00:00:00Z |
| 3 | 1100001 | 2019-07-15T00:00:00Z | 2019-07-30T00:00:00Z |
| 4 | 1011001 | 2019-07-15T00:00:00Z | 2019-07-30T00:00:00Z |
该示例的预期输出为:2,4
。 (没有用于邮寄1
和3
的预订)。
注意:
poste
的开始日期和结束日期之间。可复制性:
// Build the tables:
CREATE TABLE bookings
(`booking_id` int, `poste_id` int, `start_datetime` datetime, `number_day` int)
;
INSERT INTO bookings
(`booking_id`, `poste_id`, `start_datetime`, `number_day`)
VALUES
(1, 1, '2019-07-10', '4'),
(4, 1, '2019-07-14', '1'),
(7, 1, '2019-07-16', '4'),
(2, 2, '2019-07-10', '2'),
(9, 2, '2019-07-13', '2'),
(5, 3, '2019-07-15', '2'),
(8, 3, '2019-07-21', '3'),
(11, 3, '2019-07-28', '1'),
(12, 3, '2019-07-29', '1'),
(3, 4, '2019-07-15', '1'),
(13, 4, '2019-07-21', '2')
;
CREATE TABLE postes
(`poste_id` int, `pattern` VARCHAR(7), `start` datetime, `end` datetime);
INSERT INTO postes VALUES
(1, "1111101", "2019-07-10", "2019-07-20"),
(2, "1101101", "2019-07-10", "2019-07-20"),
(3, "1100001", "2019-07-15", "2019-07-30"),
(4, "1011001", "2019-07-15", "2019-07-30");
我的工作:到目前为止,我设法在给定的一天中找到了可用的帖子:
SELECT DISTINCT p.*
FROM postes p
LEFT JOIN bookings b
ON b.poste_id = p.poste_id
WHERE
/* Ignore date in past */
MOD(DATEDIFF("2019-07-16", p.start), LENGTH(p.pattern)) >= -1
AND
/* Filter poste with pattern = 1 */
SUBSTRING(p.pattern, MOD(DATEDIFF("2019-07-16", p.start),
LENGTH(p.pattern)) + 1 , 1) = 1
AND
/* Filter those available this day */
p.poste_id NOT IN (
SELECT b.poste_id
FROM bookings b
WHERE b.start_datetime <= "2019-07-16"
AND "2019-07-16" < DATE_ADD(b.start_datetime, INTERVAL b.number_day DAY)
);
输出:
| poste_id | pattern | start | end |
|----------|---------|----------------------|----------------------|
| 2 | 1101101 | 2019-07-10T00:00:00Z | 2019-07-20T00:00:00Z |
答案 0 :(得分:1)
由于poste.pattern
,我看不到如何直接处理日期范围。可以通过加入一个表来扩展1天解决方案,该表在单独的行中列出了某个范围内的所有日期,可以通过以下方式生成:
...用该表中的"2019-07-16"
列替换_date
。
注意:对于此任务,使用编程语言可能比仅使用SQL的方法更具性能。
答案 1 :(得分:1)
(尚不完整的答案,但至少有一些提示...)
bookings
poste_id
或room_id
的第二列吗? (我猜“房间”对于英语来说是更好的词?)DATE
数据类型代替DATETIME
。 (此外,MySQL会在日期时间文字中使T
和Z
窒息。)VARCHAR(7)
代替TINYINT UNSIGNED
。这将使您可以使用布尔运算,移位运算和BIT_COUNT()
函数。 (请参阅http://api.mongodb.com/python/current/api/pymongo/database.html#pymongo.database.Database。)这些应该有助于所需的计算。poste.start
绑定?所以,对于8.0,我可能会
REPEAT
可以很方便地用于字符,但不能用于位。也许对字符字符串进行重复,然后转换为位。)end
日期之后砍掉位。BIT_COUNT()
,以查看该范围内有多少天。SUM(number_day)
获取保留天数。bookings
中没有任何“重叠”。(按照Aprillion的建议,我可能会用“真实的”编程语言编写代码。在上面的步骤在那里可能会有用。)
对于旧版本的MySQL和/或使用VARCHAR(7)
而不是TINYINT
,上述步骤可能会起作用,但有一些替代。例如,BIT_COUNT
可以替换为LENGTH(s) - LENGTH(REPLACE(s, '1', ''))
答案 2 :(得分:1)
这是SQL问题中一个非常常见的问题-如何匹配数据库中不存在的数据? SQL更好地匹配 存在的数据。
SQL的另一种特性是,它对行集更好地工作,而不是由开始和结束定义的虚范围。因此,我的策略是将范围转换为实际行集。
首先,创建一个包含所有日期的表格:
CREATE TABLE dates (date DATE PRIMARY KEY);
INSERT INTO dates SET date = '2019-07-01';
...
INSERT INTO dates SET date = '2019-07-30';
创建一个表格,列出任何帖子使用的所有日期:
CREATE TABLE poste_dates (
poste_id INT,
date DATE,
booking_id INT,
PRIMARY KEY (poste_id, date)
);
使用每个日期的所有日期填充该日期,介于日期范围的开始和结束之间。使用联接条件进行过滤,以使与您的模式相对应的星期几为“ 1”。
INSERT INTO poste_dates (poste_id, date)
SELECT poste_id, d.date FROM postes p JOIN dates d
ON SUBSTR(p.pattern, MOD(DATEDIFF(d.date, p.start), LENGTH(p.pattern))+1, 1)
WHERE d.date BETWEEN p.start AND p.end;
Query OK, 34 rows affected (0.01 sec)
现在您拥有所有帖子的所有日期:
+----------+------------+------------+
| poste_id | date | booking_id |
+----------+------------+------------+
| 1 | 2019-07-10 | NULL |
| 1 | 2019-07-11 | NULL |
| 1 | 2019-07-12 | NULL |
| 1 | 2019-07-13 | NULL |
| 1 | 2019-07-14 | NULL |
| 1 | 2019-07-17 | NULL |
| 1 | 2019-07-18 | NULL |
| 1 | 2019-07-19 | NULL |
| 1 | 2019-07-20 | NULL |
| 2 | 2019-07-10 | NULL |
| 2 | 2019-07-11 | NULL |
| 2 | 2019-07-13 | NULL |
| 2 | 2019-07-14 | NULL |
| 2 | 2019-07-17 | NULL |
| 2 | 2019-07-18 | NULL |
| 2 | 2019-07-20 | NULL |
| 3 | 2019-07-15 | NULL |
| 3 | 2019-07-16 | NULL |
| 3 | 2019-07-22 | NULL |
| 3 | 2019-07-23 | NULL |
| 3 | 2019-07-29 | NULL |
| 3 | 2019-07-30 | NULL |
| 4 | 2019-07-15 | NULL |
| 4 | 2019-07-17 | NULL |
| 4 | 2019-07-18 | NULL |
| 4 | 2019-07-22 | NULL |
| 4 | 2019-07-24 | NULL |
| 4 | 2019-07-25 | NULL |
| 4 | 2019-07-29 | NULL |
+----------+------------+------------+
对于每个预订,请使用UPDATE
在poste_dates
表中设置预订ID。在预订时长内使用LIMIT
。我们必须一次执行一次,因为在MySQL中,当LIMIT
拥有UPDATE
时JOIN
不起作用。
UPDATE poste_dates SET booking_id = 1 WHERE poste_id = 1 AND date >= '2019-07-10' ORDER BY date LIMIT 4;
UPDATE poste_dates SET booking_id = 4 WHERE poste_id = 1 AND date >= '2019-07-14' ORDER BY date LIMIT 1;
UPDATE poste_dates SET booking_id = 7 WHERE poste_id = 1 AND date >= '2019-07-16' ORDER BY date LIMIT 4;
UPDATE poste_dates SET booking_id = 2 WHERE poste_id = 2 AND date >= '2019-07-10' ORDER BY date LIMIT 2;
UPDATE poste_dates SET booking_id = 9 WHERE poste_id = 2 AND date >= '2019-07-13' ORDER BY date LIMIT 2;
UPDATE poste_dates SET booking_id = 5 WHERE poste_id = 3 AND date >= '2019-07-15' ORDER BY date LIMIT 2;
UPDATE poste_dates SET booking_id = 8 WHERE poste_id = 3 AND date >= '2019-07-21' ORDER BY date LIMIT 3;
UPDATE poste_dates SET booking_id = 11 WHERE poste_id = 3 AND date >= '2019-07-28' ORDER BY date LIMIT 1;
UPDATE poste_dates SET booking_id = 12 WHERE poste_id = 3 AND date >= '2019-07-29' ORDER BY date LIMIT 1;
UPDATE poste_dates SET booking_id = 3 WHERE poste_id = 4 AND date >= '2019-07-15' ORDER BY date LIMIT 1;
UPDATE poste_dates SET booking_id = 13 WHERE poste_id = 4 AND date >= '2019-07-21' ORDER BY date LIMIT 2;
现在日期如下:
+----------+------------+------------+
| poste_id | date | booking_id |
+----------+------------+------------+
| 1 | 2019-07-10 | 1 |
| 1 | 2019-07-11 | 1 |
| 1 | 2019-07-12 | 1 |
| 1 | 2019-07-13 | 1 |
| 1 | 2019-07-14 | 4 |
| 1 | 2019-07-16 | 7 |
| 1 | 2019-07-17 | 7 |
| 1 | 2019-07-18 | 7 |
| 1 | 2019-07-19 | 7 |
| 1 | 2019-07-20 | NULL |
| 2 | 2019-07-10 | 2 |
| 2 | 2019-07-11 | 2 |
| 2 | 2019-07-13 | 9 |
| 2 | 2019-07-14 | 9 |
| 2 | 2019-07-16 | NULL |
| 2 | 2019-07-17 | NULL |
| 2 | 2019-07-18 | NULL |
| 2 | 2019-07-20 | NULL |
| 3 | 2019-07-15 | 5 |
| 3 | 2019-07-16 | 5 |
| 3 | 2019-07-21 | 8 |
| 3 | 2019-07-22 | 8 |
| 3 | 2019-07-23 | 8 |
| 3 | 2019-07-28 | 11 |
| 3 | 2019-07-29 | 12 |
| 3 | 2019-07-30 | NULL |
| 4 | 2019-07-15 | 3 |
| 4 | 2019-07-17 | NULL |
| 4 | 2019-07-18 | NULL |
| 4 | 2019-07-21 | 13 |
| 4 | 2019-07-22 | 13 |
| 4 | 2019-07-24 | NULL |
| 4 | 2019-07-25 | NULL |
| 4 | 2019-07-28 | NULL |
| 4 | 2019-07-29 | NULL |
+----------+------------+------------+
现在,使用该表中的booking_id为NULL的表格搜索具有日期的所有帖子非常简单。
SELECT DISTINCT poste_id FROM poste_dates WHERE booking_id IS NULL;
这仍然与您发布2和4的预期结果不同。
答案 3 :(得分:1)
从8.0开始,您可以使用即时生成的数字表和LATERAL的少许帮助来完成此操作。您可能希望创建一个持久性数字表。
with e1(n) as (
select 1 union all select 1 union all select 1 union all
select 1 union all select 1 union all select 1 union all
select 1 union all select 1 union all select 1 union all select 1
), e2(n) as (select 1 from e1 a, e1 b), -- 100 rows
e4(n) as (select 1 from e2 a, e2 b), -- 10,000 rows
numbers(n) as (
select row_number() over(order by n) N from e4
)
select distinct poste_id, pattern, start, `end`
from postes p
join numbers n on adddate(start, n.N-1) <= `end`
-- compute the date and respective position in the pattern for further usage
, lateral (select adddate(start, n.N-1) dt, (n.N-1) % length(pattern) + 1 pos) x
where substring(pattern, x.pos, 1)
and not exists (
select 1
from bookings b
where b.poste_id = p.poste_id and x.dt >= b.start_datetime and x.dt <= adddate(b.start_datetime, b.number_day))
order by p.poste_id;