Как в одном запросе 1С выбрать общее количество строк и количество строк с признаком ИСТИНА?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке отчетов или обработок в 1С часто возникает стандартная задача: проанализировать табличную часть документа (например, Бизнес-процесса или документа реализации), чтобы узнать, сколько всего в ней строк и сколько из них удовлетворяют определенному условию. В частности, речь идет о булевых реквизитах — например, галочке Оплачено. Рассмотрим ситуацию, когда нужно получить и общее число строк, и число оплаченных строк в рамках одного запроса, а также вывести на основе этих данных текстовый статус.

Почему не работает стандартная функция КОЛИЧЕСТВО?

Многие начинающие разработчики пытаются решить задачу "в лоб" с помощью функции КОЛИЧЕСТВО(), используя конструкцию, подобную этой:


ВЫБРАТЬ
    БП.Ссылка КАК Ссылка,
    КОЛИЧЕСТВО(БП.Оплачено) КАК Оплачено,
    КОЛИЧЕСТВО(БП.Оплачено = ИСТИНА) КАК ОплаченоИстинных
ИЗ
    БизнесПроцесс.БП.Оплата КАК БП

Проанализируем причину, по которой этот код не дает нужного результата, а возвращает одинаковые значения в обеих колонках. Функция запроса КОЛИЧЕСТВО(Поле) в языке 1С выполняет подсчет всех значений, которые не равны NULL.

Реквизит табличной части, имеющий тип данных Булево, на уровне базы данных всегда содержит либо ИСТИНА, либо ЛОЖЬ. При прямом обращении к табличной части (без левых соединений, которые могли бы породить пустые записи) значение NULL там просто не встречается. Поэтому выражение КОЛИЧЕСТВО(Оплачено) подсчитает абсолютно все строки табличной части. Выражение КОЛИЧЕСТВО(Оплачено = ИСТИНА) сначала вычислит логическое условие (которое вернет Истину или Ложь), а затем снова подсчитает количество этих не-NULL результатов. В итоге мы дважды получаем общее число строк.

Решение 1: Использование конструкции ВЫБОР и функции СУММА

Разберем классический и надежный способ раздельного подсчета с использованием агрегатной функции СУММА() в сочетании с условием ВЫБОР. Идея заключается в том, чтобы присвоить нужным строкам числовое значение 1, а ненужным — 0, после чего просто их сложить.

Посмотрим на рабочий пример кода, который решает эту задачу:


ВЫБРАТЬ 
    БПОПЛАТА.Ссылка КАК Ссылка,
    КОЛИЧЕСТВО(БПОПЛАТА.НомерСтроки) КАК КоличествоВсегоСтрок,
    СУММА(ВЫБОР КОГДА БПОПЛАТА.Оплачено ТОГДА 1 ИНАЧЕ 0 КОНЕЦ) КАК КоличествоОплаченныхСтрок
ИЗ 
    БизнесПроцесс.БП.Оплата КАК БПОПЛАТА
СГРУППИРОВАТЬ ПО
    БПОПЛАТА.Ссылка

В этом запросе мы делаем следующее:

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

Решение 2: Элегантный трюк с МИНИМУМ и МАКСИМУМ для статусов

Часто бизнес-логика требует не просто вывести количество, а сформировать текстовый статус документа: "Оплачено полностью", "Оплачено частично" или "Не оплачено". Для этого не обязательно высчитывать точное количество строк. В 1С значения типа Булево можно сравнивать, при этом ЛОЖЬ считается меньше, чем ИСТИНА.

Используем агрегатные функции МИНИМУМ() и МАКСИМУМ() для быстрого анализа состояния всего документа:

  1. Если МИНИМУМ(Оплачено) = ИСТИНА, это гарантированно означает, что во всех строках табличной части стоит галочка (ведь даже если бы была хоть одна "Ложь", она стала бы минимумом). Документ оплачен полностью.
  2. Если МАКСИМУМ(Оплачено) = ИСТИНА, это значит, что оплачена хотя бы одна строка. Документ оплачен частично.
  3. Если МАКСИМУМ(Оплачено) = ЛОЖЬ, значит истинных значений нет вообще, документ не оплачен.

Формирование текста в одном запросе

Рассмотрим по шагам, как объединить проверку количеств или булевых значений для вывода итогового текстового статуса прямо в запросе. Объединим описанные выше подходы:


ВЫБРАТЬ
    БПОПЛАТА.Ссылка КАК Ссылка,
    ВЫБОР 
        КОГДА КОЛИЧЕСТВО(БПОПЛАТА.НомерСтроки) = СУММА(ВЫБОР КОГДА БПОПЛАТА.Оплачено ТОГДА 1 ИНАЧЕ 0 КОНЕЦ) 
            ТОГДА "Оплачено полностью" 
        КОГДА СУММА(ВЫБОР КОГДА БПОПЛАТА.Оплачено ТОГДА 1 ИНАЧЕ 0 КОНЕЦ) > 0 
            ТОГДА "Оплачено частично"
        ИНАЧЕ "Не оплачено" 
    КОНЕЦ КАК СтатусОплаты
ИЗ
    БизнесПроцесс.БП.Оплата КАК БПОПЛАТА
СГРУППИРОВАТЬ ПО
    БПОПЛАТА.Ссылка

В этом примере мы сравниваем общее количество строк с суммой оплаченных строк. Если они равны — статус "Оплачено полностью". Если сумма больше нуля, но меньше общего количества — "Оплачено частично". Это избавляет от необходимости писать дополнительный код обработки результата на клиенте или сервере, поручая всю тяжелую логику СУБД.

Архитектурный нюанс: зачем нужны временные таблицы?

При построении сложных запросов с соединениями множества таблиц (например, когда мы связываем шапку документа, его табличную часть и данные контрагентов), не стоит выполнять подсчет и группировку непосредственно в основном запросе. Стандарты разработки от 1С настоятельно рекомендуют выносить работу с табличными частями во временные таблицы.

Если попытаться соединить таблицу шапки документа с физической таблицей табличной части без предварительной группировки во временную таблицу, произойдет эффект "размножения строк" (декартово произведение). В результате общие суммы документа (например, сумма по документу из шапки) могут задвоиться, затроиться и т.д., пропорционально количеству строк в табличной части.

Правильный архитектурный подход выглядит так:

  1. Создаем временную таблицу (например, ВТОплата), в которой группируем данные по полю Ссылка, считаем КОЛИЧЕСТВО(НомерСтроки) и СУММА(ВЫБОР...).
  2. В основном запросе делаем левое соединение главной таблицы документа с нашей временной таблицей ВТОплата по полю Ссылка.
  3. Выводим результат без риска дублирования записей и с оптимальным планом выполнения на стороне сервера баз данных.
← На главную