При разработке отчетов или механизмов проведения документов в 1С мы часто сталкиваемся с необходимостью получить данные из регистров накопления, отфильтрованные по конкретному документу или списку документов. Казалось бы, задача тривиальная, но иногда стандартные конструкции языка запросов ведут себя непредсказуемо.
Разберем конкретную ситуацию, с которой сталкиваются многие разработчики: почему при использовании условия в секции ГДЕ данные выводятся, а при попытке сделать ЛЕВОЕ СОЕДИНЕНИЕ с тем же условием результат оказывается пустым. Проанализируем причины такого поведения платформы и SQL-сервера, а также рассмотрим правильные способы решения этой задачи.
Рассмотрим исходные данные. У нас есть самописный регистр накопления ВалютнаяПереоценка. У него есть измерение Партия, которое имеет составной тип данных (например, включает документы Отражение и ВалютнаяОперация).
Мы пытаемся получить обороты по конкретному документу Отражение. Разработчик пробует два варианта запроса.
Вариант 1 (Рабочий):
В параметрах виртуальной таблицы или в секции ГДЕ устанавливается прямой отбор на измерение.
ВЫБРАТЬ
ВалютнаяПереоценкаОбороты.СуммаПриход
ИЗ
РегистрНакопления.ВалютнаяПереоценка.Обороты(, , , Партия = &Ссылка) КАК ВалютнаяПереоценкаОбороты
Этот запрос отрабатывает корректно и возвращает записи.
Вариант 2 (Проблемный):
Мы помещаем интересующий нас документ во временную таблицу (или используем таблицу документа напрямую) и пытаемся соединить её с виртуальной таблицей оборотов.
ВЫБРАТЬ
ВТ_Отражения.Ссылка,
ВалютнаяПереоценкаОбороты.СуммаПриход
ИЗ
ВТ_Отражения КАК ВТ_Отражения
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ВалютнаяПереоценка.Обороты КАК ВалютнаяПереоценкаОбороты
ПО ВТ_Отражения.Ссылка = ВалютнаяПереоценкаОбороты.Партия
Результат этого запроса часто оказывается пустым (NULL в полях регистра), даже если движения по документу точно есть.
Почему так происходит? Здесь играют роль два фактора: природа виртуальных таблиц и особенности работы с составными типами данных на уровне SQL.
РегистрНакопления.ВалютнаяПереоценка.Обороты, платформа генерирует сложный SQL-запрос, который обращается к таблицам итогов или таблицам движений. Если вы не передаете параметры внутрь виртуальной таблицы (в скобки), система пытается построить таблицу оборотов по всем данным регистра, а уже потом соединить её с вашей таблицей.Партия может содержать ссылки на разные виды документов. На уровне базы данных (SQL) это поле представлено несколькими колонками (обычно это ID типа и ID ссылки). Когда мы делаем соединение ПО ВТ_Отражения.Ссылка = ВалютнаяПереоценкаОбороты.Партия, оптимизатор SQL не всегда может корректно сопоставить конкретный тип ссылки из временной таблицы с составным полем в подзапросе виртуальной таблицы, особенно если статистика SQL-сервера не обновлена или план запроса построен неоптимально.В случае с параметром (Вариант 1) оптимизатор "пробрасывает" значение конкретной ссылки внутрь запроса, сразу отсекая лишние типы и записи. В случае с соединением (Вариант 2) условие накладывается "снаружи", и из-за несоответствия типов или сложности выборки результат может потеряться.
Согласно стандартам разработки 1С, накладывать отборы на виртуальные таблицы нужно через их параметры, а не через секцию ГДЕ или условия соединения внешнего запроса. Это критически важно для производительности.
Если у вас один документ, передавайте его в параметр:
ВЫБРАТЬ
ВалютнаяПереоценкаОбороты.Партия,
ВалютнаяПереоценкаОбороты.СуммаПриход
ИЗ
РегистрНакопления.ВалютнаяПереоценка.Обороты(, , , Партия = &Ссылка) КАК ВалютнаяПереоценкаОбороты
Если у вас список документов во временной таблице, используйте конструкцию В (...) внутри параметров виртуальной таблицы:
ВЫБРАТЬ
ВТ_Отражения.Ссылка,
ВалютнаяПереоценкаОбороты.СуммаПриход
ИЗ
ВТ_Отражения КАК ВТ_Отражения
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ВалютнаяПереоценка.Обороты(, , , Партия В
(ВЫБРАТЬ Т.Ссылка ИЗ ВТ_Отражения КАК Т)
) КАК ВалютнаяПереоценкаОбороты
ПО ВТ_Отражения.Ссылка = ВалютнаяПереоценкаОбороты.Партия
В этом случае платформа 1С сформирует SQL-запрос так, что отбор применится непосредственно к физическим таблицам регистра до того, как будут рассчитываться обороты. Это не только решит проблему с пустотой выборки, но и значительно ускорит выполнение запроса.
Если по каким-то причинам вам необходимо использовать именно соединение без параметров виртуальной таблицы (хотя это не рекомендуется), вы должны помочь платформе понять, с каким именно типом данных из составного поля Партия нужно работать.
Для этого используем оператор ВЫРАЗИТЬ. Он подсказывает системе, что нас интересуют только записи, где Партия является документом Отражение, и позволяет корректно построить соединение на уровне SQL.
ВЫБРАТЬ
ВТ_Отражения.Ссылка,
ВалютнаяПереоценкаОбороты.СуммаПриход
ИЗ
ВТ_Отражения КАК ВТ_Отражения
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ВалютнаяПереоценка.Обороты КАК ВалютнаяПереоценкаОбороты
ПО ВТ_Отражения.Ссылка = ВЫРАЗИТЬ(ВалютнаяПереоценкаОбороты.Партия КАК Документ.Отражение)
Использование ВЫРАЗИТЬ в условии соединения убирает неопределенность составного типа. Оптимизатор запросов теперь точно знает, что нужно сравнивать ссылку из ВТ_Отражения только с теми записями регистра, где Партия имеет тип Документ.Отражение.
Давайте проанализируем производительность. Представим, что в вашем регистре миллион записей, а в таблице ВТ_Отражения всего один документ.
Если ваш запрос с левым соединением к регистру возвращает пустоту, хотя данные есть:
Регистр.Обороты(..., Партия В (...))). Это самый надежный и быстрый способ.ВЫРАЗИТЬ(... КАК ...) для поля составного типа в условии соединения.