Oto rozwiązanie, które wydaje się działać:
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;
Dane wyjściowe to:
+-----+-------------+-------------------+-----------------+-----------------+
| 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 |
+-----+-------------+-------------------+-----------------+-----------------+
Analizowanie tego za pomocą EXPLAIN
pokazuje, że całkiem dobrze wykorzystuje indeksy:
+----+-------------+------------+--------+---------------+---------+---------+-------+------+-------------------------+
| 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 | |
+----+-------------+------------+--------+---------------+---------+---------+-------+------+-------------------------+
Porównaj z EXPLAIN
raport dotyczący rozwiązania podane przez @martin clayton:
+----+--------------+---------------------+--------+---------------+---------+---------+------+------+---------------------------------+
| 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 | |
+----+--------------+---------------------+--------+---------------+---------+---------+------+------+---------------------------------+
Ogólnie rzecz biorąc, chcesz uniknąć planów optymalizacji, które wymuszają Using filesort
lub Using temporary
ponieważ to zabójcy wydajności. Zapytanie przy użyciu GROUP BY
jest prawie pewne, że spowoduje tego rodzaju optymalizację, przynajmniej w MySQL.