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