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