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