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

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

При разработке отчетов и обработок в платформе 1С:Предприятие разработчики регулярно сталкиваются с задачей: выбрать из набора данных только те записи, у которых определенный реквизит принимает максимальное (или минимальное) значение в разрезе ключевого поля — например, номенклатуры, контрагента или договора. Поскольку в языке запросов 1С отсутствуют стандартные оконные функции SQL (такие как ROW_NUMBER() или RANK() OVER (PARTITION BY ... ORDER BY ...)), реализация такого отбора требует правильного построения архитектуры запроса.

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

Типичные ошибки: почему не работают ИМЕЮЩИЕ и ПЕРВЫЕ 1

Рассмотрим, с какими сложностями сталкивается разработчик при наивных попытках отфильтровать строки:

  1. Попытка использовать конструкцию ИМЕЮЩИЕ в одном запросе. Нередко пишут условие вида ИМЕЮЩИЕ Таблица.УровеньЭлемента = МАКСИМУМ(Таблица.УровеньЭлемента). Проанализируем, как это работает на уровне платформы: условие ИМЕЮЩИЕ проверяется уже после применения предложения СГРУППИРОВАТЬ ПО. Если в группировку включены зависимые поля (например, БазовыТипЦен), платформа группирует данные по уникальной комбинации всех колонок. В итоге для каждой отдельной строки ее значение автоматически совпадает с максимумом внутри этой микрогруппы из одной строки, и отсечения не происходит — в выборку попадают абсолютно все записи.
  2. Использование ВЫБРАТЬ ПЕРВЫЕ 1 с сортировкой. Конструкция ВЫБРАТЬ ПЕРВЫЕ 1 ... УПОРЯДОЧИТЬ ПО Таблица.УровеньЭлемента УБЫВ возвращает ровно одну запись на весь результат запроса. Если в таблице хранится множество позиций номенклатуры, и по каждой требуется получить свою строку с максимумом, такой подход не подходит.
  3. Избыточные поля в предложении СГРУППИРОВАТЬ ПО. Конструктор запросов 1С автоматически требует поместить в секцию группировки все поля, к которым не применены агрегатные функции (МАКСИМУМ, СУММА и т.д.). Попытка сгруппировать таблицу сразу по номенклатуре и ее типу цены приводит к тому, что строки с разными типами цен не объединяются, и вычисление максимума происходит независимо для каждого типа цены.

Классическое решение: пакетный запрос с временной таблицей максимумов

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

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


ВЫБРАТЬ
    тз.Ссылка КАК Ссылка,
    тз.БазовыТипЦен КАК БазовыТипЦен,
    тз.УровеньЭлемента КАК УровеньЭлемента
ПОМЕСТИТЬ ВТ_ИсходныеДанные
ИЗ
    &ТаблицаИсточника КАК тз
;

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

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

Разберем ключевые детали этого алгоритма:

  1. Во втором запросе пакета мы группируем данные исключительно по полю Ссылка (номенклатуре). Никакие зависимые реквизиты (БазовыТипЦен) сюда не включаются. Это гарантирует нахождение истинного максимума для каждого объекта.
  2. Для оптимизации производительности обязательно добавляем предложение ИНДЕКСИРОВАТЬ ПО для полей соединения во временной таблице. Это критично при работе с большими объемами данных.
  3. В третьем запросе соединяем таблицу максимумов с исходными данными по составному ключу (Ссылка и УровеньЭлемента), восстанавливая связанные значения зависимых полей.

Обработка возможных дублей при совпадении максимумов

Обратим внимание на потенциальный риск: если у одной и той же номенклатуры в исходной таблице окажется две или более строки с одинаковым максимальным значением УровеньЭлемента, но с разными значениями поля БазовыТипЦен, соединение вернет дубли строк.

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


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

Альтернативное решение: выборка через соединение по неравенству (Anti-Join)

Рассмотрим еще один способ получения максимальной строки без явного использования агрегатных функций и предварительной группировки. Таблицу можно соединить саму с собой по условию «строка справа имеет уровень строго больше»:


ВЫБРАТЬ
    Т1.Ссылка КАК Ссылка,
    Т1.БазовыТипЦен КАК БазовыТипЦен,
    Т1.УровеньЭлемента КАК УровеньЭлемента
ИЗ
    ВТ_ИсходныеДанные КАК Т1
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ИсходныеДанные КАК Т2
        ПО Т1.Ссылка = Т2.Ссылка
            И Т1.УровеньЭлемента < Т2.УровеньЭлемента
ГДЕ
    Т2.Ссылка ЕСТЬ NULL

Проанализируем логику этого запроса: для максимального элемента условие Т1.УровеньЭлемента < Т2.УровеньЭлемента никогда не выполнится, поэтому правая таблица подставит NULL. Фильтр Т2.Ссылка ЕСТЬ NULL отсекает все строки, для которых нашлось хотя бы одно большее значение, оставляя только максимум.

Особенности работы запроса в отчетах СКД

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

  1. Влияние структуры группировок: В СКД результат выводится в виде иерархии группировок. Если зависимое поле БазовыТипЦен добавлено в структуру группировок строк отчета отдельным уровнем или детальной записью, компоновщик переразбивает итоговые строки.
  2. Оптимизация текста запроса компоновщиком: Если в настройках отчета отключено использование полей из соединяемой таблицы, механизм генерации запроса СКД может полностью вырезать соединение из финального SQL-текста, даже если автозаполнение отключено.
  3. Назначение ролей и ресурсов: На вкладке «Наборы данных» полям запроса могут автоматически присваиваться роли измерений или периодов, что вызывает неявную группировку. Если поле назначено ресурсом, его вывод переопределяется выражением ресурса.

Если задача решается целиком в рамках СКД, можно не усложнять запрос пакетами и соединениями, а применить функцию языка выражений СКД в вычисляемых полях или ресурсах:

ВычислитьВыражение("БазовыТипЦен", , , "Первая", "УровеньЭлемента Убыв")

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

← На главную