Ovaj članak trenutno je na ruskom — engleski prijevod je u izradi.
BOOL_AND и BOOL_OR — это агрегаты PostgreSQL над булевым выражением: первый сворачивает группу в true, когда выражение истинно во всех строках, второй — когда хотя бы в одной. Их берут, чтобы прямо в SELECT спросить «все ли позиции заказа отгружены?» или «есть ли среди пользователей хотя бы один админ?», не выписывая громоздкие конструкции вида COUNT(*) = COUNT(...).
Что именно возвращают BOOL_AND и BOOL_OR
Обе функции принимают булево выражение и сворачивают группу в один флаг:
BOOL_AND(expr) возвращает true, если expr истинно во всех учитываемых строках;
BOOL_OR(expr) возвращает true, если expr истинно хотя бы в одной строке.
SELECT
BOOL_AND(active) AS all_active,
BOOL_OR(is_admin) AS has_admin
FROM users;
С GROUP BY это превращается в компактный отчёт по сегментам.
SELECT
country,
BOOL_AND(active) AS everyone_active,
BOOL_OR(is_admin) AS any_admin
FROM users
GROUP BY country;
Аргумент не обязан быть готовой колонкой-флагом. Подойдёт любое сравнение, например проверка минимальной суммы заказа.
SELECT
user_id,
BOOL_AND(amount >= 100) AS all_big_orders
FROM orders
GROUP BY user_id;
Такой запрос читается как бизнес-правило: для каждого пользователя все заказы должны быть не меньше порога.
NULL игнорируются (и это важно)
Как и большинство агрегатов, BOOL_AND и BOOL_OR пропускают строки, где выражение равно NULL. Они считают только известные true/false значения.
SELECT
user_id,
BOOL_AND(status = 'shipped') AS all_shipped
FROM orders
GROUP BY user_id;
Отсюда главная грабля: неизвестное не равно ложному. Один пустой статус не провалит BOOL_AND; он просто не попадёт в расчёт. Если все значения в группе неизвестны или строк после фильтра нет, результат будет NULL, а не true или false.
Запрос WHERE all_shipped = true молча отбросит такие группы, потому что сравнение с NULL не истинно. Если пустые статусы должны считаться нарушением, нормализуйте результат или аргумент явно: COALESCE(BOOL_AND(status = 'shipped'), false).
BOOL_AND против EVERY
EVERY — стандартный SQL-синоним для BOOL_AND в PostgreSQL. Агрегата ANY в такой роли нет: слово ANY занято синтаксисом подзапросов, поэтому для «хотя бы одной строки» используется BOOL_OR.
SELECT
dept,
EVERY(salary >= 50000) AS all_well_paid,
BOOL_OR(salary >= 200000) AS any_top_earner
FROM employees
GROUP BY dept;
Выбор имени — вопрос стиля. EVERY хорошо читается в отчётах, BOOL_AND лучше смотрится рядом с BOOL_OR, когда в коде явно нужна пара «все / хотя бы один». В HAVING булев агрегат особенно выразителен.
SELECT dept
FROM employees
GROUP BY dept
HAVING BOOL_AND(salary >= 50000);
Так можно сразу отобрать только те группы, где правило выполняется полностью.
Эмуляция в MySQL и ClickHouse
В MySQL нет готовых BOOL_AND и BOOL_OR, но булевы выражения возвращают 0 или 1. Поэтому MIN работает как AND по группе, а MAX — как OR.
SELECT
user_id,
MIN(amount >= 100) AS all_big_orders,
MAX(status = 'paid') AS any_paid
FROM orders
GROUP BY user_id;
Несколько отличий:
- в MySQL выражение
amount >= 100 уже числовое: 1 для true и 0 для false;
- в PostgreSQL для такого трюка нужен каст
::int, потому что MIN и MAX не применяют к boolean как к числу;
NULL в MIN/MAX тоже игнорируется, поэтому поведение по неизвестным значениям остаётся тем же;
- в ClickHouse обычно пишут
min(amount >= 100) и max(status = 'paid'); для nullable-сценариев смотрят на minOrNull/maxOrNull и тип UInt8.
Итог: в PostgreSQL для проверок «вся группа удовлетворяет условию» и «хотя бы одна строка удовлетворяет» берите BOOL_AND/BOOL_OR (или EVERY), помня про пропуск NULL и про NULL на полностью пустой группе. Заранее решите, что для вашей бизнес-логики значит неизвестное значение, и закрепите это решение через COALESCE, а не оставляйте на умолчания агрегата. В MySQL и ClickHouse тот же смысл собирается из MIN/MAX булева флага.
BOOL_ANDиBOOL_OR— это агрегаты PostgreSQL над булевым выражением: первый сворачивает группу вtrue, когда выражение истинно во всех строках, второй — когда хотя бы в одной. Их берут, чтобы прямо вSELECTспросить «все ли позиции заказа отгружены?» или «есть ли среди пользователей хотя бы один админ?», не выписывая громоздкие конструкции видаCOUNT(*) = COUNT(...).Что именно возвращают BOOL_AND и BOOL_OR
Обе функции принимают булево выражение и сворачивают группу в один флаг:
BOOL_AND(expr)возвращает true, еслиexprистинно во всех учитываемых строках;BOOL_OR(expr)возвращает true, еслиexprистинно хотя бы в одной строке.SELECT BOOL_AND(active) AS all_active, BOOL_OR(is_admin) AS has_admin FROM users;С
GROUP BYэто превращается в компактный отчёт по сегментам.SELECT country, BOOL_AND(active) AS everyone_active, BOOL_OR(is_admin) AS any_admin FROM users GROUP BY country;Аргумент не обязан быть готовой колонкой-флагом. Подойдёт любое сравнение, например проверка минимальной суммы заказа.
SELECT user_id, BOOL_AND(amount >= 100) AS all_big_orders FROM orders GROUP BY user_id;Такой запрос читается как бизнес-правило: для каждого пользователя все заказы должны быть не меньше порога.
NULL игнорируются (и это важно)
Как и большинство агрегатов,
BOOL_ANDиBOOL_ORпропускают строки, где выражение равноNULL. Они считают только известные true/false значения.-- a NULL status is ignored, not treated as 'not shipped' SELECT user_id, BOOL_AND(status = 'shipped') AS all_shipped FROM orders GROUP BY user_id;Отсюда главная грабля: неизвестное не равно ложному. Один пустой статус не провалит
BOOL_AND; он просто не попадёт в расчёт. Если все значения в группе неизвестны или строк после фильтра нет, результат будетNULL, а неtrueилиfalse.Запрос
WHERE all_shipped = trueмолча отбросит такие группы, потому что сравнение сNULLне истинно. Если пустые статусы должны считаться нарушением, нормализуйте результат или аргумент явно:COALESCE(BOOL_AND(status = 'shipped'), false).BOOL_AND против EVERY
EVERY— стандартный SQL-синоним дляBOOL_ANDв PostgreSQL. АгрегатаANYв такой роли нет: словоANYзанято синтаксисом подзапросов, поэтому для «хотя бы одной строки» используетсяBOOL_OR.-- EVERY is exactly BOOL_AND SELECT dept, EVERY(salary >= 50000) AS all_well_paid, BOOL_OR(salary >= 200000) AS any_top_earner FROM employees GROUP BY dept;Выбор имени — вопрос стиля.
EVERYхорошо читается в отчётах,BOOL_ANDлучше смотрится рядом сBOOL_OR, когда в коде явно нужна пара «все / хотя бы один». ВHAVINGбулев агрегат особенно выразителен.SELECT dept FROM employees GROUP BY dept HAVING BOOL_AND(salary >= 50000);Так можно сразу отобрать только те группы, где правило выполняется полностью.
Эмуляция в MySQL и ClickHouse
В MySQL нет готовых
BOOL_ANDиBOOL_OR, но булевы выражения возвращают0или1. ПоэтомуMINработает как AND по группе, аMAX— как OR.-- MySQL: MIN/MAX over a boolean expression cast to 0/1 SELECT user_id, MIN(amount >= 100) AS all_big_orders, -- like BOOL_AND MAX(status = 'paid') AS any_paid -- like BOOL_OR FROM orders GROUP BY user_id;Несколько отличий:
amount >= 100уже числовое:1для true и0для false;::int, потому чтоMINиMAXне применяют кbooleanкак к числу;NULLвMIN/MAXтоже игнорируется, поэтому поведение по неизвестным значениям остаётся тем же;min(amount >= 100)иmax(status = 'paid'); для nullable-сценариев смотрят наminOrNull/maxOrNullи типUInt8.Итог: в PostgreSQL для проверок «вся группа удовлетворяет условию» и «хотя бы одна строка удовлетворяет» берите
BOOL_AND/BOOL_OR(илиEVERY), помня про пропускNULLи проNULLна полностью пустой группе. Заранее решите, что для вашей бизнес-логики значит неизвестное значение, и закрепите это решение черезCOALESCE, а не оставляйте на умолчания агрегата. В MySQL и ClickHouse тот же смысл собирается изMIN/MAXбулева флага.