При разработке отчетов и обработок в платформе 1С:Предприятие разработчики регулярно сталкиваются с задачей: выбрать из набора данных только те записи, у которых определенный реквизит принимает максимальное (или минимальное) значение в разрезе ключевого поля — например, номенклатуры, контрагента или договора. Поскольку в языке запросов 1С отсутствуют стандартные оконные функции SQL (такие как ROW_NUMBER() или RANK() OVER (PARTITION BY ... ORDER BY ...)), реализация такого отбора требует правильного построения архитектуры запроса.
Разберем типичные ошибки при решении этой задачи, рассмотрим рабочий алгоритм через пакетный запрос с временной таблицей максимумов, а также проанализируем нюансы вывода данных в системе компоновки данных (СКД).
Рассмотрим, с какими сложностями сталкивается разработчик при наивных попытках отфильтровать строки:
ИМЕЮЩИЕ Таблица.УровеньЭлемента = МАКСИМУМ(Таблица.УровеньЭлемента). Проанализируем, как это работает на уровне платформы: условие ИМЕЮЩИЕ проверяется уже после применения предложения СГРУППИРОВАТЬ ПО. Если в группировку включены зависимые поля (например, БазовыТипЦен), платформа группирует данные по уникальной комбинации всех колонок. В итоге для каждой отдельной строки ее значение автоматически совпадает с максимумом внутри этой микрогруппы из одной строки, и отсечения не происходит — в выборку попадают абсолютно все записи.ВЫБРАТЬ ПЕРВЫЕ 1 ... УПОРЯДОЧИТЬ ПО Таблица.УровеньЭлемента УБЫВ возвращает ровно одну запись на весь результат запроса. Если в таблице хранится множество позиций номенклатуры, и по каждой требуется получить свою строку с максимумом, такой подход не подходит.МАКСИМУМ, СУММА и т.д.). Попытка сгруппировать таблицу сразу по номенклатуре и ее типу цены приводит к тому, что строки с разными типами цен не объединяются, и вычисление максимума происходит независимо для каждого типа цены.Выясним правильную последовательность действий. Чтобы изолировать вычисление максимума от лишних реквизитов, разобьем задачу на два логических шага: сначала найдем максимальное значение в разрезе ключевого поля, а затем соединим полученный срез с исходными данными для извлечения зависимых колонок.
Посмотрим на практический пример реализации такого пакета запросов:
ВЫБРАТЬ
тз.Ссылка КАК Ссылка,
тз.БазовыТипЦен КАК БазовыТипЦен,
тз.УровеньЭлемента КАК УровеньЭлемента
ПОМЕСТИТЬ ВТ_ИсходныеДанные
ИЗ
&ТаблицаИсточника КАК тз
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
тз.Ссылка КАК Ссылка,
МАКСИМУМ(тз.УровеньЭлемента) КАК УровеньЭлемента
ПОМЕСТИТЬ ВТ_Максимумы
ИЗ
ВТ_ИсходныеДанные КАК тз
СГРУППИРОВАТЬ ПО
тз.Ссылка
ИНДЕКСИРОВАТЬ ПО
Ссылка,
УровеньЭлемента
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_Максимумы.Ссылка КАК Ссылка,
ВТ_Максимумы.УровеньЭлемента КАК УровеньЭлемента,
ВТ_ИсходныеДанные.БазовыТипЦен КАК БазовыТипЦен
ИЗ
ВТ_Максимумы КАК ВТ_Максимумы
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ИсходныеДанные КАК ВТ_ИсходныеДанные
ПО ВТ_Максимумы.Ссылка = ВТ_ИсходныеДанные.Ссылка
И ВТ_Максимумы.УровеньЭлемента = ВТ_ИсходныеДанные.УровеньЭлемента
Разберем ключевые детали этого алгоритма:
БазовыТипЦен) сюда не включаются. Это гарантирует нахождение истинного максимума для каждого объекта.ИНДЕКСИРОВАТЬ ПО для полей соединения во временной таблице. Это критично при работе с большими объемами данных.Ссылка и УровеньЭлемента), восстанавливая связанные значения зависимых полей.Обратим внимание на потенциальный риск: если у одной и той же номенклатуры в исходной таблице окажется две или более строки с одинаковым максимальным значением УровеньЭлемента, но с разными значениями поля БазовыТипЦен, соединение вернет дубли строк.
Чтобы гарантировать выборку строго одной записи на каждую ссылку, применим агрегацию к зависимому полю в итоговом запросе:
ВЫБРАТЬ
ВТ_Максимумы.Ссылка КАК Ссылка,
ВТ_Максимумы.УровеньЭлемента КАК УровеньЭлемента,
МАКСИМУМ(ВТ_ИсходныеДанные.БазовыТипЦен) КАК БазовыТипЦен
ИЗ
ВТ_Максимумы КАК ВТ_Максимумы
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ИсходныеДанные КАК ВТ_ИсходныеДанные
ПО ВТ_Максимумы.Ссылка = ВТ_ИсходныеДанные.Ссылка
И ВТ_Максимумы.УровеньЭлемента = ВТ_ИсходныеДанные.УровеньЭлемента
СГРУППИРОВАТЬ ПО
ВТ_Максимумы.Ссылка,
ВТ_Максимумы.УровеньЭлемента
Рассмотрим еще один способ получения максимальной строки без явного использования агрегатных функций и предварительной группировки. Таблицу можно соединить саму с собой по условию «строка справа имеет уровень строго больше»:
ВЫБРАТЬ
Т1.Ссылка КАК Ссылка,
Т1.БазовыТипЦен КАК БазовыТипЦен,
Т1.УровеньЭлемента КАК УровеньЭлемента
ИЗ
ВТ_ИсходныеДанные КАК Т1
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ИсходныеДанные КАК Т2
ПО Т1.Ссылка = Т2.Ссылка
И Т1.УровеньЭлемента < Т2.УровеньЭлемента
ГДЕ
Т2.Ссылка ЕСТЬ NULL
Проанализируем логику этого запроса: для максимального элемента условие Т1.УровеньЭлемента < Т2.УровеньЭлемента никогда не выполнится, поэтому правая таблица подставит NULL. Фильтр Т2.Ссылка ЕСТЬ NULL отсекает все строки, для которых нашлось хотя бы одно большее значение, оставляя только максимум.
Иногда разработчики сталкиваются с ситуацией, когда в консоли запросов результат формируется корректно, а при встраивании того же кода в систему компоновки данных (СКД) строки двоятся или отчет возвращает неожиданные данные. Проанализируем возможные причины такого поведения:
БазовыТипЦен добавлено в структуру группировок строк отчета отдельным уровнем или детальной записью, компоновщик переразбивает итоговые строки.Если задача решается целиком в рамках СКД, можно не усложнять запрос пакетами и соединениями, а применить функцию языка выражений СКД в вычисляемых полях или ресурсах:
ВычислитьВыражение("БазовыТипЦен", , , "Первая", "УровеньЭлемента Убыв")
Данная функция позволяет в группировке по номенклатуре автоматически извлечь значение типа цены из первой строки детальных записей, предварительно отсортированных по убыванию уровня элемента.