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