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