Опубликован: 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

Александра Каева
Александра Каева