Семинар 4. Сложные запросы, подзапросы. Целостность.
1. Сложные запросы и подзапросы
1.1. Структура запроса
Порядок написания запроса
SELECT
[ALL | DISTINCT [ON (expression [, ...] )] ]
[* | expression [AS output_name] [, ...] ]
[FROM from_item [, ...] ]
[WHERE condition]
[GROUP BY grouping_element [, ...]]
[HAVING condition]
[ORDER BY expression [ASC | DESC | USING operator] [NULLS {FIRST | LAST}]
[, ...]]
[LIMIT {count | ALL}]
[OFFSET start [ROW | ROWS]]
[FETCH {FIRST | NEXT} [count] {ROW | ROWS} ONLY]
1.2 Ключевое слово WITH
WITH
предоставляет способ записывать дополнительные операторы для применения в больших запросах.
Эти операторы, которые также называют общими табличными выражениями (Common Table Expressions, CTE),
можно представить как определения временных таблиц, существующих только для одного запроса.
Более подробно про СТЕ будет на следующих семинарах.
Пример:
WITH
regional_sales AS (
SELECT
region,
SUM(amount) AS total_sales
FROM
orders
GROUP BY
region
),
top_regions AS (
SELECT
region
FROM
regional_sales
WHERE
total_sales > (SELECT SUM(total_sales)/10 FROM regional_sales)
)
SELECT
region,
product,
SUM(quantity) AS product_units,
SUM(amount) AS product_sales
FROM
orders
WHERE
region IN (SELECT region FROM top_regions)
GROUP BY
region,
product;
1.3 Подзапросы
Подзапрос — запрос, содержащийся в другом SQL-запросе. Запрос, содержащий другой подзапрос, называется содержащим выражением.
- Подзапрос всегда заключен в круглые скобки и обычно выполняется до содержащего выражения.
- Подзапросы могут вкладываться друг в друга.
- В
SELECT’e
подзапросы можно использовать во всех разделах, кромеGROUP BY
.
Классификация подзапросов:
- По взаимодействию с содержащим выражением:
- Связанные (т.е. ссылающиеся на столбцы основного запроса):
- Для написания таких запросов полезно использование алиасов. (
SELECT … AS T
) - Для случаев, когда в основном запросе и в подзапросе используется одна и та же таблица, использование алиасов обязательно!
- Выполняются для каждой строки содержащего выражения.
- Для написания таких запросов полезно использование алиасов. (
- Несвязанные (т.е. полностью самодостаточные и не зависящие от основного запроса) — выполняются перед выполнением содержащего выражения.
- Связанные (т.е. ссылающиеся на столбцы основного запроса):
- По результату выполнения:
- Скалярные (1 столбец и 1 строка)
- Нескалярные
Использование подзапросов:
Примеры использования подзапросов в DELETE, UPDATE и INSERT:
DELETE FROM Classes
WHERE NOT EXISTS (SELECT * FROM Marks WHERE Marks.class_id = Classes.id);
UPDATE Classes
SET featured = 1
WHERE (SELECT count(*) FROM marks WHERE class_id = id) > 10;
INSERT INTO BestStudents2024(`name`, `surname`)
SELECT name, surname FROM Students
WHERE (SELECT AVG(mark) FROM Marks WHERE Students.id = Marks.student_id) > (SELECT AVG(mark) FROM Marks);
1.4 Предикаты (для подзапросов вида 1 столбец и несколько строк)
EXISTS
— Значением условияEXISTS
являетсяTRUE
в том и только в том случае, когда мощность таблицы-результата подзапроса больше нуля, иначе значением условия являетсяFALSE
:
SELECT SupplierName
FROM Suppliers
WHERE EXISTS(
SELECT ProductName
FROM Products
WHERE SupplierId = Suppliers.supplierId
AND Price < 20);
IN
— ПредикатIN
для подзапросов работает так же, как и для обычных запросов (проверка наличия значения в списке):
SELECT emp_id
, fname
, lname
, title
FROM employee
WHERE emp_id IN(
SELECT superior_emp_id
FROM employee);
ALL
—TRUE
, если результат подзапроса пуст или значение предиката равно TRUE для каждой строки подзапроса; если хотя бы что-тоFALSE
, то вернетFALSE
, во всех остальных случаях вернетUNKNOWN
:
SELECT EMP_NO
FROM EMP
WHERE DEPT_NO = 65
AND EMP_SAL >= ALL(
SELECT EMP1.EMP_SAL
FROM EMP AS EMP1
WHERE EMP.DEPT_NO = EMP1.DEPT_NO);
ANY
—FALSE
, если результат подзапроса пуст или значение условия равноFALSE
для каждой строки подзапроса; если хотя бы что-тоTRUE
, то вернетTRUE
, в остальных случаях вернетUNKNOWN
:
SELECT EMP_NO
FROM EMP
WHERE DEPT_NO = 65
AND EMP_SAL > ANY(
SELECT EMP1.EMP_SAL
FROM EMP AS EMP1
WHERE EMP.DEPT_NO = EMP1.DEPT_NO);
CREATE TABLE AS
— создаёт таблицу и наполняет её данными, полученными в результате выполненияSELECT
. Столбцы этой таблицы получают имена и типы данных в соответствии со столбцами результатаSELECT
(хотя имена столбцов можно переопределить, добавив явно список новых имен столбцов).
CREATE TABLE NEW_TABLE AS
SELECT *
FROM OLD_TABLE;
CREATE TABLE AS
напоминает создание представления, но на самом деле есть значительная разница: эта команда создает новую таблицу и выполняет запрос только раз, чтобы наполнить таблицу начальными данными. Последующие изменения в исходных таблицах запроса в новой таблице отражаться не будут. С представлением, напротив, определяющая его команда SELECT
выполняется при каждой выборке из него.
3. Практическое задание (сложные запросы + подзапросы)
- С помощью скалярного подзапроса найти имена преподавателей, которые получили разово минимальную выплату за все время.
- С помощью скалярного подзапроса найти имена преподавателей, у которых выплата по тому или иному типу была максимальной. Вывести имя преподавателя и тип выплаты, используя case или decode (1 – выплата за семинарские занятия, 2 – выплата за лекционные занятия, 3 – премиальная часть);
- С помощью подзапроса и предиката
IN
вывести имена преподавателей, тип и сумму выплат по каждому типу за все время работы, при условии, что у преподавателя была выплата за лекционные занятия. - С помощью
EXISTS
вывести имена преподавателей, тип и сумму выплат по каждому типу за все время работы, при условии, что у преподавателя была выплата за лекционные занятия; - С помощью аналитической функции
COUNT
найти количество выплат по каждому преподавателю за каждое число; выведите также размер каждой выплаты. - С помощью аналитической функции
SUM
найти общий размер выплат по каждому преподавателю за каждое число; выведите также размер каждой выплаты. - С помощью аналитической функции
SUM
и сортировки найти суммарные выплаты по каждому преподавателю за каждое число (по нарастанию).