Database Programming & Design
859c2d4a

HAVING без GROUP BY (I)


Хорошо известным (и малопонятным) фактом является то, что запросы на языке SQL могут включать раздел HAVING без соответствующего раздела GROUP BY. Рассмотрим, например, следующий запрос:

Q8: Выдать общий объем поставок для всех деталей, если и только если минимальный объем поставки каждой детали больше 50. На языке SQL возможна следующая формулировка:

SELECT SUM(SP.QTY) AS TQY FROM SP HAVING MIN(SP.QTY) > 50 ;

Если база данных содержит значения, приведенные в первой части заметки, результатом запроса будет таблица с одним столбцом с именем TQY и одной строкой, содержащей значение 3100.

Пояснение: Поскольку раздел GROUP BY отсутствует, то раздел HAVING применяется к "сгруппированной" версии SP, содержащей ровно одну группу. Если условие раздела HAVING вычисляется в true для этой группы (а так оно и есть для базы данных наших примеров), то раздел SELECT возвращает требуемую сумму значений. Если же при вычислении условия HAVING было бы получено false, то группа была бы отвергнута и окончательный результат был бы пустым. (Более точно, результатом была бы таблица с одним столбцом, не содержащая ни одной строки.)

Можно ли сформулировать запрос без использования HAVING? Очевидная попытка (с использованием "преобразования Типа 2") дала бы следующий результат:

SELECT DISTINCT SUM(SP.QTY) AS TQY FROM SP WHERE (SELECT MIN(SP.QTY) FROM SP) > 50 ;

Но, к сожалению, эта формулировка не является логически эквивалентной предыдущей. Чтобы убедиться в этом, предположим, что минимальный объем поставки должен быть больше 500. Тогда при выполнении запроса в первой формулировке будет произведена таблица с одним столбцом и без единой строки. В отличие от этого, при выполнении запроса без раздела HAVING результирующая таблица будет состоять из одного столбца и одной строки (содержащей неопределенное значение), поскольку условие раздела WHERE вычисляется в false для каждой строки SP, и поэтому раздел SELECT будет вычисляться для пустой таблицы.

Замечание: неопределенное значение с строке результата появляется в результате некорректной спецификации SQL, в соответствии с которой значение SUM для пустого множества есть NULL (а должно было бы быть нулевым).


Но формулировка, эквивалентная исходной и не включающая HAVING, все- таки существует. Она немного более хитрая:

SELECT DISTINCT ( SELECT SUM(SP.QTY) FROM SP) AS TQY FROM SP WHERE (SELECT MIN(SP.QTY) FROM SP) > 50 ;

При выполнении запроса в этой формулировке, если (внешние) разделы FROM и WHERE совместно производят пустую таблицу, то таким будет и результат всего запроса. Причина состоит в том, что единственный элемент, указанный в разделе SELECT является не ссылкой на агрегатную функцию SUM, а скалярным выражением, содержащим такую ссылку. Мощность окончательного результата (т.е. число строк в результирующей таблице) не зависит от вида этого скалярного выражения; можно было бы заменить (SELECT SUM ...) на SP.P#, на SP.QTY или даже на литерал. Более детально, происходит следующее:


  • Предположим, что условие раздела WHERE вычисляется в false для каждой строки SP.
  • Тогда разделы FROM и WHERE совместно производят пустую таблицу (без строк).
  • Подзапрос в разделе SELECT, конечно, возвращает значение 3100. (Более точно, он вырабатывает таблицу с одним столбцом и одной строкой, содержащей численное значение, но SQL извлекает это значение из таблицы. Здесь для нас это обстоятельство не слишком важно, но в SQL вообще оно вызывает проблемы.)
  • Итак, обсуждаемая формулировка запроса логически эквивалентна следующей:

    SELECT 3100 AS TQY FROM empty ;

    (empty именует пустую таблицу.) Очевидно, что результатом такого запроса является таблица с одним столбцом TQY и без строк.


Теперь мы можем сформулировать еще одно правило преобразования: Пусть имеется таблица R{A,B}, и agg1 и agg2 - агрегатные функции, применимые к R.A и R.B соответственно. Тогда выражение

SELECT agg1(R.A) AS C FROM R HAVING agg2(R.B) comp scalar ;

может быть логически преобразовано в эквивалентное выражение

SELECT DISTINCT ( SELECT agg1(R.A) FROM R ) AS C FROM R WHERE ( SELECT agg2(R.B) FROM R ) comp scalar ;

(Здесь comp - некоторый скалярный оператор сравнения, а scalar - некоторое скалярное выражение.)

Будем называть такие преобразования "преобразованиями Типа 3". Читателям рекомендуется самостоятельно разобрать случай, когда формулировка с HAVING включает раздел WHERE.


Содержание раздела