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