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

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

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

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

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

Метод решения через временные таблицы и поиск максимальной даты

Это наиболее оптимальный и "правильный" с точки зрения производительности подход в 1С. Мы разделим задачу на несколько этапов, чтобы запрос был понятным и быстро работал.

Шаг 1. Собираем все документы в одну таблицу

Сначала нам нужно получить общий список документов, для которых мы будем искать цены. Поскольку по условию задачи нам нужны и «Приходные накладные», и «Расходные накладные», мы воспользуемся оператором ОБЪЕДИНИТЬ ВСЕ.


ВЫБРАТЬ
    ПриходнаяНакладная.Ссылка КАК Ссылка,
    ПриходнаяНакладная.Дата КАК Дата
ПОМЕСТИТЬ ВТ_ВсеДокументы
ИЗ
    Документ.ПриходнаяНакладная КАК ПриходнаяНакладная

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    РасходнаяНакладная.Ссылка,
    РасходнаяНакладная.Дата
ИЗ
    Документ.РасходнаяНакладная КАК РасходнаяНакладная
;

Шаг 2. Соединение с регистром для поиска всех предшествующих дат

Теперь нам нужно понять, какие записи в регистре «Цены номенклатуры» вообще существовали до или в день совершения каждого документа. Для этого мы соединяем нашу временную таблицу с физической таблицей регистра. Важное условие: Регистр.Период <= Документ.Дата.


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

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

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

Теперь у нас есть таблица, где каждому документу сопоставлена точная дата (период) из регистра сведений. Осталось только еще раз соединиться с регистром, чтобы забрать само числовое значение ресурса Цена. Для этой задачи есть отчет для расчета валовой прибыли по ценам на дату продажи.


ВЫБРАТЬ
    ВТ_МаксимальныеДаты.ДокументСсылка КАК Документ,
    ВТ_МаксимальныеДаты.ДокументДата КАК Дата,
    ЕСТЬNULL(ЦеныНоменклатуры.Цена, 0) КАК Цена
ИЗ
    ВТ_МаксимальныеДаты КАК ВТ_МаксимальныеДаты
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры
        ПО ВТ_МаксимальныеДаты.ПериодДляСреза = ЦеныНоменклатуры.Период
            И (ЦеныНоменклатуры.Номенклатура = &ВыбранныйТовар)
УПОРЯДОЧИТЬ ПО
    Дата

Важные нюансы реализации

Разберем несколько тонкостей, которые помогут вам защитить лабораторную работу или внедрить решение в реальную базу:

  1. Использование ЕСТЬNULL: Мы используем ЕСТЬNULL(Цена, 0), так как возможна ситуация, когда на дату документа в регистре еще вообще нет записей по этому товару. В этом случае левое соединение вернет NULL, и его правильнее заменить на ноль для корректного отображения в отчете.
  2. Периодичность регистра: В условии задачи сказано, что периодичность регистра — "день". Это упрощает задачу, так как нам достаточно сравнивать только даты. Если бы периодичность была "секунда", следовало бы учитывать МоментВремени, чтобы корректно обрабатывать изменения цен, произошедшие в ту же секунду, что и документ.
  3. Параметры запроса: Не забудьте передать в запрос параметр &ВыбранныйТовар, иначе система выдаст ошибку.
  4. Производительность: Метод с временными таблицами гораздо быстрее коррелированных подзапросов (когда запрос к регистру пишется внутри поля ВЫБРАТЬ), особенно если документов в базе несколько тысяч.

Альтернативный вариант (через подзапрос в условии соединения)

Иногда преподаватели требуют решить задачу одним "монолитным" запросом без временных таблиц. В таком случае структура будет выглядеть следующим образом:


ВЫБРАТЬ
    Доки.Ссылка,
    Доки.Дата,
    Цены.Цена
ИЗ
    (ВЫБРАТЬ Ссылка, Дата ИЗ Документ.ПриходнаяНакладная
     ОБЪЕДИНИТЬ ВСЕ
     ВЫБРАТЬ Ссылка, Дата ИЗ Документ.РасходнаяНакладная) КАК Доки
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Цены
    ПО Цены.Период = (
        ВЫБРАТЬ МАКСИМУМ(Т.Период)
        ИЗ РегистрСведений.ЦеныНоменклатуры КАК Т
        ГДЕ Т.Период <= Доки.Дата И Т.Номенклатура = &ВыбранныйТовар
    ) И Цены.Номенклатура = &ВыбранныйТовар
УПОРЯДОЧИТЬ ПО Доки.Дата

Этот вариант сложнее для понимания и отладки (поможет набор инструментов разработчика с консолью запросов и СКД), но он наглядно показывает логику поиска "среза" для каждой строки. Однако мы рекомендуем придерживаться первого способа с использованием ПОМЕСТИТЬ во временные таблицы — это стандарт современной разработки на платформе 1С:Предприятие 8.3.

← На главную