При разработке отчетов в системе «1С:Предприятие» программисты часто сталкиваются с неприятной ситуацией: количественные и суммовые показатели в отчете неожиданно увеличиваются в несколько раз. Данные начинают «задваиваться» или «затраиваться», превращая отчет в абсолютно нечитаемый и недостоверный документ.
В этой статье мы подробно разберем, почему возникает эта проблема на примере соединения табличной части документа ЗаказПокупателя и регистра накопления ЗаказыНаПроизводство. Мы проанализируем физический смысл происходящего в СУБД процесса, исправим ошибки в условиях связи и рассмотрим профессиональный подход к построению таких запросов с использованием временных таблиц и группировок.
Рассмотрим ситуацию, с которой столкнулся автор темы. Ему необходимо сопоставить данные из табличной части документа покупателя с движениями регистра накопления по производству. Первоначальный запрос автора выглядит следующим образом:
ВЫБРАТЬ
ЗаказыНаПроизводство.Период,
ЗаказыНаПроизводство.Активность,
ЗаказыНаПроизводство.ВидДвижения,
ЗаказыНаПроизводство.ДокЗаказ,
ЗаказыНаПроизводство.Номенклатура,
ЗаказыНаПроизводство.ВариантИсполнения,
ЗаказыНаПроизводство.РабочийЦентр,
ЗаказыНаПроизводство.Кол,
ЗаказыНаПроизводство.ДокЗаказ.ПодЗаказ,
ЗаказПокупателяТЧ.Цена,
ЗаказПокупателяТЧ.СуммаБезСкидки,
ЗаказПокупателяТЧ.ПроцентСкидки,
ЗаказПокупателяТЧ.СуммаСкидки,
ЗаказПокупателяТЧ.Сумма,
ЗаказыНаПроизводство.ДокЗаказ.ПодЗаказ.Автор
ИЗ
Документ.ЗаказПокупателя.ТЧ КАК ЗаказПокупателяТЧ
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ЗаказыНаПроизводство КАК ЗаказыНаПроизводство
ПО ЗаказыНаПроизводство.ДокЗаказ.ПодЗаказ.Ссылка = ЗаказПокупателяТЧ.Ссылка
ГДЕ
ЗаказыНаПроизводство.ДокЗаказ.ПодЗаказ ССЫЛКА Документ.ЗаказПокупателя
И НЕ ЗаказыНаПроизводство.ДокЗаказ.ПометкаУдаления
И ЗаказыНаПроизводство.ДокЗаказ.Проведен
Проанализируем условие соединения в данном запросе:
ПО ЗаказыНаПроизводство.ДокЗаказ.ПодЗаказ.Ссылка = ЗаказПокупателяТЧ.Ссылка
Здесь кроется фундаментальная ошибка проектирования реляционных запросов. Мы связываем многострочную часть документа (табличную часть) с многострочной таблицей движений регистра только по одному общему полю — ссылке на документ-основание. На уровне СУБД такое действие порождает частичное декартово произведение (Cartesian product) строк в рамках каждого конкретного документа.
Представим простой пример:
Заказ покупателя содержится 5 строк с разной номенклатурой (например, Стол, Стул, Шкаф, Тумба, Полка).Заказы на производство по этому заказу было сделано 3 записи (движения по производству стола, стула и шкафа).Поскольку в условии соединения указано только равенство ссылок на документы, СУБД обязана сопоставить каждую строку из табличной части документа с каждой строкой из регистра. В результате для этого документа мы получим в выборке не 5 и не 3 строки, а 15 строк (5 строк ТЧ * 3 строки регистра). Показатели сумм и цен из заказа покупателя продублируются трижды для каждой номенклатуры, исказив конечный результат отчета.
Чтобы СУБД понимала, какая именно строка табличной части соответствует конкретной строке регистра накопления, нам необходимо связать их по ключевой аналитике — номенклатуре и характеристике (в исходном регистре она называется вариантом исполнения).
Давайте скорректируем условие связи в запросе:
ПО ЗаказыНаПроизводство.ДокЗаказ.ПодЗаказ = ЗаказПокупателяТЧ.Ссылка
И ЗаказыНаПроизводство.Номенклатура = ЗаказПокупателяТЧ.Номенклатура
И ЗаказыНаПроизводство.ВариантИсполнения = ЗаказПокупателяТЧ.Характеристика
Примечание: Обратите внимание, что обращение к свойству .Ссылка у поля, которое уже является ссылочным (как в ПодЗаказ.Ссылка), избыточно. Платформа 1С делает лишнюю работу по разыменованию ссылки. Пишите напрямую: ПодЗаказ.
После добавления этих условий СУБД соединит только те строки, где совпадают и документ, и сама номенклатура. На первый взгляд, это решает проблему. Однако на практике даже такое соединение может приводить к дублированию данных.
Выясним причины, по которым прямое левое соединение табличной части и регистра по номенклатуре может выдать неверный результат:
Чтобы гарантированно избежать дублирования информации и обеспечить высокую скорость работы запроса, применим золотой стандарт разработки в 1С: разделим запрос на этапы с использованием временных таблиц и предварительной группировкой данных.
Разберем этот алгоритм по шагам:
Сначала выберем данные из табличной части заказа покупателя. Обязательно сгруппируем их по ключевым полям (Ссылка, Номенклатура, Характеристика), а числовые показатели просуммируем. Это гарантирует, что для связки «Один заказ + Одна номенклатура» у нас останется строго одна уникальная строка.
ВЫБРАТЬ
ЗаказПокупателяТовары.Ссылка КАК Ссылка,
ЗаказПокупателяТовары.Номенклатура КАК Номенклатура,
ЗаказПокупателяТовары.ХарактеристикаНоменклатуры КАК Характеристика,
СУММА(ЗаказПокупателяТовары.Количество) КАК КоличествоДок,
СРЕДНЕЕ(ЗаказПокупателяТовары.Цена) КАК Цена,
СУММА(ЗаказПокупателяТовары.Сумма) КАК СуммаДок
ПОМЕСТИТЬ ВТ_ЗаказыПокупателей
ИЗ
Документ.ЗаказПокупателя.Товары КАК ЗаказПокупателяТовары
ГДЕ
ЗаказПокупателяТовары.Ссылка В (&МассивЗаказов)
СГРУППИРОВАТЬ ПО
ЗаказПокупателяТовары.Ссылка,
ЗаказПокупателяТовары.Номенклатура,
ЗаказПокупателяТовары.ХарактеристикаНоменклатуры
ИНДЕКСИРОВАТЬ ПО
Ссылка,
Номенклатура,
Характеристика
;
Аналогично поступим с регистром накопления. Выберем нужные записи, сгруппируем их по документу-основанию, номенклатуре и характеристике, просуммировав ресурсы регистра.
ВЫБРАТЬ
ЗаказыНаПроизводство.ДокЗаказ.ПодЗаказ КАК ДокЗаказПодЗаказ,
ЗаказыНаПроизводство.Номенклатура КАК Номенклатура,
ЗаказыНаПроизводство.ВариантИсполнения КАК ВариантИсполнения,
СУММА(ЗаказыНаПроизводство.Кол) КАК КоличествоПроизводство
ПОМЕСТИТЬ ВТ_ДанныеПроизводства
ИЗ
РегистрНакопления.ЗаказыНаПроизводство КАК ЗаказыНаПроизводство
ГДЕ
ЗаказыНаПроизводство.ДокЗаказ.ПодЗаказ В (&МассивЗаказов)
И НЕ ЗаказыНаПроизводство.ДокЗаказ.ПометкаУдаления
И ЗаказыНаПроизводство.ДокЗаказ.Проведен
СГРУППИРОВАТЬ ПО
ЗаказыНаПроизводство.ДокЗаказ.ПодЗаказ,
ЗаказыНаПроизводство.Номенклатура,
ЗаказыНаПроизводство.ВариантИсполнения
ИНДЕКСИРОВАТЬ ПО
ДокЗаказПодЗаказ,
Номенклатура,
ВариантИсполнения
;
Теперь, когда обе таблицы гарантированно уникальны в разрезе связки «Документ + Номенклатура + Характеристика», мы можем безопасно соединить их. Никакого умножения строк и дублирования сумм произойти физически не может.
ВЫБРАТЬ
ВТ_ЗаказыПокупателей.Ссылка КАК ЗаказПокупателя,
ВТ_ЗаказыПокупателей.Номенклатура КАК Номенклатура,
ВТ_ЗаказыПокупателей.Характеристика КАК Характеристика,
ВТ_ЗаказыПокупателей.КоличествоДок КАК КоличествоЗаказано,
ВТ_ЗаказыПокупателей.Цена КАК Цена,
ВТ_ЗаказыПокупателей.СуммаДок КАК СуммаЗаказа,
ЕСТЬNULL(ВТ_ДанныеПроизводства.КоличествоПроизводство, 0) КАК КоличествоПроизведено
ИЗ
ВТ_ЗаказыПокупателей КАК ВТ_ЗаказыПокупателей
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ДанныеПроизводства КАК ВТ_ДанныеПроизводства
ПО ВТ_ЗаказыПокупателей.Ссылка = ВТ_ДанныеПроизводства.ДокЗаказПодЗаказ
И ВТ_ЗаказыПокупателей.Номенклатура = ВТ_ДанныеПроизводства.Номенклатура
И ВТ_ЗаказыПокупателей.Характеристика = ВТ_ДанныеПроизводства.ВариантИсполнения
Проанализируем еще одну критическую ошибку в исходном запросе автора, которая способна сильно замедлить базу данных при увеличении объема записей:
ЗаказыНаПроизводство.ДокЗаказ.ПодЗаказ.Автор
ЗаказыНаПроизводство.ДокЗаказ.ПодЗаказ.Ссылка
Использование нескольких точек («разыменование полей через точку» или Dot-Walking) в запросах 1С приводит к созданию СУБД скрытых неявных левых соединений (LEFT JOIN) со всеми таблицами метаданных, которые могут быть записаны в этих полях. Ситуация усугубляется, если реквизит имеет составной тип данных. В таком случае SQL-запрос превращается в огромную «колбасу» из соединений, парализуя работу сервера.
Как правильно поступать в таких случаях?
Если вам действительно необходим реквизит шапки связанного документа (например, Автор), то правильнее всего выгрузить необходимые документы во временную таблицу, соединить её с таблицей сотрудников (пользователей) и уже оттуда забрать готовые данные. Ещё лучшим архитектурным решением является фиксация этих важных аналитик непосредственно в измерениях или реквизитах самого регистра при проведении документа, чтобы при построении отчетов системе не приходилось «ходить через три таблицы» в шапку другого документа.
Мы подробно разобрали проблему дублирования данных в отчете 1С. Для её гарантированного решения всегда придерживайтесь следующих правил проектирования:
СГРУППИРОВАТЬ ПО) с суммированием числовых полей, чтобы свести связку соединяемых полей к уникальному ключу.