Опубликован: 10.10.2005 | Уровень: специалист | Доступ: платный | ВУЗ: Московский государственный университет имени М.В.Ломоносова
Лекция 5:

Группировка и условия раздела HAVING, порождаемые и соединенные таблицы

Предикат between
SELECT DEPT_NO, MIN(EMP_SAL), MAX(EMP_SAL)
FROM EMP
WHERE DEPT_NO IS NOT NULL
GROUP BY DEPT_NO
HAVING AVG(EMP_SAL) BETWEEN
	(SELECT AVG(EMP_SAL)
	FROM EMP) AND 30000.00;
15.5. Найти номера отделов и минимальный и максимальный размер зарплаты служащих для отделов, в которых средний размер зарплаты служащих не меньше среднего размера зарплаты служащих во всей компании и не больше 30000 руб.

Еще раз приведем возможную формулировку этого запроса без использования разделов GROUP BY и HAVING ( пример 15.5.1):

SELECT DISTINCT DEPT_NO, (SELECT MIN(EMP1.EMP_SAL)
	FROM EMP EMP1
	WHERE EMP1.DEPT_NO = EMP.DEPT_NO),
	(SELECT MAX(EMP1.EMP_SAL)
	FROM EMP EMP1
	WHERE EMP1.DEPT_NO = EMP.DEPT_NO)
FROM EMP
WHERE (SELECT AVG(EMP1.EMP_SAL)
	  FROM EMP EMP1
	  WHERE EMP1.DEPT_NO = EMP.DEPT_NO) BETWEEN
	(SELECT AVG(EMP_SAL)
	  FROM EMP) AND 30000.00;
15.5.1.

Как видно, отказ от использования раздела GROUP BY приводит к размножению однотипных подзапросов, строящих одну и ту же группу строк, над которой вычисляется агрегатная функция.

Предикат null
SELECT DEPT.DEPT_NO, COUNT(*)
FROM DEPT, EMP EMP1, EMP EMP2
WHERE DEPT.DEPT_NO = EMP2.DEPT_NO
 AND DEPT.DEPT_MNG = EMP1.EMP_NO
GROUP BY DEPT.DEPT_NO, EMP1.DEPT_NO
HAVING EMP1.DEPT_NO IS NULL;
15.6. Найти номера и число служащих отделов, данные о руководителях которых не содержат номер отдела (конечно, в этом случае нас интересуют только те отделы, у которых имеется руководитель).

Как и в примере 15.4, условие раздела HAVING можно переместить в раздел WHERE и получить вторую формулировку ( пример 15.6.1):

SELECT DEPT.DEPT_NO, COUNT(*)
FROM DEPT, EMP EMP1, EMP EMP2
WHERE DEPT.DEPT_NO = EMP2.DEPT_NO AND
	DEPT.DEPT_MNG = EMP1.EMP_NO AND
	EMP1.DEPT_NO IS NULL
GROUP BY DEPT.DEPT_NO;
15.6.1.

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

SELECT DEPT.DEPT_NO, (SELECT COUNT(*)
	FROM EMP
	WHERE DEPT.DEPT_NO = EMP.DEPT_NO)
FROM DEPT, EMP
WHERE DEPT.DEPT_MNG = EMP.EMP_NO AND
		EMP.DEPT_NO IS NULL;
15.6.2.
Предикат in
SELECT DEPT.DEPT_NO
FROM DEPT, EMP
WHERE DEPT.DEPT_NO = EMP.DEPT_NO
GROUP BY DEPT.DEPT_NO
HAVING AVG(EMP.EMP_SAL) IN
	(SELECT MAX(EMP1.EMP_SAL)
	  FROM EMP, DEPT DEPT1
	  WHERE EMP.DEPT_NO = DEPT1.DEPT_NO
	  AND DEPT1.DEPT_NO <> DEPT.DEPT_NO
	  GROUP BY DEPT.DEPT_NO);
15.7. Найти номера отделов, в которых средний размер зарплаты служащих равен максимальному размеру зарплаты служащих какого-либо другого отдела.

Этот запрос, помимо прочего, демонстрирует наличие в условии раздела HAVING вложенного подзапроса с корреляцией. Как и раньше, можно избавиться от разделов GROUP BY и HAVING во внешнем запросе ( пример 15.7.1):

SELECT DEPT.DEPT_NO
FROM DEPT
WHERE (SELECT AVG(EMP_SAL)
	FROM EMP
	WHERE EMP.DEPT_NO = DEPT.DEPT_NO) IN
	(SELECT MAX(EMP1.EMP_SAL)
	FROM EMP, DEPT DEPT1
	WHERE EMP.DEPT_NO = DEPT1.DEPT_NO
	  AND DEPT1.DEPT_NO <> DEPT.DEPT_NO
	GROUP BY DEPT.DEPT_NO);
15.7.1.

Но в данном случае мы не можем отказаться от раздела GROUP BY во втором вложенном запросе, поскольку без этого невозможно получить множество значений результатов вызова агрегатной функции.

Предикат like
SELECT EMP_NAME, COUNT(*)
FROM EMP, DEPT
WHERE EMP.DEPT_NO = DEPT.DEPT_NO 
GROUP BY DEPT.DEPT_NO, EMP_NAME
HAVING COUNT(*) > 1 
	AND EMP.EMP_NAME LIKE (SELECT EMP1.EMP_NAME
	  FROM EMP EMP1
	  WHERE EMP1.EMP_NO = DEPT.DEPT_MNG) || '%';
15.8. Во всех отделах найти имена и число служащих, у которых в данном отделе имеются однофамильцы и фамилии которых начинаются со строки символов, изображающей фамилию руководителя отдела.

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

SELECT EMP_NAME, (SELECT COUNT(*)
	FROM EMP EMP1
	WHERE EMP1.DEPT_NO = EMP.DEPT_NO
	  AND EMP1.EMP_NAME = EMP.EMP_NAME
	  AND EMP1.EMP_NO <> EMP.EMP_NO) + 1
FROM EMP
WHERE (SELECT COUNT(*)
	FROM EMP EMP1
	WHERE EMP1.DEPT_NO = EMP.DEPT_NO
	  AND EMP1.EMP_NAME = EMP.EMP_NAME
	  AND EMP1.EMP_NO <> EMP.EMP_NO) > 1 
 AND EMP_NAME LIKE (SELECT EMP1.EMP_NAME
	FROM EMP EMP1, DEPT
	WHERE EMP.DEPT_NO = DEPT.DEPT_NO
	AND EMP1.EMP_NO = DEPT.DEPT_MNG) || '%';
15.8.1.
Алексей Ковтун
Алексей Ковтун

При попытке исполнения запроса:

CREATE DOMAIN EMP_NO AS INTEGER

    CHECK (VALUE BETWEEN 1 AND 10000);

Выдается ошибка: Неизвестный тип объекта "DOMAIN" в интсрукции CREATE, DROP или ALTER. 

Используется SQL Server MS SQL 2008R2

Александра Каева
Александра Каева
Евгений Вершинин
Евгений Вершинин
Россия, Нижний Новгород, Нижегородский государственный технический университет, 2008
Aleksandr Arshinskyi
Aleksandr Arshinskyi
Россия