sqlpostgresqlaggregateboolean

EVERY in SQL: Asserting a Condition Holds for the Whole Group

EVERY is the SQL-standard synonym for BOOL_AND: true when a condition holds for every counted row, NULL over an empty group.

3 min de cititReferencesql · postgresql · aggregate · boolean · clickhouse
Acest articol este momentan în limba rusă — traducerea în engleză este în curs.

EVERY — агрегат для правил вида «все строки группы прошли проверку». Все позиции заказа отгружены, все платежи положительные, все сотрудники отдела получают больше нуля. Вместо цикла в приложении база возвращает один булев флаг на группу.

Базовый синтаксис

Внутрь EVERY передаётся булево выражение. Если среди учитываемых строк все значения true, агрегат возвращает true; если есть хотя бы одно false — false.

SELECT dept, EVERY(salary > 0) AS all_paid
FROM employees
GROUP BY dept;

В примере отдел проходит проверку, только если у каждого сотрудника зарплата положительная. Один salary <= 0 делает результат false.

Логика похожа на большое AND по группе:

  • все true -> true;
  • есть false -> false;
  • нет ни одного известного true/false значения -> NULL.

Последний пункт важен: SQL-агрегат не возвращает true на пустой группе. Он возвращает неизвестность, и это нужно обрабатывать явно.

Валидационные роллапы

Самый полезный сценарий — свернуть детали в один флаг состояния. Например, понять, все ли заказы пользователя отгружены.

SELECT user_id,
       EVERY(status = 'shipped') AS fully_shipped,
       COUNT(*)                  AS line_items
FROM orders
GROUP BY user_id;

Один заказ с status <> 'shipped' превращает fully_shipped в false. Это удобно для дашбордов: вместо ручного обхода строк у вас один статус на пользователя.

EVERY хорошо работает в HAVING, когда нужны только группы, полностью прошедшие правило.

SELECT u.country, EVERY(o.amount > 0) AS all_positive
FROM users u
JOIN orders o ON o.user_id = u.id
GROUP BY u.country
HAVING EVERY(o.amount > 0);

Такой запрос одновременно считает флаг и отсекает нарушителей, не заставляя приложение повторять ту же проверку.

EVERY против BOOL_AND

В PostgreSQL EVERY(x) и BOOL_AND(x) функционально совпадают. Разница в стиле и аудитории.

-- identical results
SELECT EVERY(amount > 0)    FROM orders;
SELECT BOOL_AND(amount > 0) FROM orders;

EVERY ближе к стандарту SQL и хорошо читается как фраза. BOOL_AND явно показывает булеву операцию и удобен в паре с BOOL_OR. Для отчётных витрин можно выбрать EVERY; для системного SQL, где рядом есть «все / хотя бы один», часто понятнее BOOL_AND и BOOL_OR.

Обработка NULL

Главная грабля — EVERY игнорирует строки, где аргумент равен NULL. Это поведение агрегатов, а не ошибка.

SELECT EVERY(status = 'shipped') AS result
FROM orders
WHERE id IN (1, 2);  -- suppose row 2 has status IS NULL

Если status IS NULL, выражение status = 'shipped' тоже даёт NULL, и такая строка не участвует в расчёте. Если после исключения NULL-строк в группе ничего не осталось, EVERY вернёт NULL, а не true.

Это может сделать отчёт обманчиво зелёным: неизвестные статусы не проваливают проверку. Если NULL должен считаться нарушением, сводите его к ложному значению до агрегирования.

SELECT EVERY(COALESCE(status, 'pending') = 'shipped') AS strict
FROM orders
GROUP BY user_id;

Либо оборачивайте сам агрегат в COALESCE, если хотите трактовать полностью пустую группу как провал.

MySQL и ClickHouse

Стандартный EVERY есть не везде. PostgreSQL поддерживает и EVERY, и BOOL_AND. В MySQL их нет, но булево условие является числом, поэтому эквивалент строится через MIN: минимум равен 1 только если все строки истинны.

-- MySQL: equivalent of EVERY(amount > 0)
SELECT user_id, MIN(amount > 0) = 1 AS all_positive
FROM orders
GROUP BY user_id;

В ClickHouse используют тот же принцип через min(cond); иногда встречается minIf, если условие отбора нужно отделить от проверяемого выражения. Смысл остаётся тем же: свести группу к одному флагу.

Запомните: EVERY означает «все известные значения истинны». Для строгой валидации данных всегда решайте судьбу NULL явно — через COALESCE в аргументе или вокруг результата. EVERY особенно выразителен в доменных правилах. Все строки заказа должны быть положительными, все документы клиента должны быть проверены, все события батча должны иметь один и тот же tenant. Такой запрос читается почти как спецификация, и это его сильная сторона: ревьюер видит правило без расшифровки счётчиков и сравнений.

Однако для строгих проверок почти всегда нужно отдельно решить судьбу отсутствующих данных. Если NULL означает «ещё не проверили», агрегат по умолчанию может скрыть проблему. Если NULL означает «не применимо», игнорирование может быть правильным. Поэтому в серьёзных валидациях полезно выводить две метрики рядом: EVERY по нормализованному условию и количество строк с неизвестным значением. Тогда зелёный флаг не маскирует дырки в данных. В доменной модели EVERY хорошо смотрится как финальный слой проверки, но не как единственная диагностика. Флаг сообщает, что правило выполнено или нарушено, но не показывает, какая строка виновата. Поэтому для эксплуатации рядом часто держат drill-down запрос: он возвращает строки, где условие ложно или неизвестно. Флаг нужен дашборду, детали нужны инженеру, который будет чинить данные. Если правило критично для целостности, лучше не ограничиваться отчётным EVERY, а закрепить инвариант ближе к данным: ограничением, внешним ключом, проверкой загрузчика или отдельной таблицей нарушений. EVERY удобен для диагностики и агрегации состояния, но не должен быть единственной защитой от плохих данных.

Exersează pe probleme reale

Rezolvă probleme în antrenorul SQL cu notare instantanee și indicii.

Deschide antrenorul