我维护了一个在线预订系统,由于我们试图找到的错误,该系统偶尔会包含重复的重叠预订。虽然我们正在这样做,但我已经收到一个查询,列出过去两个月的重叠预订,以便我们手动解决这些问题。
我的问题是这个查询需要花费很长时间(5分钟以上)来运行,并且预订系统会停止运行,但这样做会对我们的用户造成不利影响。所以我想提高它的表现。
下面是伪编码的相关模式。有两个关键表及其各自的列。
Bookings Accounts
ID : int ID : int
Status : bool Status : bool
StartTime : datetime Name : varchar
EndTime : datetime
RoomID : int
MemberID : int
AccountID : int
PK: ID PK: ID
Index: StartTime, EndTime,
MemberID, AccountID,
RoomID, Status
键都是简单的键(即没有复合键)。 Bookings.AccountID是Accounts.ID的外键。
查询大致是:
SELECT b1.AccountID, a.Name, b1.ID, b2.ID, b1.StartTime, b1.EndTime, b1.RoomID
FROM Bookings b1
LEFT JOIN Bookings b2
ON b1.MemberID = b2.MemberID
AND b1.RoomID = b2.RoomID
AND b2.StartTime > SUBDATE(NOW(), INTERVAL 2 MONTH))
LEFT JOIN Accounts a
ON b1.AccountId = a.ID
WHERE b1.ID != b2.ID
AND b1.Status = 1
AND b2.Status = 1
AND b1.StartTime > SUBDATE(NOW(), INTERVAL 2 MONTH))
AND (
(b1.StartTime >= b2.StartTime AND b2.EndTime <= b1.EndTime AND b1.StartTime < b2.EndTime) OR
(b1.StartTime <= b2.StartTime AND b2.EndTime >= b1.EndTime AND b2.StartTime < b1.EndTime) OR
(b2.StartTime <= b1.StartTime AND b2.EndTime >= b1.EndTime)
)
据我所知,查询基本上将预订表连接到自身(过去两个月),并试图消除不同的预订。也就是说,它会查找属于同一会员的有效(状态= 1)预订,这些预订的持续时间重叠。
最后三个条款寻找(a)从另一个开始的预订和之后的结束; (b)从另一方开始并在期间结束的预订; (c)完全包含在另一方内的预订。这似乎省略了(对于我的)一个完全在另一个周围的预订(虽然我不知道为什么)。
预订表非常大(约2米行),因为它有多年的预订数据。可以改进此查询的性能(或用更好的查询替换)吗?欢迎任何建议。
答案 0 :(得分:0)
我会像这样重写查询
SELECT sub.*, a.Name, a.id
from (
SELECT b1.AccountId, b1.ID, b2.ID, b1.StartTime, b1.EndTime, b1.RoomID
FROM (select SUBDATE(NOW(), INTERVAL 2 MONTH) as subDate) const, Bookings b1
LEFT JOIN Bookings b2
ON b1.MemberID = b2.MemberID
AND b1.RoomID = b2.RoomID
AND b2.StartTime > const.subDate
AND b1.ID != b2.ID
AND b2.Status = 1
WHERE
b1.Status = 1
AND b1.StartTime > const.subDate
AND (
(b1.StartTime >= b2.StartTime AND b2.EndTime <= b1.EndTime AND b1.StartTime < b2.EndTime) OR
(b1.StartTime <= b2.StartTime AND b2.EndTime >= b1.EndTime AND b2.StartTime < b1.EndTime) OR
(b2.StartTime <= b1.StartTime AND b2.EndTime >= b1.EndTime)
)
) sub
LEFT JOIN Accounts a ON
sub.AccountId = a.ID
更新:同时检查是否存在MemberID,RoomId,StartTime列的索引。如果没有这样的索引引入它们
答案 1 :(得分:0)
您没有说这是否像是酒店/租赁预订的电子商务网站,或类似于内部网站点,用于预订组织内的会议室,演讲厅等。我将假设它是前者,因为该站点的5分钟停机时间会很长,但对于后者,可能没那么大。
所以这是一个可以使用的启发式:用户不可能(但不是不可能)用户会在两个月内多次预订同一个房间。如果您在时间范围内选择所有房间ID和用户ID,结果中的重复行可能是双重预订,或者可能只是一个度假的人。
这是一种可以完成重复行检测的方法:
SELECT ID, StartTime, EndTime, RoomID, MemberID
FROM Bookings WHERE ID NOT IN
( SELECT t.ID FROM
(
SELECT count(ID) as c, ID
FROM Bookings
GROUP BY RoomID, MemberID
)
AS t WHERE t.c = 1 )
你也可以使用像这样的存储过程(伪代码):
DECLARE id, rid, mid, old_rid, old_mid INT;
DECLARE cur CURSOR FOR SELECT ID, RoomID, MemberID FROM Bookings ORDER BY RoomID, MemberID;
old_rid, old_mid = 0;
LOOP
/* check for break condition here */
FETCH cur into id, rid, mid;
IF rid == old_rid AND mid == old_mid
INSERT INTO temp_table VALUES (id);
END IF;
SET old_rid = rid;
SET old_mid = mid;
END LOOP;
然后,您将运行一个类似于原始查询的查询,并对结果进行StartTime / EndTime比较。
答案 2 :(得分:0)
基本上你正在寻找所有独特的预订。搜索所有重复项的速度更快,因为该列表应该更短:
DROP TABLE IF EXISTS duplicate_bookings;
CREATE TEMPORARY TABLE duplicate_bookings AS SELECT MAX(b1.ID) as last_bookings_id, b1.AccountID, b1.StartTime, b1.EndTime, b1.RoomID
FROM Bookings b1
GROUP BY b1.AccountID, b1.StartTime, b1.EndTime, b1.RoomID
HAVING COUNT(*)>1;
此查询选择所有重复的预订,并且(我)假设您要删除上次预订(MAX(b1.ID))
删除预订:
DELETE FROM bookings WHERE id IN (SELECT last_bookings_id FROM duplicate_bookings);
好处:您可以重复这是一个循环(在单个数据库会话中执行所有SQL,包括删除表duplicate_bookings),如果您有三次重复,等等。
为了防止新的重复并快速找到您的错误,并假设您正在使用innodb:添加一个唯一索引:
CREATE UNIQUE INDEX idx_nn_1 ON Bookings(AccountID, StartTime, EndTime,RoomID);
你只能在删除重复项后添加此索引。从那时起,新的重复插入将失败。
也可以帮助删除的临时索引是非唯一索引:
CREATE INDEX idx_nn_2 ON Bookings(AccountID, StartTime, EndTime,RoomID);
答案 3 :(得分:0)
此复合索引
INDEX(MemberID, RoomID, StartTime)
应该加快第一次加入。
这应该加快SELECT:
INDEX(Status, StartTime)
(不,在字段上有单独的INDEX是不一样的。)
对于重叠时间范围,请考虑以下紧凑形式:
WHERE a.start < b.end AND a.end > b.start
Status = 1
的含义是什么?表中有多少百分比1
?