Как не ронять базу лишний раз. Используем FILTER для оконок.

Обычно для фильтрации в SQL используют WHERE. Как мы знаем из другой заметки, логический путь выполнения запроса такой: сначала берем данные, потом фильтруем, затем агрегируем.

Но что, если нам нужно взять все данные (то есть не использовать WHERE) и при этом сделать расчет только по определенным строкам? Нужно сочетать несочетаемое: видеть все строки, но считать не по всем.

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

Если мы просто сделаем COUNT(*) OVER(...), база будет считать всё подряд. Мы можем добавить PARTITION BY и посчитать количество продаж каждого вида, но:

  1. Нам не нужна лишняя информация по другим категориям.
  2. Что если лимитированных стульев 100 штук, а остальных товаров 2 миллиона? Посчитать 100 штук явно проще и быстрее, чем 2 млн и 100 штук.

Когда мы используем FILTER, база будет считать только там, где нужно:

COUNT(*) FILTER (WHERE furniture = 'chairs') OVER (ORDER BY date)

То есть сначала фильтруем, а потом считаем. В строках со стульями мы получим количество проданных экземпляров, а в остальных категориях — просто null. Это чище и понятнее, чем городить CASE WHEN внутри оконки.

Важно: работает не со всеми оконными функциями, а только с агрегацией. То есть row_number() так не сработает.

Синтаксис: aggregate_function() FILTER (WHERE conditions) OVER (PARTITION BY ... ORDER BY ...)

В некоторых СУБД, например в ClickHouse, есть альтернативы: sumIf(amount, furniture = 'chairs').

Связи: 📌 В другой заметке рассказывал, как использовать алиасы для оконок, чтобы сделать код чище: Псевдоним для окна (WINDOW) 📌 А здесь про то, в каком порядке база на самом деле читает запрос: Порядок выполнения запроса в SQL


В этом посте были ссылки, но мы их удалили по правилам Сетки