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