Как получить цену номенклатуры на дату каждого документа в запросе 1С?

Программист 1С v8.3 (Управляемые формы) 1С:Розница Управленческий учет Торговля и дистрибуция
← На главную

При разработке отчетов или обработок мы часто сталкиваемся с необходимостью получить данные о продажах из документов и дополнить их актуальными ценами на момент совершения операции. Например, проанализируем ситуацию: у нас есть табличная часть документа ЧекККМ, и нам нужно рассчитать сумму проданных товаров по двум разным видам цен — розничной и оптовой. Проблема заключается в том, что виртуальная таблица СрезПоследних умеет получать актуальные данные только на одну конкретную дату, переданную в параметры, а нам нужно получать разные цены для каждого отдельного чека, исходя из его времени проведения.

Разберем по шагам, почему "в лоб" эта задача не решается, какие ошибки часто допускают разработчики и как правильно спроектировать запрос или настроить СКД для получения нужного результата.

Разбор типичных ошибок при соединении с регистром сведений

Рассмотрим подход, который изначально попытался реализовать автор вопроса. Он пытался связать временную таблицу документов с физической таблицей регистра ЦеныНоменклатуры по условию, что дата документа больше или равна дате установки цены.

Выясним причину, почему такой подход (или его вариации) работает плохо или выдает нулевые цены:

  1. Проблема производительности: Прямое соединение таблицы чеков с физической таблицей регистра по условию >= — это самый ресурсозатратный способ. Система берет каждую строку чека и перебирает тысячи записей регистра цен. На реальных базах данных такой запрос может выполняться часами.
  2. Неправильное использование левого соединения: Если мы используем левое соединение (ЛЕВОЕ СОЕДИНЕНИЕ), но при этом накладываем фильтр по виду цены в секции ГДЕ (например, ГДЕ ЦеныНоменклатуры.ВидЦены = &ВидЦены), запрос неявно превращается во внутреннее соединение. Если оптовая цена для товара не задана, строка с продажей из чека вообще исчезнет из отчета! Условие отбора по правой таблице всегда нужно переносить в секцию ПО.
  3. Ошибки при объединении таблиц: При использовании ОБЪЕДИНИТЬ ВСЕ для сбора оптовых и розничных цен в одну выборку необходимо правильно группировать итоговый результат. Иначе для одной номенклатуры мы получим две разные строки в отчете: в одной будет заполнена оптовая цена (а розничная равна 0), а в другой — розничная.
  4. Игнорирование измерений регистра: Если цены в базе устанавливаются в разрезе характеристик или упаковок, соединение только по полю Номенклатура приведет к дублированию строк или возврату пустых значений.

Решение 1: Оптимальный запрос через временные таблицы (классический метод)

Если мы пишем код программно (без СКД), нам необходимо вручную сэмулировать работу среза последних для каждой даты документа. Разберем правильный алгоритм. Для реализации этой задачи на практике можно использовать методику построения запросов для получения среза последних на каждую дату.

Шаг 1. Получаем уникальные пары "Номенклатура + Дата" из документов. Обязательно проверяем статус чека и его проведенность, чтобы не цеплять отмененные операции.

Шаг 2. Ищем максимальную дату установки цены, которая меньше или равна дате нашего чека. Это делается левым соединением нашей ВТ с физической таблицей ЦеныНоменклатуры и последующей группировкой с функцией МАКСИМУМ по периоду.

Шаг 3. Зная точную дату (секунду) установки актуальной цены для каждого чека, мы еще раз соединяемся с таблицей регистра, чтобы забрать саму сумму цены.

Посмотрим на пример правильного структурирования такого запроса:


// 1. Получаем продажи (уникальные даты и товары)
ВЫБРАТЬ
    ЧекККМТовары.Номенклатура КАК Номенклатура,
    ЧекККМТовары.Ссылка.Дата КАК ПериодЧека,
    ЧекККМТовары.Характеристика КАК Характеристика,
    СУММА(ЧекККМТовары.Количество) КАК Количество,
    СУММА(ЧекККМТовары.Сумма) КАК СуммаПродажи
ПОМЕСТИТЬ ВТ_Продажи
ИЗ
    Документ.ЧекККМ.Товары КАК ЧекККМТовары
ГДЕ
    ЧекККМТовары.Ссылка.Проведен = ИСТИНА
    И ЧекККМТовары.Ссылка.СтатусЧекаККМ = ЗНАЧЕНИЕ(Перечисление.СтатусыЧековККМ.Пробит)
    И ЧекККМТовары.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
    ЧекККМТовары.Номенклатура,
    ЧекККМТовары.Ссылка.Дата,
    ЧекККМТовары.Характеристика;

// 2. Ищем даты последних установленных цен (ОПТ)
ВЫБРАТЬ
    ВТ_Продажи.Номенклатура КАК Номенклатура,
    ВТ_Продажи.Характеристика КАК Характеристика,
    ВТ_Продажи.ПериодЧека КАК ПериодЧека,
    МАКСИМУМ(ЦеныНоменклатуры.Период) КАК ПериодЦены
ПОМЕСТИТЬ ВТ_ПериодыЦенОпт
ИЗ
    ВТ_Продажи КАК ВТ_Продажи
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры
        ПО ВТ_Продажи.Номенклатура = ЦеныНоменклатуры.Номенклатура
            И ВТ_Продажи.Характеристика = ЦеныНоменклатуры.Характеристика
            И ВТ_Продажи.ПериодЧека >= ЦеныНоменклатуры.Период
            И (ЦеныНоменклатуры.ВидЦены = &ОптовыйВидЦены)
СГРУППИРОВАТЬ ПО
    ВТ_Продажи.Номенклатура,
    ВТ_Продажи.Характеристика,
    ВТ_Продажи.ПериодЧека;

// 3. Получаем сами цены и соединяем с продажами
ВЫБРАТЬ
    ВТ_Продажи.ПериодЧека КАК ПериодЧека,
    ВТ_Продажи.Номенклатура КАК Номенклатура,
    ВТ_Продажи.Количество КАК Количество,
    ВТ_Продажи.СуммаПродажи КАК СуммаПродажи,
    ЕСТЬNULL(ЦеныНоменклатуры.Цена, 0) КАК ЦенаОптовая,
    ЕСТЬNULL(ЦеныНоменклатуры.Цена, 0) * ВТ_Продажи.Количество КАК СуммаОптовая
ИЗ
    ВТ_Продажи КАК ВТ_Продажи
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ПериодыЦенОпт КАК ВТ_ПериодыЦенОпт
        ПО ВТ_Продажи.Номенклатура = ВТ_ПериодыЦенОпт.Номенклатура
            И ВТ_Продажи.Характеристика = ВТ_ПериодыЦенОпт.Характеристика
            И ВТ_Продажи.ПериодЧека = ВТ_ПериодыЦенОпт.ПериодЧека
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры
        ПО ВТ_ПериодыЦенОпт.Номенклатура = ЦеныНоменклатуры.Номенклатура
            И ВТ_ПериодыЦенОпт.Характеристика = ЦеныНоменклатуры.Характеристика
            И ВТ_ПериодыЦенОпт.ПериодЦены = ЦеныНоменклатуры.Период
            И (ЦеныНоменклатуры.ВидЦены = &ОптовыйВидЦены)

Важный момент: Обратите внимание на использование функции ЕСТЬNULL. При математических операциях сложения или умножения, если хотя бы один аргумент равен пустоте (NULL), весь результат превратится в NULL. Поэтому отсутствующие цены нужно принудительно заменять на ноль. Кроме того, при работе со сложными пакетными запросами с несколькими временными таблицами полезно использовать инструмент для автоматической расстановки инструкций удаления ВТ.

Решение 2: Использование возможностей СКД (Самый современный способ)

Если ваша конечная цель — вывод данных в отчет, то строить многоэтажные запросы с временными таблицами совершенно излишне. Система компоновки данных (СКД) имеет встроенный, оптимизированный механизм для получения среза последних на каждую дату.

Рассмотрим подробнее алгоритм настройки в СКД:

  1. Создайте Набор данных 1 (Запрос). В нем выберите данные только из регистра накопления или табличной части документов продаж (в нашем случае это ЧекККМ). Получите оттуда Номенклатуру, Характеристику, Дату чека, Количество и Сумму.
  2. Создайте Набор данных 2 (Запрос). В нем обратитесь к виртуальной таблице РегистрСведений.ЦеныНоменклатуры.СрезПоследних. Выберите поля: Номенклатура, Характеристика, ВидЦены и Цена. Никаких параметров в тексте запроса второго набора указывать не нужно!
  3. Перейдите на вкладку Связи наборов данных. Укажите источником связи Набор 1 (продажи), а приемником — Набор 2 (цены).
  4. В условиях связи свяжите поля Номенклатура и Характеристика.
  5. В поле Параметр (в этой же строке связи) выберите стандартный параметр СКД — Период.
  6. В колонке Выражение источника выберите поле ПериодЧека (или Дата документа) из вашего первого набора.

Что произойдет "под капотом"? СКД сама сгруппирует все уникальные даты из документов продаж и выполнит серию оптимизированных запросов к регистру цен, передавая каждую дату в качестве параметра периода для виртуальной таблицы СрезПоследних. Вы получите точные цены на момент проведения каждого чека без написания сложного SQL-кода.

Дополнительные проверки при пустых ценах

Если после применения любого из методов цены всё равно выводятся равными нулю, проанализируем ситуацию по следующим пунктам:

  1. Валюта цены: Если оптовая цена задана в USD, а документ оформляется в рублях, убедитесь, что вы не забыли добавить в запрос логику пересчета валют через регистр курсов валют на дату документа.
  2. Магазины и форматы: В конфигурации "Розница" (где активно используется ЧекККМ) цены часто привязываются не глобально, а к форматам магазинов. Проверьте, действует ли выбранный вами розничный или оптовый вид цены в конкретном магазине, по которому строится отчет. Возможно, цены лежат в регистре ДействующиеЦеныНоменклатуры, а не в основном.
  3. Упаковки: Если товар продается в штуках, а цена в регистре установлена за коробку, прямое соединение по номенклатуре не сработает корректно. Обязательно учитывайте коэффициент упаковок.
← На главную