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