Отредактировано: окончательный ответ внизу.
Почему многие вопросы SQL забывают имя таблицы?
-- Buggy: should reference (lo.max + 1)
SELECT lo.max + 1 AS min_range
FROM example lo, example hi
WHERE hi.min - (lo.max - 1) >= 40 -- Example won't work with 50
AND NOT EXISTS (SELECT * FROM example AS mid
WHERE mid.min > lo.max
AND mid.max < hi.min
)
Условие NOT EXISTS имеет решающее значение - оно гарантирует, что вы рассматриваете только смежные диапазоны.
Это касается случая "достаточно большой разрыв".
Номинально, вы можете справиться с «нет достаточно большого промежутка» с предложением UNION:
...
UNION
SELECT MAX(max)+1
FROM example
WHERE NOT EXISTS(
SELECT lo.max + 1 AS min_range
FROM example lo, example hi
WHERE hi.min - (lo.max - 1) >= 40 -- Example won't work with 50
AND NOT EXISTS (SELECT * FROM example AS mid
WHERE mid.min > lo.max
AND mid.max < hi.min
)
)
Внутренний SELECT является прямой транскрипцией первого, с отступом.
SQL выше не был проверен. Первая часть работает (особенно на тестовых данных) - но может дать несколько ответов. Таким образом, его необходимо изменить (исправляя, как мне кажется, ошибку «вдвое больше»):
SELECT MIN(lo.max + 1) AS min_range
FROM example lo, example hi
WHERE hi.min - (lo.max + 1) >= 40 -- Example won't work with 50
AND NOT EXISTS (SELECT * FROM example AS mid
WHERE mid.min > lo.max
AND mid.max < hi.min
)
Предложение UNION вызывает у меня некоторое горе ... не дает ожидаемого ответа.
Синтаксически, мне пришлось изменить его на:
SELECT MIN(lo.max + 1) AS min_range
FROM example lo, example hi
WHERE hi.min - (lo.max + 1) >= 40 -- Example won't work with 50
AND NOT EXISTS (SELECT * FROM example AS mid
WHERE mid.min > lo.max
AND mid.max < hi.min
)
UNION
SELECT MAX(solo.max)+1
FROM example AS solo
WHERE NOT EXISTS(
SELECT MIN(lo.max + 1) AS min_range
FROM example lo, example hi
WHERE hi.min - (lo.max - 1) >= 40 -- Example won't work with 50
AND NOT EXISTS (SELECT * FROM example AS mid
WHERE mid.min > lo.max
AND mid.max < hi.min
)
)
Это позволяет обойти проблемы с ключевым словом MAX, используемым в качестве имени столбца (возможно, я мог бы написать example.max
вместо solo.max
. Но это не дает мне ожидаемого ответа.
UNION эквивалентен OR, конечно, в этом случае, и этот запрос, кажется, дает ответ, который я хочу:
SELECT MIN(lo.max + 1) AS min_range
FROM example lo, example hi
WHERE (hi.min - (lo.max + 1) >= 40
AND NOT EXISTS (SELECT * FROM example AS mid
WHERE mid.min > lo.max
AND mid.max < hi.min
)
)
OR lo.max = (SELECT MAX(solo.max) FROM Example AS Solo)
;
Крайне важно, чтобы в предложении ИЛИ указывалось lo.max
, а не hi.max
; в противном случае вы получите неправильный ответ.
ОК - версия UNION обречена, потому что SQL неправильно определяет поведение MIN. В частности, если нет соответствующих строк, то MIN возвращает одну строку со значением NULL, а не возвращает никаких строк. Это означает, что первое предложение UNION возвращает NULL, если не найдено ни одной строки; второе предложение можно «исправить», пропустив MIN из SELECT внутри NOT EXISTS, но вы все равно получите две строки (NULL и правильное значение) из оператора, что на самом деле неприемлемо. Таким образом, используется версия OR - и SQL снова кусается со значениями NULL.
Строго избегать нулей можно, обрамляя UNION в табличном выражении в предложении FROM. В итоге это будет немного проще:
SELECT MIN(min_range)
FROM (SELECT (lo.max + 1) AS min_range
FROM example lo, example hi
WHERE hi.min - (lo.max + 1) >= 49
AND NOT EXISTS (SELECT * FROM example AS mid
WHERE mid.min > lo.max
AND mid.max < hi.min
)
UNION
SELECT MAX(solo.max + 1) AS min_range
FROM example AS solo
);
Первая половина UNION может вернуть любое количество слотов, включая ноль; вторая всегда возвращает значение (при условии, что в таблице есть какие-либо строки). Внешний запрос затем выбирает самое низкое из этих значений.
Эта версия, конечно, может использоваться для выделения строк:
INSERT INTO Example(min, max)
SELECT MIN(min_range) AS min, MIN(min_range) + (50 - 1) AS max
FROM (SELECT (lo.max + 1) AS min_range
FROM example lo, example hi
WHERE hi.min - (lo.max + 1) >= 50
AND NOT EXISTS (SELECT * FROM example mid
WHERE mid.min > lo.max
AND mid.max < hi.min
)
UNION
SELECT MAX(solo.max + 1) AS min_range
FROM example AS solo
);