Почему левое соединение в запросе 1С работает как внутреннее и пропадают строки с NULL?

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

При разработке алгоритмов контроля остатков или списания себестоимости в платформе «1С:Предприятие» разработчики часто сталкиваются с ситуацией: в запросе используется ЛЕВОЕ СОЕДИНЕНИЕ между табличной частью документа и регистром остатков, однако в результат не попадают строки табличной части, по которым остаток в регистре отсутствует. В итоге запрос ведет себя как ВНУТРЕННЕЕ СОЕДИНЕНИЕ. Разберем подробнее причины такого поведения и выясним, как правильно составить запрос согласно стандартам разработки 1С.

Причина проблемы: условие на правую таблицу в секции ГДЕ

Проанализируем исходный текст запроса, в котором проявляется ошибка:


ВЫБРАТЬ
    У_СборкаСоставКомплекта.Номенклатура КАК Номенклатура,
    У_СборкаСоставКомплекта.Количество КАК Количество,
    ХозрасчетныйОстатки.СуммаОстаток КАК СуммаОстаток,
    ЕСТЬNULL(ХозрасчетныйОстатки.КоличествоОстаток, 0) КАК КоличествоОстаток
ИЗ
    Документ.У_Сборка.СоставКомплекта КАК У_СборкаСоставКомплекта
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрБухгалтерии.Хозрасчетный.Остатки КАК ХозрасчетныйОстатки
        ПО У_СборкаСоставКомплекта.Номенклатура = ХозрасчетныйОстатки.Субконто1
ГДЕ
    ХозрасчетныйОстатки.Счет = &СчетУчетаТоваров
    И У_СборкаСоставКомплекта.Ссылка = &Ссылка

Выясним причину некорректного поведения. Механизм работы оператора ГДЕ в языке запросов применяется ко всей результирующей выборке уже после выполнения соединения таблиц. Когда по номенклатуре из табличной части в регистре нет остатков, правая таблица ХозрасчетныйОстатки возвращает значения NULL во всех своих полях, включая поле ХозрасчетныйОстатки.Счет.

В секции ГДЕ проверяется условие ХозрасчетныйОстатки.Счет = &СчетУчетаТоваров. Сравнение NULL = &СчетУчетаТоваров дает результат ЛОЖЬ (точнее, Unknown/NULL). В итоге СУБД отсекает все строки, где остатки отсутствовали, и левое соединение превращается во внутреннее.

Вариант 1: перенос условия в секцию ПО (исправление логики выборки)

Самое простое синтаксическое исправление заключается в переносе отбора по полям правой таблицы из секции ГДЕ в секцию соединения ПО:


ВЫБРАТЬ
    У_СборкаСоставКомплекта.Номенклатура КАК Номенклатура,
    У_СборкаСоставКомплекта.Количество КАК Количество,
    ЕСТЬNULL(ХозрасчетныйОстатки.СуммаОстаток, 0) КАК СуммаОстаток,
    ЕСТЬNULL(ХозрасчетныйОстатки.КоличествоОстаток, 0) КАК КоличествоОстаток
ИЗ
    Документ.У_Сборка.СоставКомплекта КАК У_СборкаСоставКомплекта
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрБухгалтерии.Хозрасчетный.Остатки КАК ХозрасчетныйОстатки
        ПО У_СборкаСоставКомплекта.Номенклатура = ХозрасчетныйОстатки.Субконто1
        И ХозрасчетныйОстатки.Счет = &СчетУчетаТоваров
ГДЕ
    У_СборкаСоставКомплекта.Ссылка = &Ссылка

Теперь условие по счету применяется непосредственно во время соединения. Если остаток не найден, строка документа сохраняется, а числовые поля благодаря функции ЕСТЬNULL корректно заменяются нулями.

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

Вариант 2: правильная передача параметров в виртуальную таблицу

По стандартам разработки 1С любые условия по счетам, периоду и субконто должны передаваться непосредственно в параметры виртуальной таблицы РегистрБухгалтерии.<Имя>.Остатки(). Параметры виртуальной таблицы остатков регистра бухгалтерии располагаются в следующем порядке:

  1. Период — точка во времени (например, &МоментВремени или объект Граница).
  2. УсловиеСчета — выражение для фильтрации счетов (например, Счет = &СчетУчетаТоваров).
  3. ВидыСубконто — массив или список видов субконто для фиксации порядка полей Субконто1, Субконто2.
  4. УсловиеСубконто — логическое выражение для отбора по значениям субконто.
  5. Организация — фильтр по балансовому измерению организации.

Посмотрим, как выглядит оптимизированный запрос с передачей условия по счету в параметры виртуальной таблицы:


ВЫБРАТЬ
    У_СборкаСоставКомплекта.Номенклатура КАК Номенклатура,
    У_СборкаСоставКомплекта.Количество КАК Количество,
    ЕСТЬNULL(ХозрасчетныйОстатки.СуммаОстаток, 0) КАК СуммаОстаток,
    ЕСТЬNULL(ХозрасчетныйОстатки.КоличествоОстаток, 0) КАК КоличествоОстаток
ИЗ
    Документ.У_Сборка.СоставКомплекта КАК У_СборкаСоставКомплекта
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрБухгалтерии.Хозрасчетный.Остатки(
        &МоментВремени,
        Счет = &СчетУчетаТоваров,
        ,
    ) КАК ХозрасчетныйОстатки
        ПО У_СборкаСоставКомплекта.Номенклатура = ХозрасчетныйОстатки.Субконто1
ГДЕ
    У_СборкаСоставКомплекта.Ссылка = &Ссылка

Рекомендованный эталон: пакетный запрос с временными таблицами

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

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

Разберем по шагам создание правильного пакетного запроса:


ВЫБРАТЬ
    ДокументТЧ.Номенклатура КАК Номенклатура,
    СУММА(ДокументТЧ.Количество) КАК Количество
ПОМЕСТИТЬ ВТ_ТоварыДокумента
ИЗ
    Документ.У_Сборка.СоставКомплекта КАК ДокументТЧ
ГДЕ
    ДокументТЧ.Ссылка = &Ссылка
СГРУППИРОВАТЬ ПО
    ДокументТЧ.Номенклатура
ИНДЕКСИРОВАТЬ ПО
    Номенклатура
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ.Номенклатура КАК Номенклатура,
    ВТ.Количество КАК КоличествоДокумента,
    ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК КоличествоОстаток,
    ЕСТЬNULL(Остатки.СуммаОстаток, 0) КАК СуммаОстаток
ИЗ
    ВТ_ТоварыДокумента КАК ВТ
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрБухгалтерии.Хозрасчетный.Остатки(
        &МоментВремени,
        Счет = &СчетУчетаТоваров,
        &ВидыСубконтоНоменклатура,
        Субконто1 В (ВЫБРАТЬ ВТ_ТоварыДокумента.Номенклатура ИЗ ВТ_ТоварыДокумента)
    ) КАК Остатки
        ПО ВТ.Номенклатура = Остатки.Субконто1

Проанализируем ключевые преимущества этого подхода:

  1. Табличная часть сворачивается в ВТ_ТоварыДокумента, исключая задвоение остатков.
  2. Временная таблица индексируется по полю Номенклатура, что ускоряет операцию соединения.
  3. В параметре УсловиеСубконто виртуальной таблицы остатков передается условие Субконто1 В (ВЫБРАТЬ ...). СУБД считывает из базы только остатки нужных позиций.
  4. В первый параметр Период передается &МоментВремени документа, что гарантирует получение корректных остатков на момент проведения.
  5. Все числовые поля присоединенной таблицы обернуты в функцию ЕСТЬNULL, что предотвращает появление NULL при вычислениях дефицита и себестоимости.
← На главную