Ecco una soluzione che sembra funzionare:
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;
L'output è:
+-----+-------------+-------------------+-----------------+-----------------+
| 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 |
+-----+-------------+-------------------+-----------------+-----------------+
Analizzandolo con EXPLAIN
mostra che utilizza abbastanza bene gli indici:
+----+-------------+------------+--------+---------------+---------+---------+-------+------+-------------------------+
| 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 | |
+----+-------------+------------+--------+---------------+---------+---------+-------+------+-------------------------+
Confronta con EXPLAIN
report per la soluzione data di @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 | |
+----+--------------+---------------------+--------+---------------+---------+---------+------+------+---------------------------------+
In generale, vuoi evitare i piani di ottimizzazione che forzano l'Using filesort
o Using temporary
perché questi sono assassini di prestazioni. Una query che utilizza GROUP BY
è quasi certo che causerà questo tipo di ottimizzazione, almeno in MySQL.