我有两个MySQL(MyIsAm)表,代表租赁单位和预订:
ID
,Name
等等......)ID
,F_LU_ID
,Start
,End
) F_LU_ID
是单位的外键。
搜索特定时间段内可用单位的最佳方法是什么?搜索传递开始,结束和持续时间。
我有兴趣知道是否甚至可以在MySQL中执行此操作,但如果不是,那么最好的方法是在PHP中执行此操作。
示例
在回答下面的答案时,我觉得一个例子可以帮助解释这个问题。
一个LettingUnit:
一些LettingUnitBookings:
如果我们搜索:
然后我们希望单位出现。因为在搜索范围内可以预订5天。
如果持续时间为10,那么该单位将不会显示,因为搜索范围内没有10个连续的未预订天数。
答案 0 :(得分:2)
这是一个似乎有效的解决方案:
SELECT t.*, DATEDIFF(t.LatestAvailable, t.EarliestAvailable) AS LengthAvailable
FROM
(SELECT u.*,
COALESCE(b1.End, @StartOfWindow) AS EarliestAvailable,
COALESCE(b2.Start, @EndOfWindow) AS LatestAvailable
FROM LettingUnits u
LEFT OUTER JOIN LettingUnitBookings b1
ON (u.ID = b1.F_LU_ID AND b1.End BETWEEN @StartOfWindow AND @EndOfWindow)
LEFT OUTER JOIN LettingUnitBookings b2
ON (u.ID = b2.F_LU_ID AND b2.Start BETWEEN @StartOfWindow AND @EndOfWindow
AND b2.Start >= b1.End) -- edit: new term
) AS t
LEFT OUTER JOIN LettingUnitBookings x
ON (t.ID = x.F_LU_ID AND x.Start < t.LatestAvailable AND x.End > t.EarliestAvailable)
WHERE x.ID IS NULL AND DATEDIFF(t.LatestAvailable, t.EarliestAvailable) >= @WindowSize;
输出结果为:
+-----+-------------+-------------------+-----------------+-----------------+
| ID | Name | EarliestAvailable | LatestAvailable | LengthAvailable |
+-----+-------------+-------------------+-----------------+-----------------+
| 123 | Foo Cottage | 2009-01-05 | 2009-01-10 | 5 |
| 123 | Foo Cottage | 2009-01-20 | 2009-01-25 | 5 |
| 456 | Bar Cottage | 2009-01-20 | 2009-01-31 | 11 |
+-----+-------------+-------------------+-----------------+-----------------+
使用EXPLAIN
进行分析表明它很好地使用了索引:
+----+-------------+------------+--------+---------------+---------+---------+-------+------+-------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+--------+---------------+---------+---------+-------+------+-------------------------+
| 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 9 | Using where |
| 1 | PRIMARY | x | ref | F_LU_ID | F_LU_ID | 8 | t.ID | 2 | Using where; Not exists |
| 2 | DERIVED | u | system | NULL | NULL | NULL | NULL | 1 | |
| 2 | DERIVED | b1 | ref | F_LU_ID | F_LU_ID | 8 | const | 0 | |
| 2 | DERIVED | b2 | ref | F_LU_ID | F_LU_ID | 8 | const | 0 | |
+----+-------------+------------+--------+---------------+---------+---------+-------+------+-------------------------+
与@martin clayton的解决方案given的EXPLAIN
报告进行比较:
+----+--------------+---------------------+--------+---------------+---------+---------+------+------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------+---------------------+--------+---------------+---------+---------+------+------+---------------------------------+
| 1 | PRIMARY | lu | system | PRIMARY,ID | NULL | NULL | NULL | 1 | |
| 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 4 | Using where |
| 2 | DERIVED | <derived3> | ALL | NULL | NULL | NULL | NULL | 4 | Using temporary; Using filesort |
| 2 | DERIVED | <derived5> | ALL | NULL | NULL | NULL | NULL | 4 | Using where; Using join buffer |
| 5 | DERIVED | LettingUnitBookings | ALL | NULL | NULL | NULL | NULL | 3 | |
| 6 | UNION | LettingUnitBookings | index | NULL | F_LU_ID | 8 | NULL | 3 | Using index |
| NULL | UNION RESULT | <union5,6> | ALL | NULL | NULL | NULL | NULL | NULL | |
| 3 | DERIVED | LettingUnitBookings | ALL | NULL | NULL | NULL | NULL | 3 | |
| 4 | UNION | LettingUnitBookings | index | NULL | F_LU_ID | 8 | NULL | 3 | Using index |
| NULL | UNION RESULT | <union3,4> | ALL | NULL | NULL | NULL | NULL | NULL | |
+----+--------------+---------------------+--------+---------------+---------+---------+------+------+---------------------------------+
通常,您希望避免强制Using filesort
或Using temporary
的优化计划,因为这些是性能杀手。使用GROUP BY
的查询几乎肯定会导致这种优化,至少在MySQL中。
答案 1 :(得分:1)
它不漂亮。
F_LU_ID
WHERE
条件(开始,结束,持续时间)我忽略了包含没有预订的BookingUnits。
看起来像这样:
SELECT @StartOfWindow := '2009-01-01',
@EndOfWindow := '2009-02-01',
@WindowSize := 5
;
SELECT
lu.Name,
Slots.*
FROM (
SELECT
lub1.F_LU_ID,
DATE_ADD( MAX( lub2.date_time ), INTERVAL 1 DAY ) AS StartOfSlot,
DATE_SUB( lub1.date_time, INTERVAL 1 DAY ) AS EndOfSlot,
DATEDIFF( lub1.date_time, MAX( lub2.date_time ) ) - 1 AS AvailableDays
FROM
( SELECT F_LU_ID, Start AS date_time FROM LettingUnitBookings
UNION
SELECT F_LU_ID, CAST( '9999-12-31' AS DATE ) FROM LettingUnitBookings
) AS lub1,
( SELECT F_LU_ID, End AS date_time FROM LettingUnitBookings
UNION
SELECT F_LU_ID, CAST( '1000-01-01' AS DATE ) FROM LettingUnitBookings
) AS lub2
WHERE
lub2.date_time <= lub1.date_time
AND lub2.F_LU_ID = lub1.F_LU_ID
GROUP BY
lub1.F_LU_ID,
lub1.date_time
) Slots
JOIN LettingUnits lu
ON lu.ID = Slots.F_LU_ID
WHERE
Slots.AvailableDays >= @WindowSize
AND (
( DATEDIFF( Slots.EndOfSlot, @EndOfWindow ) >= @WindowSize
AND DATEDIFF( @StartOfWindow, Slots.StartOfSlot ) >= @WindowSize
)
OR
( DATEDIFF( @EndOfWindow, Slots.StartOfSlot ) >= @WindowSize
AND DATEDIFF( Slots.EndOfSlot, @StartOfWindow ) >= @WindowSize
)
)
给出
Name F_LU_ID StartOfSlot EndOfSlot AvailableDays
Foo Cottage 123 2009-01-06 2009-01-09 5
Foo Cottage 123 2009-01-21 2009-01-24 5
希望可以根据您的需要进行调整。
或者,如果预订可以在上次预订结束的同一天开始,您可以稍微调整一下......
SELECT
lu.Name,
Slots.*
FROM (
SELECT
lub1.F_LU_ID,
MAX( lub2.date_time ) AS StartOfSlot,
lub1.date_time AS EndOfSlot,
DATEDIFF( lub1.date_time, MAX( lub2.date_time )) AS AvailableDays
FROM
( SELECT F_LU_ID, Start AS date_time FROM LettingUnitBookings
UNION
SELECT F_LU_ID, CAST( '9999-12-31' AS DATE ) FROM LettingUnitBookings
) AS lub1,
( SELECT F_LU_ID, End AS date_time FROM LettingUnitBookings
UNION
SELECT F_LU_ID, CAST( '1000-01-01' AS DATE ) FROM LettingUnitBookings
) AS lub2
WHERE
lub2.date_time <= lub1.date_time
AND lub2.F_LU_ID = lub1.F_LU_ID
GROUP BY
lub1.F_LU_ID,
lub1.date_time
) Slots
JOIN LettingUnits lu
ON lu.ID = Slots.F_LU_ID
WHERE
Slots.AvailableDays >= @WindowSize
AND
( DATEDIFF( Slots.EndOfSlot, @EndOfWindow ) >= @WindowSize
AND DATEDIFF( @StartOfWindow, Slots.StartOfSlot ) >= @WindowSize
)
OR
( DATEDIFF( @EndOfWindow, Slots.StartOfSlot ) >= @WindowSize
AND DATEDIFF( Slots.EndOfSlot, @StartOfWindow ) >= @WindowSize
)