При разработке отчетов или обработок в 1С часто возникает стандартная задача: проанализировать табличную часть документа (например, Бизнес-процесса или документа реализации), чтобы узнать, сколько всего в ней строк и сколько из них удовлетворяют определенному условию. В частности, речь идет о булевых реквизитах — например, галочке Оплачено. Рассмотрим ситуацию, когда нужно получить и общее число строк, и число оплаченных строк в рамках одного запроса, а также вывести на основе этих данных текстовый статус.
Многие начинающие разработчики пытаются решить задачу "в лоб" с помощью функции КОЛИЧЕСТВО(), используя конструкцию, подобную этой:
ВЫБРАТЬ
БП.Ссылка КАК Ссылка,
КОЛИЧЕСТВО(БП.Оплачено) КАК Оплачено,
КОЛИЧЕСТВО(БП.Оплачено = ИСТИНА) КАК ОплаченоИстинных
ИЗ
БизнесПроцесс.БП.Оплата КАК БП
Проанализируем причину, по которой этот код не дает нужного результата, а возвращает одинаковые значения в обеих колонках. Функция запроса КОЛИЧЕСТВО(Поле) в языке 1С выполняет подсчет всех значений, которые не равны NULL.
Реквизит табличной части, имеющий тип данных Булево, на уровне базы данных всегда содержит либо ИСТИНА, либо ЛОЖЬ. При прямом обращении к табличной части (без левых соединений, которые могли бы породить пустые записи) значение NULL там просто не встречается. Поэтому выражение КОЛИЧЕСТВО(Оплачено) подсчитает абсолютно все строки табличной части. Выражение КОЛИЧЕСТВО(Оплачено = ИСТИНА) сначала вычислит логическое условие (которое вернет Истину или Ложь), а затем снова подсчитает количество этих не-NULL результатов. В итоге мы дважды получаем общее число строк.
Разберем классический и надежный способ раздельного подсчета с использованием агрегатной функции СУММА() в сочетании с условием ВЫБОР. Идея заключается в том, чтобы присвоить нужным строкам числовое значение 1, а ненужным — 0, после чего просто их сложить.
Посмотрим на рабочий пример кода, который решает эту задачу:
ВЫБРАТЬ
БПОПЛАТА.Ссылка КАК Ссылка,
КОЛИЧЕСТВО(БПОПЛАТА.НомерСтроки) КАК КоличествоВсегоСтрок,
СУММА(ВЫБОР КОГДА БПОПЛАТА.Оплачено ТОГДА 1 ИНАЧЕ 0 КОНЕЦ) КАК КоличествоОплаченныхСтрок
ИЗ
БизнесПроцесс.БП.Оплата КАК БПОПЛАТА
СГРУППИРОВАТЬ ПО
БПОПЛАТА.Ссылка
В этом запросе мы делаем следующее:
КОЛИЧЕСТВО(БПОПЛАТА.НомерСтроки). Реквизит НомерСтроки есть в любой табличной части, он никогда не равен NULL, поэтому это самый оптимальный способ получить общее число записей.ВЫБОР КОГДА БПОПЛАТА.Оплачено ТОГДА 1 ИНАЧЕ 0 КОНЕЦ. Если галочка стоит, система возвращает единицу. Затем функция СУММА() суммирует все единицы, выдавая точное количество истинных значений.СГРУППИРОВАТЬ ПО, чтобы подсчет производился в разрезе каждого отдельного документа (Бизнес-процесса).Часто бизнес-логика требует не просто вывести количество, а сформировать текстовый статус документа: "Оплачено полностью", "Оплачено частично" или "Не оплачено". Для этого не обязательно высчитывать точное количество строк. В 1С значения типа Булево можно сравнивать, при этом ЛОЖЬ считается меньше, чем ИСТИНА.
Используем агрегатные функции МИНИМУМ() и МАКСИМУМ() для быстрого анализа состояния всего документа:
МИНИМУМ(Оплачено) = ИСТИНА, это гарантированно означает, что во всех строках табличной части стоит галочка (ведь даже если бы была хоть одна "Ложь", она стала бы минимумом). Документ оплачен полностью.МАКСИМУМ(Оплачено) = ИСТИНА, это значит, что оплачена хотя бы одна строка. Документ оплачен частично.МАКСИМУМ(Оплачено) = ЛОЖЬ, значит истинных значений нет вообще, документ не оплачен.Рассмотрим по шагам, как объединить проверку количеств или булевых значений для вывода итогового текстового статуса прямо в запросе. Объединим описанные выше подходы:
ВЫБРАТЬ
БПОПЛАТА.Ссылка КАК Ссылка,
ВЫБОР
КОГДА КОЛИЧЕСТВО(БПОПЛАТА.НомерСтроки) = СУММА(ВЫБОР КОГДА БПОПЛАТА.Оплачено ТОГДА 1 ИНАЧЕ 0 КОНЕЦ)
ТОГДА "Оплачено полностью"
КОГДА СУММА(ВЫБОР КОГДА БПОПЛАТА.Оплачено ТОГДА 1 ИНАЧЕ 0 КОНЕЦ) > 0
ТОГДА "Оплачено частично"
ИНАЧЕ "Не оплачено"
КОНЕЦ КАК СтатусОплаты
ИЗ
БизнесПроцесс.БП.Оплата КАК БПОПЛАТА
СГРУППИРОВАТЬ ПО
БПОПЛАТА.Ссылка
В этом примере мы сравниваем общее количество строк с суммой оплаченных строк. Если они равны — статус "Оплачено полностью". Если сумма больше нуля, но меньше общего количества — "Оплачено частично". Это избавляет от необходимости писать дополнительный код обработки результата на клиенте или сервере, поручая всю тяжелую логику СУБД.
При построении сложных запросов с соединениями множества таблиц (например, когда мы связываем шапку документа, его табличную часть и данные контрагентов), не стоит выполнять подсчет и группировку непосредственно в основном запросе. Стандарты разработки от 1С настоятельно рекомендуют выносить работу с табличными частями во временные таблицы.
Если попытаться соединить таблицу шапки документа с физической таблицей табличной части без предварительной группировки во временную таблицу, произойдет эффект "размножения строк" (декартово произведение). В результате общие суммы документа (например, сумма по документу из шапки) могут задвоиться, затроиться и т.д., пропорционально количеству строк в табличной части.
Правильный архитектурный подход выглядит так:
ВТОплата), в которой группируем данные по полю Ссылка, считаем КОЛИЧЕСТВО(НомерСтроки) и СУММА(ВЫБОР...).ВТОплата по полю Ссылка.