Получи случайную криптовалюту за регистрацию!

3 СПОСОБА ФИЛЬТРАЦИИ СТРОК ПЕРЕД АГРЕГИРОВАНИЕМ: SUBQUERY, COU | IT Resume

3 СПОСОБА ФИЛЬТРАЦИИ СТРОК ПЕРЕД АГРЕГИРОВАНИЕМ: SUBQUERY, COUNT + CASE, FILTER


ПРОЛОГ

Сгруппировать строки и посчитать какую-то метрику (например, сумму или среднее) - типичная операция в SQL. Мы знаем, что делается это с помощью оператора GROUP BY.

Однако иногда при расчетах нужно учитывать не все строки, а только удовлетворяющие некоторому условию. Давайте рассмотрим простейший пример.

Дана таблица users со столбцами:

* id
* name
* is_verified - подтвердил ли пользователь аккаунт (True/False)
* date_joined

Задача:

Посчитать количество подтвержденных пользователей, зарегистрировавшихся в сервисе в мае 2022 года, с разбивкой по дням.

* Кстати, работаем мы с PostgreSQL.



СПОСОБ 1. ПОДЗАПРОС.

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

В нашем случае запрос будет выглядеть так:

with filtered_users as (
select
id,
is_active,
to_char(date_joined, 'DD') as "day"
from users
where is_active is True
and to_char(date_joined, 'YYYY-MM') = '2022-05'
)
select
day,
count(*) as cnt
from filtered_users
group by day

Очевидные минусы такого подхода:

* На большом количестве строк подзапрос потенциально загрузит в память миллионы строк
* Запрос довольно громоздкий для той задачи, что поставлена перед нами



СПОСОБ 2. COUNT + CASE

Классический прием для решения таких задач - использование оператора CASE внутри агрегатных функций. Ответ можно записать в таком виде:

select
to_char(date_joined, 'DD') as "day",
count(case when is_active is True then 1 end) as cnt
from users
where to_char(date_joined, 'YYYY-MM') = '2022-05'
group by "day"

Видим, что запрос стал намного меньше + мы сразу же проводим все вычисления, не нагружая память.

«Фокус» в том, что внутри функции count мы считаем количество строк, которые удовлетворяют условию. Проверка условия осуществляется с помощью условного оператора CASE.




СПОСОБ 3. FILTER

Аналогичный результат агрегирования мы можем получить с помощью предложения FILTER. Механика действий у него аналогичная - на вход агрегатной функции count подаются только те строки, которые удовлетворяют условию фильтрации.

select
to_char(date_joined, 'DD') as "day",
count(*) filter(where is_active is True) as cnt
from users
where to_char(date_joined, 'YYYY-MM') = '2022-05'
group by "day"

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

- - - - - - - - - -

Освойте еще больше «фишечек» в нашем Симуляторе