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

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

При разработке отчетов или механизмов проведения документов в 1С мы часто сталкиваемся с необходимостью получить данные из регистров накопления, отфильтрованные по конкретному документу или списку документов. Казалось бы, задача тривиальная, но иногда стандартные конструкции языка запросов ведут себя непредсказуемо.

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

Суть проблемы

Рассмотрим исходные данные. У нас есть самописный регистр накопления ВалютнаяПереоценка. У него есть измерение Партия, которое имеет составной тип данных (например, включает документы Отражение и ВалютнаяОперация).

Мы пытаемся получить обороты по конкретному документу Отражение. Разработчик пробует два варианта запроса.

Вариант 1 (Рабочий):

В параметрах виртуальной таблицы или в секции ГДЕ устанавливается прямой отбор на измерение.


ВЫБРАТЬ
    ВалютнаяПереоценкаОбороты.СуммаПриход
ИЗ
    РегистрНакопления.ВалютнаяПереоценка.Обороты(, , , Партия = &Ссылка) КАК ВалютнаяПереоценкаОбороты

Этот запрос отрабатывает корректно и возвращает записи.

Вариант 2 (Проблемный):

Мы помещаем интересующий нас документ во временную таблицу (или используем таблицу документа напрямую) и пытаемся соединить её с виртуальной таблицей оборотов.


ВЫБРАТЬ
    ВТ_Отражения.Ссылка,
    ВалютнаяПереоценкаОбороты.СуммаПриход
ИЗ
    ВТ_Отражения КАК ВТ_Отражения
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ВалютнаяПереоценка.Обороты КАК ВалютнаяПереоценкаОбороты
        ПО ВТ_Отражения.Ссылка = ВалютнаяПереоценкаОбороты.Партия

Результат этого запроса часто оказывается пустым (NULL в полях регистра), даже если движения по документу точно есть.

Разбираемся в причинах

Почему так происходит? Здесь играют роль два фактора: природа виртуальных таблиц и особенности работы с составными типами данных на уровне SQL.

  1. Виртуальная таблица — это вложенный запрос. Когда вы обращаетесь к РегистрНакопления.ВалютнаяПереоценка.Обороты, платформа генерирует сложный SQL-запрос, который обращается к таблицам итогов или таблицам движений. Если вы не передаете параметры внутрь виртуальной таблицы (в скобки), система пытается построить таблицу оборотов по всем данным регистра, а уже потом соединить её с вашей таблицей.
  2. Составной тип данных. Измерение Партия может содержать ссылки на разные виды документов. На уровне базы данных (SQL) это поле представлено несколькими колонками (обычно это ID типа и ID ссылки). Когда мы делаем соединение ПО ВТ_Отражения.Ссылка = ВалютнаяПереоценкаОбороты.Партия, оптимизатор SQL не всегда может корректно сопоставить конкретный тип ссылки из временной таблицы с составным полем в подзапросе виртуальной таблицы, особенно если статистика SQL-сервера не обновлена или план запроса построен неоптимально.

В случае с параметром (Вариант 1) оптимизатор "пробрасывает" значение конкретной ссылки внутрь запроса, сразу отсекая лишние типы и записи. В случае с соединением (Вариант 2) условие накладывается "снаружи", и из-за несоответствия типов или сложности выборки результат может потеряться.

Решение 1: Использование параметров виртуальной таблицы (Рекомендуемое)

Согласно стандартам разработки 1С, накладывать отборы на виртуальные таблицы нужно через их параметры, а не через секцию ГДЕ или условия соединения внешнего запроса. Это критически важно для производительности.

Если у вас один документ, передавайте его в параметр:


ВЫБРАТЬ
    ВалютнаяПереоценкаОбороты.Партия,
    ВалютнаяПереоценкаОбороты.СуммаПриход
ИЗ
    РегистрНакопления.ВалютнаяПереоценка.Обороты(, , , Партия = &Ссылка) КАК ВалютнаяПереоценкаОбороты

Если у вас список документов во временной таблице, используйте конструкцию В (...) внутри параметров виртуальной таблицы:


ВЫБРАТЬ
    ВТ_Отражения.Ссылка,
    ВалютнаяПереоценкаОбороты.СуммаПриход
ИЗ
    ВТ_Отражения КАК ВТ_Отражения
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ВалютнаяПереоценка.Обороты(, , , Партия В 
            (ВЫБРАТЬ Т.Ссылка ИЗ ВТ_Отражения КАК Т)
        ) КАК ВалютнаяПереоценкаОбороты
        ПО ВТ_Отражения.Ссылка = ВалютнаяПереоценкаОбороты.Партия

В этом случае платформа 1С сформирует SQL-запрос так, что отбор применится непосредственно к физическим таблицам регистра до того, как будут рассчитываться обороты. Это не только решит проблему с пустотой выборки, но и значительно ускорит выполнение запроса.

Решение 2: Явное приведение типов (ВЫРАЗИТЬ)

Если по каким-то причинам вам необходимо использовать именно соединение без параметров виртуальной таблицы (хотя это не рекомендуется), вы должны помочь платформе понять, с каким именно типом данных из составного поля Партия нужно работать.

Для этого используем оператор ВЫРАЗИТЬ. Он подсказывает системе, что нас интересуют только записи, где Партия является документом Отражение, и позволяет корректно построить соединение на уровне SQL.


ВЫБРАТЬ
    ВТ_Отражения.Ссылка,
    ВалютнаяПереоценкаОбороты.СуммаПриход
ИЗ
    ВТ_Отражения КАК ВТ_Отражения
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ВалютнаяПереоценка.Обороты КАК ВалютнаяПереоценкаОбороты
        ПО ВТ_Отражения.Ссылка = ВЫРАЗИТЬ(ВалютнаяПереоценкаОбороты.Партия КАК Документ.Отражение)

Использование ВЫРАЗИТЬ в условии соединения убирает неопределенность составного типа. Оптимизатор запросов теперь точно знает, что нужно сравнивать ссылку из ВТ_Отражения только с теми записями регистра, где Партия имеет тип Документ.Отражение.

Почему Вариант 1 (параметры) лучше Варианта 2 (соединение)

Давайте проанализируем производительность. Представим, что в вашем регистре миллион записей, а в таблице ВТ_Отражения всего один документ.

Итог

Если ваш запрос с левым соединением к регистру возвращает пустоту, хотя данные есть:

  1. Проверьте, имеет ли поле соединения составной тип.
  2. Перенесите условие отбора внутрь параметров виртуальной таблицы (конструкция Регистр.Обороты(..., Партия В (...))). Это самый надежный и быстрый способ.
  3. Если соединение неизбежно без параметров внутри, используйте оператор ВЫРАЗИТЬ(... КАК ...) для поля составного типа в условии соединения.
← На главную