В практике разработки на платформе 1С:Предприятие часто возникают ситуации, когда данные необходимо извлечь не из текущего объекта, а из глубоко вложенной структуры. Представьте иерархию: Документ 1 содержит в своей табличной части ссылку на Документ 2, тот, в свою очередь, в своей табличной части хранит ссылку на Документ 3, и так далее, пока мы не доберемся до нужного нам реквизита в Документе 4. Подобные задачи требуют четкого понимания архитектуры запросов и работы СУБД, чтобы не допустить критического снижения производительности системы.
В этой статье мы подробно разберем, как правильно выстраивать связи в запросе, на что обратить внимание при работе с табличными частями и как оптимизировать выборку, чтобы план запроса оставался эффективным даже на больших объемах данных.
Рассмотрим ситуацию, описанную автором: нам нужно пройти путь через четыре документа. Основная сложность заключается в том, что связи идут не через реквизиты шапки, а через строки табличных частей. Это означает, что для каждого документа в цепочке нам необходимо обращаться к его подчиненной таблице (табличной части).
Проанализируем базовую логику построения такого запроса. Самый простой и очевидный путь — использование последовательных ВНУТРЕННИХ СОЕДИНЕНИЙ (INNER JOIN) или ЛЕВЫХ СОЕДИНЕНИЙ (LEFT JOIN). Разберем по шагам, как должна выглядеть эта цепочка:
Документ1.Товары.Документ2.Товары по ссылке, хранящейся в Документ1.Товары.Номенклатура (или ином связующем реквизите).Посмотрим на пример реализации такого запроса, где мы пытаемся добраться от «Заказа поставщику» до данных в «Таможенной декларации» через промежуточное «Приобретение товаров и услуг»:
ВЫБРАТЬ
ЗаказПоставщику.Ссылка КАК СсылкаЗаказ,
ТаможеннаяДекларацияИмпортТовары.Ссылка.Организация КАК Организация
ИЗ
Документ.ЗаказПоставщику КАК ЗаказПоставщику
ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПриобретениеТоваровУслуг.Товары КАК ПриобретениеТоваровУслугТовары
ПО ПриобретениеТоваровУслугТовары.ЗаказПоставщику = ЗаказПоставщику.Ссылка
ЛЕВОЕ СОЕДИНЕНИЕ Документ.ТаможеннаяДекларацияИмпорт.Товары КАК ТаможеннаяДекларацияИмпортТовары
ПО ТаможеннаяДекларацияИмпортТовары.ДокументПоступления = ПриобретениеТоваровУслугТовары.Ссылка
ГДЕ
ЗаказПоставщику.Ссылка = &Ссылка
В данном примере мы видим использование ЛЕВОЕ СОЕДИНЕНИЕ. Это важно, если мы хотим получить основной документ даже в том случае, если цепочка связей где-то прерывается. Если же нам нужны только полные цепочки, следует использовать ВНУТРЕННЕЕ СОЕДИНЕНИЕ.
Как справедливо отмечено в обсуждении, последовательные соединения множества табличных частей могут порождать неэффективные планы запроса в SQL. Когда СУБД видит запрос с четырьмя-пятью вложенными соединениями огромных физических таблиц табличных частей, оптимизатору становится сложно определить правильный порядок сканирования индексов.
Выясним основные причины деградации производительности в таких запросах:
Документ2.Товары с Документ3.Товары по реквизиту ДокументОснование, который не является индексированным, СУБД будет вынуждена выполнять полное сканирование (Full Scan) таблицы, что на больших базах приведет к зависанию запроса.ТЧ.Ресурс.Реквизит.Реквизит в полях ВЫБРАТЬ создает скрытые LEFT JOIN, которые еще сильнее усложняют структуру запроса.Для решения проблемы «плохого плана» проанализируем методику использования временных таблиц. Вместо одного гигантского запроса, разделим получение данных на этапы. Это позволит нам принудительно проиндексировать промежуточные результаты и упростить работу оптимизатору SQL.
Разберем алгоритм оптимизированного запроса:
Документ1.ТабличнаяЧасть во временную таблицу ВТ_Звено1.ВТ_Звено1.ВТ_Звено1 с табличной частью Документ2 и выгружаем результат во ВТ_Звено2.Рассмотрим пример кода для такого подхода:
// 1. Получаем ссылки на второй документ из первого
ВЫБРАТЬ РАЗЛИЧНЫЕ
ТЧ1.ДокументСсылка2 КАК Ссылка2
ПОМЕСТИТЬ ВТ_Связи_1_2
ИЗ
Документ.Документ1.ТабличнаяЧасть КАК ТЧ1
ГДЕ
ТЧ1.Ссылка = &ПараметрСсылка;
ИНДЕКСИРОВАТЬ ПО
Ссылка2;
// 2. Переходим к третьему звену
ВЫБРАТЬ РАЗЛИЧНЫЕ
ТЧ2.ДокументСсылка3 КАК Ссылка3
ПОМЕСТИТЬ ВТ_Связи_2_3
ИЗ
ВТ_Связи_1_2 КАК Связи
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.Документ2.ТабличнаяЧасть КАК ТЧ2
ПО Связи.Ссылка2 = ТЧ2.Ссылка;
ИНДЕКСИРОВАТЬ ПО
Ссылка3;
Такой подход гарантирует, что на каждом этапе СУБД работает с подготовленным и ограниченным набором данных.
Проанализируем еще один важный нюанс: если реквизит в табличной части, по которому идет связь, имеет составной тип (например, ДокументСсылка.Реализация и ДокументСсылка.Поступление), производительность запроса резко упадет. Это происходит потому, что SQL-сервер будет вынужден соединяться со всеми физическими таблицами, входящими в этот тип.
Решение: Всегда используйте оператор ВЫРАЗИТЬ. Это явно указывает платформе, к какой конкретно таблице нужно обращаться.
Рассмотрим правильное применение этой функции в коде запроса:
ВЫБРАТЬ
ВЫРАЗИТЬ(ТЧ.ДокументОснование КАК Документ.ПриобретениеТоваровУслуг).Организация КАК Организация
ИЗ
Документ.ЗаказПоставщику.Товары КАК ТЧ
Применение ВЫРАЗИТЬ в условиях ПО при соединениях также критически важно для производительности в высоконагруженных системах.
Иногда вместо написания сложных запросов к вложенным табличным частям эффективнее использовать встроенный механизм платформы — Критерии отбора. Если в вашей конфигурации настроен критерий (например, СвязанныеДокументы), вы можете программно получить список всех подчиненных объектов гораздо быстрее. Однако это подходит больше для формирования отчетов или списков в интерфейсе, чем для массовой обработки данных в одном запросе.
Подведем итоги. Для построения надежной архитектуры запроса при получении данных из цепочки документов придерживайтесь следующих правил:
ВЫРАЗИТЬ.ЛЕВОЕ СОЕДИНЕНИЕ и ВНУТРЕННЕЕ СОЕДИНЕНИЕ: первый вариант безопаснее с точки зрения сохранения строк, второй — быстрее отсекает ненужные данные.Соблюдение этих правил позволит вам создавать эффективные и отказоустойчивые алгоритмы извлечения данных в системе 1С:Предприятие.