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