🐘 Руководство по SQL для начинающих. Часть 2: фильтрация данных, запрос внутри запроса, работа с массивами
Во второй части статьи узнаете, как фильтровать данные, работать с множествами и какие бывают функции для работы с массивами.
Фильтрация данных с помощью операторов WHERE и HAVING
Скорее всего, вы уже знакомы с оператором для фильтрации данных WHERE
и, возможно, слышали об операторе HAVING
. Но чем же конкретно они отличаются? Давайте сделаем несколько запросов к таблице успеваемости (grades
), чтобы в этом разобраться. Воспользуемся оператором ORDER BY RANDOM()
, чтобы выбрать произвольные данные, затем LIMIT 5
, чтобы запрос выдал только 5 записей. Упорядочивать все строки только ради примера достаточно неэффективно, но если таблица небольшая – это допустимо.
Каждая строка отображает оценку ученика по определенному предмету. Давайте узнаем средний балл каждого ученика. Для этого нам понадобится:
GROUP BY
– для группировки по ученикам.AVG(score)
– для вычисления среднего значения.ROUND
– для округления полученных значений.
Теперь давайте представим, что из предыдущего запроса, нам нужны только те строки, где средний балл (avg_score
) больше, чем 50 и меньше, чем 75. То есть запрос должен отобразить только ученика со student_id=2
. Что произойдет при использовании оператора WHERE
?
Результаты выглядят совершенно неверными. Ученик с id=5
не отображается в результате запроса, а ученики с id
1, 3 и 4 на месте. К тому же их средний балл (avg_score
) изменился. А что если бы это были данные какого-нибудь важного отчета? Есть вероятность как минимум растеряться.
Давайте вспомним, что оператор HAVING
является агрегирующей функцией. Такие функции обрабатывают набор строк для подсчета и возвращения одного значения.
Теперь посмотрим, что изменится при использовании оператора HAVING
.
Эти два запроса выдают совершенно разные результаты, потому что операторы WHERE
и HAVING
фильтруют данные на разных этапах агрегации. WHERE
обрабатывает данные перед агрегацией, а HAVING
после, и фильтрует уже результаты.
Результат агрегации в запросе с оператором WHERE
изменился, потому что мы изменили входные данные для подсчета среднего балла каждого ученика. У ученика с id=5
нет оценок в диапазоне с 50 по 75, поэтому он был исключен из запроса. В то время как оператор HAVING
отфильтровал результаты уже после подсчета.
Когда вы освоите применение операторов WHERE
и HAVING
по отдельности, можете попробовать использовать их вместе. Например: мы хотим отобразить учеников, чей средний балл только по домашним работам не меньше 50 и не больше 75 баллов.
Условные операторы: CASE WHEN и COALESCE
Иногда, к данным в колонке нужно применить некое условие наподобие if-else
. Возможно у вас есть таблица для прогнозирования поведения модели и вам нужно вывести данные в виде положительных и отрицательных значений, основываясь на каком-то условии.
На примере нашей базы данных давайте представим, что нам нужно вывести оценки не в виде цифр, а в виде букв. Это легко делается с помощью оператора CASE WHEN
.
Логика, которую мы передаем в блок CASE WHEN
может охватывать сразу несколько колонок. Давайте выведем в результат запроса колонку instructor
, за основу возьмем таблицу students
. Например, если ученику назначен учитель, то в эту колонку запишем имя учителя, если нет, то имя ученика.
Если мы работаем с данными, которые могут не иметь значения, то есть являются null
, оператор COALESCE
– лучший выбор. COALESCE
– проверка на null
, то есть при передаче в него параметров null
, он вернет первое значение, не являющееся null
. Перепишем предыдущий запрос.
Четвертая строка в этом запросе, делает то же самое, что и строки с четвертой по седьмую в предыдущем. То есть, если значение в колонке teacher
не null
, возвращаем имя учителя, если null
, возвращаем имя ученика.
COALESCE
будет пропускать все переданные ему аргументы, пока не дойдет до элемента со значением не null
. Если же все аргументы имеют значение null
, то в возвращаемом значении тоже будет null
.
И наконец, в Postgres есть еще оператор условия IF
, однако он используется для управления несколькими запросами сразу, но не одним. Даже если вы data scientist или data engineer маловероятно, что вы будете им пользоваться. Если вы хотите освоить работу с данным оператором, попробуйте воспользоваться платформой Airflow.
Операции над множествами: UNION, INTERSECT, и EXCEPT
При использовании оператора JOIN
мы к одной таблице горизонтально присоединяем другую таблицу. В запросе ниже видно, мы получили данные об ученике с именем Adam
из трех таблиц students
, grades
и assignments
. В качестве ключа для связки таблиц использовали поле id
.
В большинстве случаев использование оператора JOIN
полностью покрывает наши потребности. Но что если мы захотим объединить таблицы вертикально. Например, у нас есть таблица и снизу к ней мы хотим добавить другую таблицу.
Давайте представим, что с системой успеваемости в нашей школе произошли очень странные изменения. Теперь для определения того, сдал ученик экзамен или нет используются не оценки. Ученик сдаст экзамен если: его имя начинается с буквы А или В или же в его имени только 5 букв. Мы можем написать 2 запроса, которые покажут нам всех учеников, подходящих под каждое условие, а затем используем оператор UNION ALL
, чтобы объединить результаты в одну таблицу.
Здесь, на строках с 4 по 11 и с 18 по 24 мы впервые увидели вложенные запросы (или подзапросы). Обратите внимание, что таким запросам необходимо присваивать имена (в нашем случае x и y) чтобы работал оператор UNION ALL
.
Также вы могли обратить внимание, что мы использовали оператор UNION ALL
, а не просто UNION
. Дело в том, что UNION ALL
возвращает все строки, в то время как UNION
убирает дубли. То есть если в двух подзапросах будут две одинаковые строки, UNION
покажет в результатах только одну. Именно для этого запроса ничего не изменится, потому что ученик Betty
удовлетворяет обоим условиям, а вот если не выводить колонку reason в результате мы увидим имя Betty
только один раз.
При выборе UNION
или UNION ALL
подумайте, нужны ли вам повторяющиеся значения. При написании сложных запросов я предпочитаю использовать UNION ALL
, чтобы убедиться, что в результирующей таблице то количество строк, которое нужно, и, если есть дубли, значит, где-то раньше я, скорее, всего ошибся с соединениями таблиц (JOIN
). Чем раньше вы будете фильтровать данные в запросе, тем эффективнее он будет работать.
Операторы UNION
и UNION ALL
возвращают все строки из подзапросов (в случае с UNION
без дублей). Два других оператора INTERSECT
и EXCEPT
, позволяют нам вернуть только те строки, которые соответствуют определенным критериям. INTERSECT
(пересечение) вернет только те строки, которые присутствуют в обоих запросах, а EXCEPT
(исключение) вернет строки из подзапроса А, которых нет в подзапросе Б.
В следующем запросе попробуем работу оператора INTERSECT
, который отобразит строки, присутствующие в обоих подзапросах (id 2
и id 3
). В отличие от UNION
нам не нужно присваивать имена вложенным запросам.
А теперь изменим запрос и применим оператор EXCEPT
, который выведет строки из подзапроса А, которых нет в подзапросе Б (в нашем случае строка с id=1
).
Операции над множествами дают нам возможность комбинировать результаты запросов (UNION
), просматривать пересекающиеся записи (INTERSECT
) и извлекать отличающиеся данные (EXCEPT
). Больше не нужно сравнивать результаты запросов вручную.
Функции для работы с массивами
Данные в реляционных базах обычно являются атомарными, то есть одна ячейка содержит только одно значение (например, только одна оценка в строке таблицы grades
). Но иногда, может быть полезно хранить данные в виде массива. Для таких случаев Postgres предоставляет большой набор функций, которые позволяют управлять массивами.
Одна из полезных функций ARRAY_AGG
позволяет преобразовать строки в массив. В следующем запросе мы написали ARRAY_AGG(score)
и использовали группировку по имени (GROUP BY name
) чтобы отобразить массив, включающий в себя все оценки каждого ученика.
Также в нашем арсенале есть следующие функции:
CARDINALITY
– выводит количество элементов в массиве.ARRAY_REPLACE
- заменяет указанные элементы.ARRAY_REMOVE
- удаляет указанные элемент.
UNNEST
– еще одна функция, которая может вам пригодиться. Её действие противоположно функции ARRAY_AGG
, то есть она позволяет разделить массив на отдельные строки.
В этой части статьи мы с вами разобрали:
- фильтрацию данных с помощью операторов
WHERE
иHAVING
; - условные операторы
CASE WHEN
иCOALESCE
; - операции над множествами;
- функции для работы с массивами.
В финальной части статьи разберем:
- присоединение таблицы к самой себе (
self join
); - оконные функции (
window function
); - посмотрим вглубь запросов (
explain
).
Материалы по теме
- 🐘 8 лучших GUI клиентов PostgreSQL в 2021 году
- 🐍🐬 Python и MySQL: практическое введение
- 🐍🗄️ Управление данными с помощью Python, SQLite и SQLAlchemy