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