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

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

В практике разработки на платформе 1С часто возникает ситуация, когда стандартная виртуальная таблица СрезПоследних периодического регистра сведений работает не так, как ожидает программист. Типичный пример: в регистре «Цены номенклатуры» есть измерения Номенклатура, ТипЦен и Валюта. Если для одной позиции номенклатуры установлены цены в разных валютах (например, в USD и EUR), стандартный запрос к СрезПоследних вернет обе записи, так как платформа считает уникальной комбинацию всех измерений. Но что делать, если нам нужна только одна, самая последняя цена, независимо от того, в какой валюте она была установлена?

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

Почему стандартный СрезПоследних возвращает «лишние» данные

Важно понимать внутреннюю механику платформы. Виртуальная таблица СрезПоследних не является физической таблицей в базе данных (за исключением некоторых случаев в версии 8.3, о которых мы скажем позже). При обращении к ней 1С генерирует SQL-запрос, который ищет максимальный период для каждой уникальной комбинации измерений.

Если в вашем регистре три измерения — Номенклатура, Валюта и ТипЦен, то система найдет последнюю запись для (Товар 1, USD, Оптовая), последнюю для (Товар 1, EUR, Оптовая) и так далее. Если ваша цель — получить просто последнюю цену по полю Номенклатура, игнорируя Валюта, стандартная виртуальная таблица вам не поможет, так как она обязана учитывать весь ключ записей.

Решение №1: Использование подзапроса в условии ГДЕ

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

Рассмотрим пример такого запроса:


ВЫБРАТЬ
    Т1.Период КАК Период,
    Т1.Номенклатура КАК Номенклатура,
    Т1.Валюта КАК Валюта,
    Т1.Цена КАК Цена
ИЗ
    РегистрСведений.ЦеныНоменклатуры КАК Т1
ГДЕ
    Т1.Период В (
        ВЫБРАТЬ
            МАКСИМУМ(Т2.Период)
        ИЗ
            РегистрСведений.ЦеныНоменклатуры КАК Т2
        ГДЕ
            Т1.Номенклатура = Т2.Номенклатура
    )

Преимущества: этот код компактен и нагляден. Он позволяет получить срез по любому произвольному набору полей (в данном случае — только по номенклатуре).

Важное ограничение: этот метод может работать крайне медленно на больших объемах данных. СУБД придется выполнять вложенный запрос для каждой строки основной таблицы (коррелированный подзапрос), что часто приводит к сканированию всей таблицы (Table Scan) вместо использования индексов.

Решение №2: Использование временных таблиц (Рекомендуемый метод)

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

Посмотрим на алгоритм реализации по шагам:

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

Пример кода запроса:


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

////////////////////////////////////////////////////////////////////////////////
// Шаг 2: Получаем данные (валюту и цену) на эти даты
ВЫБРАТЬ
    ВТ.Период КАК Период,
    ВТ.Номенклатура КАК Номенклатура,
    Цены.Валюта КАК Валюта,
    Цены.Цена КАК Цена
ИЗ
    ВТ_ПоследниеДаты КАК ВТ
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Цены
        ПО ВТ.Номенклатура = Цены.Номенклатура
        И ВТ.Период = Цены.Период

Этот метод значительно быстрее, так как платформа эффективно использует индексы по полям Номенклатура + Период.

Проблема коллизий: Что если две цены установлены в одну секунду?

Выясним важную деталь: в периодических регистрах 1С точность периода обычно составляет 1 секунду. Если в одну и ту же секунду были введены две разные записи (например, цена в USD и цена в EUR), то даже использование МАКСИМУМ(Период) вернет две строки для одной номенклатуры. Это и есть техническая коллизия.

Чтобы гарантированно получить только одну запись, нам нужно добавить дополнительное правило выбора. Например, выбирать максимальную валюту или использовать уникальный идентификатор записи.

Проанализируем пример решения через АВТОНОМЕРЗАПИСИ (доступно во временных таблицах):


// 1. Помещаем все возможные «последние» записи во временную таблицу
ВЫБРАТЬ
    Цены.Период КАК Период,
    Цены.Номенклатура КАК Номенклатура,
    Цены.Валюта КАК Валюта,
    Цены.Цена КАК Цена
ПОМЕСТИТЬ ВТ_ВсеПоследние
ИЗ
    РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, ) КАК Цены;

////////////////////////////////////////////////////////////////////////////////
// 2. Выбираем из них только одну (например, с максимальной валютой или через автономер)
ВЫБРАТЬ
    ВТ.Номенклатура КАК Номенклатура,
    МАКСИМУМ(ВТ.Период) КАК Период,
    МАКСИМУМ(ВТ.Валюта) КАК Валюта
ПОМЕСТИТЬ ВТ_ЕдинственныеЗаписи
ИЗ
    ВТ_ВсеПоследние КАК ВТ
СГРУППИРОВАТЬ ПО
    ВТ.Номенклатура;

////////////////////////////////////////////////////////////////////////////////
// 3. Финальная выборка цен
ВЫБРАТЬ
    ВТ.Номенклатура,
    ВТ.Период,
    ВТ.Валюта,
    Цены.Цена
ИЗ
    ВТ_ЕдинственныеЗаписи КАК ВТ
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ВсеПоследние КАК Цены
        ПО ВТ.Номенклатура = Цены.Номенклатура
        И ВТ.Период = Цены.Период
        И ВТ.Валюта = Цены.Валюта

Использование СКД для решения задачи

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

Выполните следующие действия в конструкторе СКД:

  1. Добавьте в запрос выборку из физической таблицы регистра (а не из виртуальной).
  2. На вкладке «Набор данных» найдите колонку «Роль».
  3. Для поля Период укажите роль «Период» и порядковый номер.
  4. Для поля Номенклатура укажите роль «Измерение».
  5. Для полей Валюта и ТипЦен не указывайте роль «Измерение».

В этом случае СКД сама сформирует SQL-запрос таким образом, чтобы схлопнуть результат до последней записи в разрезе указанных вами измерений.

Технические нюансы и производительность

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

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

← На главную