Как построить быстрый отчет по цепочке документов: Заказ покупателя → Заказ поставщику → Поступление → Отгрузка?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

В процессе работы часто возникает задача отследить полный жизненный цикл заказа: от момента его оформления клиентом до финальной отгрузки товара. Попытка решить эту задачу "в лоб", соединяя напрямую таблицы документов, почти всегда приводит к серьезным проблемам с производительностью. Запрос может выполняться минуты или даже часы, а при увеличении периода выборки — и вовсе не возвращать результат. Рассмотрим, почему так происходит и как построить эффективный и быстрый запрос для анализа цепочки движения заказов.

Анализ исходной проблемы — почему запрос к документам работает медленно?

Давайте проанализируем первоначальный запрос, который стал причиной проблемы. Его основная идея — последовательно соединить четыре документа через ЛЕВОЕ СОЕДИНЕНИЕ.


ВЫБРАТЬ
    ЗаказПокупателя.Ссылка КАК СсылкаПокуп,
    // ... другие поля
    ЗаказПоставщику.Ссылка КАК СсылкаПоста,
    ПриходнаяНакладная.Ссылка КАК СсылкаПрих,
    РасходнаяНакладная.Ссылка КАК СсылкаРасх
ИЗ   
    Документ.ЗаказПокупателя КАК ЗаказПокупателя
        ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаказПоставщику КАК ЗаказПоставщику
            ПО ЗаказПокупателя.Ссылка = ЗаказПоставщику.Товары.ЗаказПокупателя		
        ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПриходнаяНакладная КАК ПриходнаяНакладная
            ПО ПриходнаяНакладная.Товары.ЗаказПокупателя = ЗаказПокупателя.Ссылка
        ЛЕВОЕ СОЕДИНЕНИЕ Документ.РасходнаяНакладная КАК РасходнаяНакладная
            ПО ЗаказПокупателя.Ссылка = РасходнаяНакладная.Товары.ЗаказПокупателя.Ссылка
ГДЕ
    ЗаказПокупателя.Дата МЕЖДУ &НачПериода И &КонПериода

Здесь есть две критические ошибки, которые убивают производительность:

  1. Соединение по табличной части. Условие вида ПО ЗаказПокупателя.Ссылка = ЗаказПоставщику.Товары.ЗаказПокупателя является крайне ресурсоемким. Для каждой записи из таблицы Документ.ЗаказПокупателя система вынуждена полностью сканировать всю вложенную таблицу ЗаказПоставщику.Товары всех документов ЗаказПоставщику. Если у вас тысячи заказов, а в каждом десятки строк, количество операций становится астрономическим. Это главная причина "зависания" запроса.
  2. Логическая ошибка в связях. Запрос связывает документы только по ссылке на исходный заказ покупателя. Но он не учитывает номенклатуру. Это приводит к тому, что если в заказе покупателя был "Стол", а для его обеспечения создан заказ поставщику на "Ножки для стола" и "Столешницу", то в отчете они могут быть связаны с другим заказом поставщику на "Стулья", если тот тоже был создан на основании того же заказа покупателя. В результате получается "винегрет" из данных, который не отражает реальную цепочку движения конкретных товаров.

Вывод: Прямые запросы к таблицам документов подходят для получения данных из самих документов, но не для анализа связей и движений. Для этого в платформе 1С существует более подходящий механизм.

Решение: Переход от Документов к Регистрам

Основной принцип оптимизации аналитических запросов в 1С — работать с регистрами, а не с документами. Документы — это объекты для фиксации хозяйственных операций. Регистры (в частности, регистры накопления) — это специально подготовленные, проиндексированные таблицы, которые хранят уже обработанную информацию из документов (движения) и предназначены для быстрого получения срезов и итогов.

Чтобы решить нашу задачу, нужно разбить сложный запрос на несколько логических шагов, используя временные таблицы (ВТ). Такой подход позволяет системе оптимизировать каждый шаг отдельно и работать с меньшими объемами данных на каждом этапе соединения.

Пошаговое построение запроса с использованием Временных Таблиц (ВТ)

Разберем процесс создания отчета по шагам. Предположим, что в нашей конфигурации существуют следующие регистры накопления:

Шаг 1. Выбираем исходные Заказы покупателей

На первом шаге создадим временную таблицу, в которую поместим все заказы покупателей за интересующий нас период. Это будет основа нашего отчета.


ВЫБРАТЬ
    ЗаказПокупателя.Ссылка,
    ЗаказПокупателя.Дата,
    ЗаказПокупателя.Номер,
    ЗаказПокупателя.Контрагент,
    ЗаказПокупателя.Менеджер,
    ЗаказыПокупателейТовары.Номенклатура,
    ЗаказыПокупателейТовары.Количество
ПОМЕСТИТЬ ВТ_ЗаказыПокупателей
ИЗ
    Документ.ЗаказПокупателя.Товары КАК ЗаказыПокупателейТовары
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ЗаказПокупателя КАК ЗаказПокупателя
        ПО ЗаказыПокупателейТовары.Ссылка = ЗаказПокупателя.Ссылка
ГДЕ
    ЗаказПокупателя.Дата МЕЖДУ &НачалоПериода И &КонецПериода
    И ЗаказПокупателя.Проведен = ИСТИНА

На этом этапе мы получили отправную точку — список всех заказанных товаров по документам за период.

Шаг 2. Находим связанные Заказы поставщикам

Теперь обратимся к регистру, который хранит информацию о размещении заказов у поставщиков. В типовых конфигурациях в таких регистрах часто есть измерение типа ЗаказПокупателя, которое и служит связующим звеном. Мы присоединим движения этого регистра к нашей первой ВТ.


ВЫБРАТЬ
    ЗаказыПоставщикамОбороты.Регистратор КАК ЗаказПоставщику,
    ЗаказыПоставщикамОбороты.ЗаказПокупателя,
    ЗаказыПоставщикамОбороты.Номенклатура,
    ЗаказыПоставщикамОбороты.КоличествоОборот
ПОМЕСТИТЬ ВТ_ЗаказыПоставщикам
ИЗ
    РегистрНакопления.ЗаказыПоставщикам.Обороты(
            ,
            ,
            Регистратор,
            ЗаказПокупателя В (ВЫБРАТЬ Ссылка ИЗ ВТ_ЗаказыПокупателей)
    ) КАК ЗаказыПоставщикамОбороты

Важный момент: мы используем виртуальную таблицу Обороты. Она работает значительно быстрее, чем обращение к физической таблице движений. В качестве параметра отбора мы передаем список заказов покупателей из нашей первой временной таблицы, что резко сужает область поиска.

Шаг 3. Собираем данные о поступлении товаров

Аналогично поступаем с поступлениями. Нам нужно найти движения по регистру складского учета (например, ТоварыНаСкладах), где регистратором выступает документ ПриходнаяНакладная (или ПоступлениеТоваровУслуг). Связь может идти через документ-основание или через тот же заказ покупателя, если он "протягивается" через всю цепочку.


ВЫБРАТЬ
    ТоварыНаСкладахОбороты.Регистратор КАК ПриходнаяНакладная,
    ТоварыНаСкладахОбороты.ЗаказПокупателя, // Предполагаем, что такое измерение есть
    ТоварыНаСкладахОбороты.Номенклатура,
    ТоварыНаСкладахОбороты.КоличествоПриход
ПОМЕСТИТЬ ВТ_Приходы
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Обороты(
            ,
            ,
            Регистратор,
            ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход)
            И ЗаказПокупателя В (ВЫБРАТЬ Ссылка ИЗ ВТ_ЗаказыПокупателей)
    ) КАК ТоварыНаСкладахОбороты
ГДЕ
    ТИПЗНАЧЕНИЯ(ТоварыНаСкладахОбороты.Регистратор) = ТИП(Документ.ПриходнаяНакладная)

Шаг 4. Собираем данные об отгрузке

Последний шаг — отгрузка. Логика та же: обороты по регистру ТоварыНаСкладах, но уже с видом движения Расход и фильтром по типу регистратора РасходнаяНакладная.


ВЫБРАТЬ
    ТоварыНаСкладахОбороты.Регистратор КАК РасходнаяНакладная,
    ТоварыНаСкладахОбороты.ЗаказПокупателя,
    ТоварыНаСкладахОбороты.Номенклатура,
    ТоварыНаСкладахОбороты.КоличествоРасход
ПОМЕСТИТЬ ВТ_Расходы
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Обороты(
            ,
            ,
            Регистратор,
            ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Расход)
            И ЗаказПокупателя В (ВЫБРАТЬ Ссылка ИЗ ВТ_ЗаказыПокупателей)
    ) КАК ТоварыНаСкладахОбороты
ГДЕ
    ТИПЗНАЧЕНИЯ(ТоварыНаСкладахОбороты.Регистратор) = ТИП(Документ.РасходнаяНакладная)

Шаг 5. Финальное соединение и результат

Теперь, когда у нас есть четыре подготовленные и небольшие по размеру временные таблицы, мы можем собрать из них итоговый результат. Используем ЛЕВОЕ СОЕДИНЕНИЕ, отталкиваясь от основной таблицы ВТ_ЗаказыПокупателей.


ВЫБРАТЬ
    ВТ_ЗаказыПокупателей.Ссылка КАК ЗаказПокупателя,
    ВТ_ЗаказыПокупателей.Дата КАК ДатаПокуп,
    ВТ_ЗаказыПокупателей.Номер КАК НомерПокуп,
    ПРЕДСТАВЛЕНИЕ(ВТ_ЗаказыПокупателей.Контрагент) КАК КонтрагентПокуп,
    ПРЕДСТАВЛЕНИЕ(ВТ_ЗаказыПокупателей.Менеджер) КАК Менеджер,
    ВТ_ЗаказыПоставщикам.ЗаказПоставщику,
    ВТ_ЗаказыПоставщикам.ЗаказПоставщику.Дата КАК ДатаПоста,
    ВТ_ЗаказыПоставщикам.ЗаказПоставщику.Номер КАК НомерПоста,
    ВТ_Приходы.ПриходнаяНакладная,
    ВТ_Приходы.ПриходнаяНакладная.Дата КАК ДатаПрих,
    ВТ_Приходы.ПриходнаяНакладная.Номер КАК НомерПрих,
    ВТ_Расходы.РасходнаяНакладная,
    ВТ_Расходы.РасходнаяНакладная.Дата КАК ДатаРасх,
    ВТ_Расходы.РасходнаяНакладная.Номер КАК НомерРасх
ИЗ
    ВТ_ЗаказыПокупателей КАК ВТ_ЗаказыПокупателей
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ЗаказыПоставщикам КАК ВТ_ЗаказыПоставщикам
        ПО ВТ_ЗаказыПокупателей.Ссылка = ВТ_ЗаказыПоставщикам.ЗаказПокупателя
        И ВТ_ЗаказыПокупателей.Номенклатура = ВТ_ЗаказыПоставщикам.Номенклатура

        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Приходы КАК ВТ_Приходы
        ПО ВТ_ЗаказыПокупателей.Ссылка = ВТ_Приходы.ЗаказПокупателя
        И ВТ_ЗаказыПокупателей.Номенклатура = ВТ_Приходы.Номенклатура

        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Расходы КАК ВТ_Расходы
        ПО ВТ_ЗаказыПокупателей.Ссылка = ВТ_Расходы.ЗаказПокупателя
        И ВТ_ЗаказыПокупателей.Номенклатура = ВТ_Расходы.Номенклатура
УПОРЯДОЧИТЬ ПО
    ДатаПокуп,
    НомерПокуп

Обратите внимание на два момента в финальном запросе:

Такой подход, основанный на регистрах и временных таблицах, является стандартом для построения сложных аналитических отчетов в 1С. Он не только в десятки, а то и сотни раз быстрее прямого соединения документов, но и обеспечивает гораздо большую гибкость и корректность получаемых данных.

← На главную