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