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

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

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

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

Анализ проблемы: почему группировки по периоду недостаточно

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


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

Проанализируем ситуацию: если по номенклатуре "Кирпич красный" 01.01.2023 в 12:00:00 было два документа поступления (№1 с ценой 10 руб. и №2 с ценой 12 руб.), то условие ЗакупкиОбороты.Период = МинимальныйПериод.Период выберет оба этих документа. В результате мы получим две строки вместо одной «начальной» цены. Чтобы этого избежать, нам нужно найти уникальный идентификатор, который позволит отличить один документ от другого даже в пределах одной секунды.

Решение 1: Использование Момента времени

Самый надежный способ определить порядок документов в 1С — это использование поля МоментВремени. Объект МоментВремени включает в себя не только дату и время, но и ссылку на сам документ. При сравнении двух моментов времени система сначала сравнивает время, а при равенстве времени — уникальные идентификаторы ссылок (GUID). Это гарантирует, что один документ всегда будет «раньше» или «позже» другого.

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


ВЫБРАТЬ
    РН_Закупки.Регистратор,
    РН_Закупки.Номенклатура,
    РН_Закупки.МоментВремени
ПОМЕСТИТЬ ВТ_Закупки
ИЗ
    РегистрНакопления.Закупки КАК РН_Закупки
ГДЕ
    РН_Закупки.Активность
    И РН_Закупки.Период МЕЖДУ &НачалоПериода И &КонецПериода;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ РАЗЛИЧНЫЕ
    ВТ_Закупки.Номенклатура,
    ВТ_Закупки.Регистратор
ИЗ
    ВТ_Закупки КАК ВТ_Закупки
ГДЕ
    ВТ_Закупки.Регистратор В
            (ВЫБРАТЬ ПЕРВЫЕ 1
                Отбор.Регистратор
            ИЗ
                ВТ_Закупки КАК Отбор
            ГДЕ
                Отбор.Номенклатура = ВТ_Закупки.Номенклатура
            УПОРЯДОЧИТЬ ПО
                Отбор.МоментВремени ВОЗР)

В этом примере мы используем конструкцию ВЫБРАТЬ ПЕРВЫЕ 1 внутри коррелированного подзапроса. Для каждой строки основной таблицы ВТ_Закупки система ищет один самый ранний регистратор, упорядоченный по МоментВремени.

Решение 2: Оптимизация для больших объемов данных (Временные таблицы)

Хотя предыдущее решение работает корректно, использование подзапроса в условии ГДЕ может существенно замедлить выполнение запроса на больших базах данных (десятки тысяч строк и более). Для оптимизации мы рекомендуем использовать метод с предварительным получением минимального момента времени.

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

  1. Выбираем минимальный МоментВремени для каждой номенклатуры во временную таблицу.
  2. Индексируем временную таблицу для быстрого соединения.
  3. Соединяем временную таблицу с основной таблицей регистра по полям Номенклатура и МоментВремени.

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

////////////////////////////////////////////////////////////////////////////////
ИНДЕКСИРОВАТЬ ПО
    ВТ_МинМоменты.Номенклатура,
    ВТ_МинМоменты.МинМомент;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Закупки.Номенклатура,
    Закупки.Регистратор,
    Закупки.Цена,
    Закупки.Количество
ИЗ
    ВТ_МинМоменты КАК МинМоменты
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.Закупки КАК Закупки
    ПО МинМоменты.Номенклатура = Закупки.Номенклатура
    И МинМоменты.МинМомент = Закупки.МоментВремени

Важный нюанс: Поле МоментВремени является составным типом данных на уровне базы данных. Использование МИНИМУМ(МоментВремени) поддерживается платформой 1С и позволяет эффективно находить самую раннюю запись.

Решение 3: Что делать, если в одном документе несколько строк?

Иногда возникают ситуации, когда одна и та же номенклатура указана в документе несколько раз (например, с разной ценой или по разным характеристикам). В этом случае даже МоментВремени не поможет, так как он одинаков для всех строк одного документа-регистратора.

Для решения этой специфической задачи нам потребуется поле НомерСтроки. Рассмотрим алгоритм действий:

  1. Сначала найдем минимальный МоментВремени (как в Решении 2).
  2. Затем для этого момента времени найдем минимальный НомерСтроки.
  3. Только после этого получим данные конкретной строки.

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


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

Подведение итогов и рекомендации

Мы рассмотрели несколько способов получения первых записей в запросе. Выбор конкретного метода зависит от ваших требований к производительности и сложности данных:

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

← На главную