При разработке и доработке учетных систем на платформе 1С разработчики часто сталкиваются с вопросом производительности при получении остатков номенклатуры. Существует распространенное заблуждение, будто любое соединение таблиц на уровне СУБД выполняется как вложенный построчный цикл, и поэтому всегда эффективнее предварительно поместить номенклатуру во временную таблицу и передать ее в параметры виртуальной таблицы. Разберем детально, как на самом деле СУБД выполняет соединения, как работают виртуальные таблицы остатков и какие паттерны построения запросов обеспечивают максимальную скорость работы.
Отвечая на вопрос о механике соединения двух таблиц: соединение не является обязательным построчным циклом. В реляционных СУБД, таких как Microsoft SQL Server или PostgreSQL, физическая реализация логического оператора соединения (JOIN) выбирается оптимизатором запросов на основе статистических данных, объема таблиц и наличия индексов. Проанализируем три основных алгоритма соединения, используемых СУБД:
Таким образом, соединение справочника и регистра не превращается автоматически в медленный цикл, если статистика базы данных актуальна и задействованы правильные индексы.
Чтобы понять, как оптимизировать запрос, выясним внутреннюю механику работы регистра накопления. В физической структуре базы данных 1С остатки хранятся в таблице итогов (например, _AccumRgT), а детальные записи — в таблице движений (_AccumRg). Виртуальная таблица Остатки представляет собой готовый подзапрос, формируемый платформой.
Критически важно помнить следующее правило: условия отбора всегда должны передаваться в параметры самой виртуальной таблицы. Если передать условие номенклатуры в секцию ГДЕ основного запроса или только в условие связи ПО, платформа и СУБД сначала рассчитают остатки по абсолютно всем позициям регистра в базе, и лишь после этого отсекут ненужное. Это порождает гигантский объем чтений с диска и перегрузку процессора.
Когда мы указываем условие в круглых скобках параметров виртуальной таблицы, оно транслируется непосредственно в блок WHERE внутреннего запроса к физическим таблицам СУБД, отсекая лишние записи на самом раннем этапе выборки.
Рассмотрим сценарии работы со списком номенклатуры. Нужно ли выбирать справочник Номенклатура во временную таблицу только ради передачи в виртуальную таблицу остатков?
tempdb (запись и последующее удаление данных). В такой ситуации достаточно обратиться напрямую к виртуальной таблице остатков без условий отбора.ИНДЕКСИРОВАТЬ ПО. Без явного индекса временная таблица создается без первичного ключа, и СУБД будет выполнять ее полное сканирование (Table Scan).При этом важно учитывать способ передачи списка. Конструкция вида В (&МассивНоменклатуры) транслируется в SQL-запрос как перечисление параметров IN (@P1, @P2, ...). В Microsoft SQL Server действует жесткое ограничение на 2100 параметров в одном запросе. Кроме того, при передаче сотен параметров фаза компиляции плана запроса существенно замедляется. Передача фильтра через временную таблицу лишена этого недостатка.
Посмотрим на практические примеры построения запросов для типичных учетных задач.
Вариант 1. Получение остатков только по позициям документа (ненулевые остатки)
Если нам не требуются строки с нулевыми остатками, достаточно отфильтровать виртуальную таблицу по временной таблице, не выполняя последующих тяжелых соединений:
ВЫБРАТЬ РАЗЛИЧНЫЕ
ДокументТабЧасть.Номенклатура КАК Номенклатура
ПОМЕСТИТЬ ВТ_Товары
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК ДокументТабЧасть
ГДЕ
ДокументТабЧасть.Ссылка = &Ссылка
ИНДЕКСИРОВАТЬ ПО
Номенклатура
;
ВЫБРАТЬ
Остатки.Номенклатура КАК Номенклатура,
Остатки.ВНаличииОстаток КАК Количество
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(
&Период,
Номенклатура В (ВЫБРАТЬ ВТ_Товары.Номенклатура ИЗ ВТ_Товары КАК ВТ_Товары)
) КАК Остатки
Вариант 2. Полный список позиций с остатками (включая нулевые остатки)
Если необходимо вывести все позиции из исходного списка, даже если по ним нет движений и остатков, используем левое соединение. Обратите внимание: отбор в параметрах виртуальной таблицы мы обязательно дублируем:
ВЫБРАТЬ РАЗЛИЧНЫЕ
ДокументТабЧасть.Номенклатура КАК Номенклатура
ПОМЕСТИТЬ ВТ_Товары
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК ДокументТабЧасть
ГДЕ
ДокументТабЧасть.Ссылка = &Ссылка
ИНДЕКСИРОВАТЬ ПО
Номенклатура
;
ВЫБРАТЬ
ВТ_Товары.Номенклатура КАК Номенклатура,
ЕСТЬNULL(Остатки.ВНаличииОстаток, 0) КАК Количество
ИЗ
ВТ_Товары КАК ВТ_Товары
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(
&Период,
Номенклатура В (ВЫБРАТЬ ВТ_Товары.Номенклатура ИЗ ВТ_Товары КАК ВТ_Товары)
) КАК Остатки
ПО ВТ_Товары.Номенклатура = Остатки.Номенклатура
Разберем ключевые шаги, которые следует выполнить, если запрос получения остатков выполняется медленно:
_AccumRgT, но и миллионы записей движений из _AccumRg за весь период смещения. Регулярный пересчет итогов регламентным заданием критически важен.