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

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

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

Сложность заключается в том, что стандартная виртуальная таблица СрезПоследних регистра сведений позволяет получить данные только на одну конкретную дату, передаваемую в параметрах запроса. Если нам нужно получить цены на 100 разных дат для 100 разных документов, использование СрезПоследних в классическом виде становится невозможным. Рассмотрим, как эффективно решить эту задачу с помощью механизмов языка запросов 1С.

Почему нельзя использовать СрезПоследних в цикле или простом соединении

Многие начинающие разработчики пытаются решить задачу "в лоб", делая запросы в цикле. Мы категорически не рекомендуем этот подход, так как это создает огромную нагрузку на сервер базы данных. Другая типичная ошибка — попытка левого соединения таблицы документов с виртуальной таблицей СрезПоследних. Проблема в том, что в виртуальную таблицу нельзя передать поле Период из основной таблицы в качестве параметра даты. В итоге запрос либо выдаст ошибку, либо вернет цены на текущий момент (или на дату, указанную в параметре &Период), что будет логически неверно.

Алгоритм решения задачи: Соединение — Временная таблица — Соединение

Для корректного получения цен на разные даты мы воспользуемся проверенной схемой, состоящей из трех основных этапов. Разберем по шагам, как построить такой запрос.

Шаг 1. Подготовка исходных данных

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

Шаг 2. Поиск ближайшей даты установки цены

На этом этапе мы соединяем нашу временную таблицу с физической таблицей регистра сведений ЦеныНоменклатуры. Условие соединения: номенклатура совпадает, а период записи в регистре цен меньше или равен дате нашего документа. С помощью агрегатной функции МАКСИМУМ(Период) мы находим ту самую "последнюю" дату установки цены перед (или в день) совершения операции. Результат сохраняем во временную таблицу втМаксПериод.

Шаг 3. Получение итоговой цены

Теперь, когда у нас есть уникальная связка "Номенклатура + Дата документа + Точная дата записи в регистре цен", мы снова соединяемся с физической таблицей регистра сведений ЦеныНоменклатуры, но теперь уже по точному совпадению периода. Это гарантирует нам получение актуальной цены без задвоений.

Пример реализации запроса

Рассмотрим практический пример кода. Предположим, нам нужно получить цены по регистру Продажи в разрезе каждого дня реализации.


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

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

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

Анализируем важные технические нюансы

Проанализируем ситуацию, которая часто встречается в реальных базах данных. Простого соединения по номенклатуре и периоду может быть недостаточно. Рассмотрим дополнительные факторы, которые могут повлиять на результат:

  1. Проблема дублирования периодов: Если в вашей системе возможна запись двух разных цен на одну и ту же номенклатуру в одну и ту же секунду (например, при массовой загрузке данных), то функция МАКСИМУМ(Период) вернет одну дату, но соединение на Шаге 3 выдаст две строки. Чтобы этого избежать, необходимо добавлять в условия соединения дополнительные измерения, такие как Характеристика или ЕдиницаИзмерения, либо использовать уникальный номер записи.
  2. Учет характеристик номенклатуры: В современных конфигурациях (ERP, УТ 11, КА) цены почти всегда хранятся в разрезе характеристик. Если вы не укажете условие по характеристике, запрос может вернуть неверную цену или перемножить строки. Всегда проверяйте, используется ли в регистре ЦеныНоменклатуры измерение Характеристика. Если в документе характеристика пустая, в соединении нужно явно указывать: Цены.Характеристика = ЗНАЧЕНИЕ(Справочник.ХарактеристикиНоменклатуры.ПустаяСсылка).
  3. Оптимизация производительности: Обратите внимание на использование ключевого слова ИНДЕКСИРОВАТЬ ПО во временных таблицах. Индексация по полям связи (Номенклатура, Период) критически важна при больших объемах данных. Без индексов SQL-сервер будет выполнять полное сканирование таблиц, что приведет к существенному замедлению работы системы.
  4. Работа с валютами: Часто цены хранятся в валюте поставщика, а отчет нужно построить в рублях. В этом случае алгоритм усложняется: вам нужно аналогичным трехэтапным методом получить курс валюты из регистра КурсыВалют на дату каждого документа, а затем перемножить полученную цену на курс.
  5. Обработка отсутствующих цен: Мы рекомендуем использовать функцию ЕСТЬNULL(Цена, 0) в итоговой выборке. Если на дату документа цена еще ни разу не была установлена, левое соединение вернет NULL, что может привести к ошибкам в арифметических операциях в коде 1С.

Альтернативный метод: Интервальные запросы

Выясним причину, по которой иногда стоит рассмотреть другой способ. В очень редких случаях, когда записей в регистре цен миллионы, а документов тысячи, метод с МАКСИМУМ(Период) может работать медленно из-за специфики выполнения GROUP BY в SQL. Альтернативой является построение таблицы интервалов, где для каждой записи о цене вычисляется "Дата начала" и "Дата окончания" (следующая дата изменения цены). Тогда условие соединения выглядит как: ДатаДокумента МЕЖДУ РегистрЦен.Период И РегистрЦен.ДатаКонца. Однако реализация такого "смещения" дат в языке запросов 1С технически более громоздка и требует дополнительного соединения регистра самого с собой.

Подводя итог, можно сказать, что описанный метод с использованием трех последовательных временных таблиц является "золотым стандартом" в 1С. Он обеспечивает отличный баланс между читаемостью кода и производительностью системы, позволяя за один вызов сервера получить всю необходимую информацию о стоимостных показателях в разрезе произвольного количества дат.

← На главную