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