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