Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лабы базы Данных / Базы данных / Введение в модель данных SQL.doc
Скачиваний:
82
Добавлен:
22.03.2015
Размер:
1.93 Mб
Скачать

Логические выражения раздела having

Приведем примеры использования в логических выражениях раздела HAVING некоторых предикатов, обсуждавшихся в предыдущей лекции. Теоретически в этих логических выражениях можно использовать все предикаты, но применение тех предикатов, которые мы проиллюстрируем, является более естественным.

Предикаты сравнения

SELECT DEPT_NO

FROM EMP

WHERE DEPT_NO IS NOT NULL

GROUP BY DEPT_NO

HAVING COUNT(*) = 30;

Пример 15.1. Найти номера отделов, в которых работает ровно 30 сотрудников. (html,txt)

Конечно, этот запрос можно сформулировать и без использования разделов GROUP BY и HAVING. Например, возможна следующая формулировка (пример 15.1.1):

SELECT DISTINCT DEPT_NO

FROM EMP

WHERE (SELECT COUNT (*)

FROM EMP EMP1

WHERE EMP1.DEPT_NO = EMP.DEPT_NO) = 30;

Пример 15.1.1. (html,txt)

Обратите внимание, что в формулировке 15.1.1 отдельная проверка условия DEPT_NO IS NOT NULL не требуется.

SELECT DEPT_NO

FROM EMP

WHERE DEPT_NO IS NOT NULL

GROUP BY DEPT_NO

HAVING AVG(EMP_SAL) > 12000.00;

Пример 15.2. Найти номера всех отделов, в которых средний размер зарплаты сотрудников превосходит 12000 руб. (html,txt)

Очевидно, что и в этом случае возможна формулировка запроса без использования разделов GROUP BY и HAVING (пример 15.2.1):

SELECT DISTINCT DEPT_NO

FROM EMP

WHERE (SELECT AVG(EMP1.EMP_SAL)

FROM EMP EMP1

WHERE EMP1.DEPT_NO = EMP.DEPT_NO) > 12000.00;

Пример 15.2.1. (html,txt)

Немного задержимся на этих примерах и обсудим, что означает различие в формулировках запросов. В соответствии с семантикой оператора SELECT, при выполнении запросов 15.1.1 и 15.2.1 для каждой строки таблицы EMP в цикле просмотра внешнего запроса будет выполняться подзапрос, который в случае наших примеров выберет из таблицы EMP (EMP1) все строки со значением столбца DEPT_NO, равным значению этого столбца в текущей строке внешнего цикла. Другими словами, для каждой строки внешнего цикла образуется группа, для нее проверяется условие выборки, и в списке выборки используется имя столбца этой неявной группировки. Из-за того, что группа образуется и оценивается для каждой строки таблицы EMP, мы вынуждены указать в разделе SELECT спецификацию DISTINCT.

Формулировки 15.1 и 15.2 обеспечивают более четкие указания для выполнения запроса. Нужно сразу сгруппировать таблицу EMP в соответствии со значениями столбца DEPT_NO, отобрать нужные группы, и для каждой отобранной группы вычислить значения выражений списка выборки. В этом случае семантика выполнения запроса не предписывает выполнения лишних действий. Конечно, в развитой реализации SQL компилятор должен суметь понять, что формулировки 15.1.1 и 15.2.1 эквивалентны формулировкам 15.1 и 15.2 соответственно, и избежать выполнения лишних действий.

SELECT DEPT_NO

FROM EMP

WHERE DEPT_NO IS NOT NULL

GROUP BY DEPT_NO

HAVING SUM(EMP_SAL) < (SELECT SUM(EMP1.EMP_SAL)

FROM EMP EMP1, DEPT

WHERE EMP1.EMP_NO = DEPT_MNG);

Пример 15.3. Найти номера всех отделов, в которых суммарный объем зарплаты сотрудников меньше суммарного объема зарплаты всех руководителей отделов. (html,txt)

И в этом случае возможна формулировка без использования разделов GROUP BY и HAVING (пример 15.3.1). Эта формулировка является более сложной, чем в случае двух предыдущих примеров, но и к ней применимы приведенные выше замечания.

SELECT DISTINCT DEPT_NO

FROM EMP

WHERE (SELECT SUM(EMP1.EMP_SAL)

FROM EMP EMP1

WHERE EMP1.DEPT_NO = EMP.DEPT_NO) <

(SELECT SUM(EMP1.EMP_SAL)

FROM EMP EMP1, DEPT

WHERE EMP1.EMP_NO = DEPT_MNG);

Пример 15.3.1. (html, txt)

SELECT DEPT.DEPT_NO, EMP.EMP_NAME, COUNT(*),

MIN(EMP1.EMP_SAL), MAX(EMP1.EMP_SAL), AVG(EMP1.EMP_SAL)

FROM DEPT, EMP, EMP EMP1

WHERE DEPT.DEPT_NO = EMP1.DEPT_NO

GROUP BY DEPT.DEPT_NO, DEPT.DEPT_MNG, EMP.EMP_NO, EMP.EMP_NAME

HAVING DEPT.DEPT_MNG = EMP.EMP_NO;

Пример 15.4. Для каждого отдела найти его номер, имя руководителя, число сотрудников, минимальный, максимальный и средний размеры зарплаты сотрудников. (html,txt)

Этот запрос иллюстрирует несколько интересных особенностей языка SQL. Во-первых, это первый пример запроса с соединениями, в котором присутствуют разделы GROUP BY и HAVING. Во-вторых, одно условие соединения находится в разделе WHERE, а другое - в разделе HAVING. На самом деле, можно было бы перенести в раздел WHERE и второе условие соединения, и, скорее всего, на практике использовалась бы формулировка, приведенная в примере 15.4.1:

SELECT DEPT.DEPT_NO, EMP.EMP_NAME, COUNT(*),

MIN(EMP1.EMP_SAL), MAX(EMP1.EMP_SAL), AVG(EMP1.EMP_SAL)

FROM DEPT, EMP, EMP EMP1

WHERE DEPT.DEPT_NO = EMP1.DEPT_NO

AND DEPT.DEPT_MNG = EMP.EMP_NO

GROUP BY DEPT.DEPT_NO, EMP.EMP_NAME;

Пример 15.4.1. (html,txt)

Но первая формулировка тоже верна, поскольку второе условие соединения определено на столбцах группировки.

Наконец, легко видеть, что по существу группировка производится по значениям столбца DEPT.DEPT_NO. Остальные столбцы, указанные в списке столбцов группировки, функционально определяются столбцом DEPT.DEPT_NO. Тем не менее, в первой формулировке мы включили в этот список столбцы DEPT.DEPT_MNG и EMP.EMP_NO, чтобы их имена можно было использовать в условии раздела HAVING, и столбец EMP.EMP_NAME, чтобы можно было использовать его имя в списке выборки раздела SELECT. Другими словами, мы вынуждены расширять запрос избыточными данными, чтобы выполнить формальные синтаксические требования языка. Как видно, во второй формулировке мы смогли удалить из списка группировки два столбца. Кстати, не следует думать, что многословие первой формулировки помешает СУБД выполнить запрос настолько же эффективно, как запрос во второй формулировке. Грамотно построенный оптимизатор SQL сам приведет первую формулировку ко второй.

Наконец, и этот запрос можно сформулировать без использования раздела GROUP BY за счет использования подзапросов в списке раздела SELECT (пример 15.4.2):

SELECT DEPT.DEPT_NO, EMP.EMP_NAME,

(SELECT COUNT(*)

FROM EMP

WHERE EMP.DEPT_NO = DEPT.DEPT_NO),

(SELECT MIN(EMP_SAL)

FROM EMP

WHERE EMP.DEPT_NO = DEPT.DEPT_NO),

(SELECT MAX(EMP_SAL)

FROM EMP

WHERE EMP.DEPT_NO = DEPT.DEPT_NO),

(SELECT AVG(EMP_SAL)

FROM EMP

WHERE EMP.DEPT_NO = DEPT.DEPT_NO)

FROM DEPT, EMP

WHERE DEPT.DEPT_MNG = EMP.EMP_NO;

Пример 15.4.2. (html,txt)

Здесь мы снова имеем замаскированную группировку строк по значениям столбца DEPT.DEPT_NO и вычисление агрегатных функций для каждой группы. Формально группа строится каждый раз заново при вызове каждой агрегатной функции. Хороший компилятор SQL должен привести формулировку 15.4.2 к виду 15.4.1.

И последнее замечание. Во всех приведенных формулировках в результат не попадут данные об отделах, в которых отсутствует руководитель (столбец DEPT.DEPT_MNG может содержать неопределенное значение). Вообще говоря, это не противоречит условию запроса, но если бы мы хотели выдавать в результате NULL в качестве имени руководителя отдела с отсутствующим руководителем, то можно было немного усложнить формулировку запроса, например, следующим образом (пример 15.4.3):

SELECT DEPT.DEPT_NO,

CASE WHEN DEPT.DEPT_MNG IS NULL THEN NULL

ELSE (SELECT EMP.EMP_NAME

FROM EMP

WHERE EMP.EMP_NO = DEPT.DEPT_MNG),

COUNT(*), MIN(EMP1.EMP_SAL),

MAX(EMP1.EMP_SAL), AVG(EMP1.EMP_SAL)

FROM DEPT, EMP, EMP EMP1

WHERE DEPT.DEPT_NO = EMP1.DEPT_NO

GROUP BY DEPT.DEPT_NO;

Пример 15.4.3. (html,txt)

Соседние файлы в папке Базы данных