Раздел HAVING
Вот запрос, для формулировки которого большинство людей использовало бы раздел HAVING:
Q4: Для каждой детали, поставляемой более чем одним поставщиком, выдать номер детали.
Возможной формулировкой с использованием GHB могла бы быть следующая:
SELECT SP.P# FROM SP GROUP BY SP.P# HAVING COUNT(*) > 1 ;
Результатом такого запроса является таблица
P# ----- P1 P2 P4 P5
Вот формулировка без использования разделов GROUP BY и HAVING:
SELECT DISTINCT SP.P# FROM SP WHERE (SELECT COUNT(*) FROM SP AS SPX WHERE SPX.P# = SP.P#) > 1 ;
Снова эта формулировка длиннее варианта с GBH, но логически они эквивалентны. И опять легко обобщить пример. Если использовать обозначения из предыдущего раздела, то выражение
SELECT R.A FROM R GROUP BY R.A HAVING agg(R.B) comp scalar ;
(где comp является некоторой операцией скалярного сравнения, а scalar - некоторое скалярное выражение) может быть логически преобразовано к эквивалентному выражению
SELECT DISTINCT R.A FROM R WHERE (SELECT agg(R.B) FROM R AS RX WHERE RX.A = R.A) comp scalar ;
Будем называть такого типа преобразования преобразованиями Типа 2. Посмотрим, что произойдет, если исходная формулировка будет включать раздел WHERE.
Q5: Для каждой детали, поставляемой более чем одним поставщиком (кроме поставщика S1), выдать номер детали.
Формулировка с GBH:
SELECT SP.P# FROM SP WHERE SP.P# <> 'S1' GROUP BY SP.P# HAVING COUNT(*) > 1 ;
Формулировка без GBH лишь немного хитрее:
SELECT DISTINCT SP.P# FROM SP WHERE (SELECT COUNT(*) FROM SP AS SPX WHERE SPX.P# = SP.P# AND SPX.S# <> 'S1') > 1 ;
Пример показывает, что преобразование Типа 2 может быть легко обобщено для случая наличия раздела WHERE. Вот несколько более сложный пример:
Q6: Для каждой детали, поставляемой более чем одним поставщиком, выдать номер детали и общее число поставок этой детали.
Формулировка запроса с использованием GBH:
SELECT SP.P#, SUM(SP.QTY) AS TQY FROM SP GROUP BY SP.P# HAVING COUNT(*) > 1 ;
Применяя правила преобразований Типа 1 и 2, получим следующее:
SELECT DISTINCT SP.P#, (SELECT SUM(SPX.QTY) FROM SP AS SPX WHERE SPX.P# = SP.P#) AS TQY FROM SP WHERE (SELECT COUNT(*) FROM SP AS SPX WHERE SPX.P# = SP.P#) > 1 ;
И еще один пример:
Q7: Для каждой детали, поставляемой более чем одним поставщиком, выдать общее число поставок этой детали, но без номера детали.
Вот формулировка с применением GBH:
SELECT SUM(SP.QTY) AS TQY FROM SP GROUP BY SP.P# HAVING COUNT(*) > 1 ;
Преобразованный вариант:
SELECT (SELECT SUM(SPX.QTY) FROM SP AS SPX WHERE SPX.P# = SP.P#) AS TQY FROM SP WHERE (SELECT COUNT(*) FROM SP AS SPX WHERE SPX.P# = SP.P#) > 1 ;
В результате выполнения этих запросов были бы получены следующие результаты:
С GROUP BY Без GROUP BY и HAVING и HAVING
TQY TQY ----- ----- 600 600 1000 1000 500 500 500
Снова запросы производят разные результаты и потому не эквивалентны. Но эта ситуация аналогична той, которая обсуждалась по поводу примера Q3, и не требует дополнительных комментариев.