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