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

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

В практике разработки на платформе 1С:Предприятие часто возникает задача: из множества записей, относящихся к одному объекту, выбрать только одну, обладающую максимальным (или минимальным) значением определенного ресурса. Например, нужно получить для каждого контрагента только одну действующую скидку с максимальным процентом или для каждой номенклатуры — самую последнюю закупку. Рассмотрим, как эффективно решить эту задачу несколькими способами.

Метод 1: Использование временных таблиц и группировок (Классический подход)

Этот метод является универсальным и работает на всех версиях платформы 1С 8. Разберем ситуацию на примере автора темы: у физического лица может быть несколько дисконтных карт, и нам нужно выбрать только ту карту, у которой ПроцентСкидки является максимальным.

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

Шаг 1: Получение максимального значения для каждого владельца

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


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

Шаг 2: Получение данных конкретной карты

Теперь, когда у нас есть пары "Владелец — Максимальный процент", мы можем соединить эту таблицу с исходным справочником. Условием связи будет совпадение и владельца, и процента скидки. Это позволит нам "вытащить" ссылку на карту и её код.


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

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

Шаг 3: Объединение с основной информацией

На финальном этапе мы присоединяем полученные данные к списку физических лиц, используя ЛЕВОЕ СОЕДИНЕНИЕ, чтобы не потерять тех людей, у которых карт нет вовсе.


ВЫБРАТЬ
    ФизЛица.Ссылка КАК ФизЛицо,
    ЕСТЬNULL(ВТ_ИтоговыеКарты.Карточка, ЗНАЧЕНИЕ(Справочник.ИнформационныеКарты.ПустаяСсылка)) КАК Карта,
    ЕСТЬNULL(ВТ_ИтоговыеКарты.ПроцентСкидки, 0) КАК Скидка
ИЗ
    Справочник.ФизическиеЛица КАК ФизЛица
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ИтоговыеКарты КАК ВТ_ИтоговыеКарты
        ПО ФизЛица.Ссылка = ВТ_ИтоговыеКарты.ВладелецКарты

Метод 2: Использование оконных функций (Платформа 8.3.20+)

Если ваша база работает на современной платформе (версии 8.3.20 и выше), задачу можно решить гораздо элегантнее с помощью оконных функций. Это позволяет избежать создания нескольких временных таблиц и делает запрос более производительным.

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


ВЫБРАТЬ
    Карты.ВладелецКарты КАК ВладелецКарты,
    Карты.Ссылка КАК Карточка,
    Карты.ВидДисконтнойКарты.ПроцентСкидки КАК ПроцентСкидки,
    НОМЕР_СТРОКИ() ОБЩЕЕ(ПАРТИЦИЯ ПО Карты.ВладелецКарты УПОРЯДОЧИТЬ ПО Карты.ВидДисконтнойКарты.ПроцентСкидки УБЫВ) КАК ПорядковыйНомер
ПОМЕСТИТЬ ВТ_ПронумерованныеКарты
ИЗ
    Справочник.ИнформационныеКарты КАК Карты
;

ВЫБРАТЬ
    ВТ_ПронумерованныеКарты.ВладелецКарты,
    ВТ_ПронумерованныеКарты.Карточка,
    ВТ_ПронумерованныеКарты.ПроцентСкидки
ИЗ
    ВТ_ПронумерованныеКарты КАК ВТ_ПронумерованныеКарты
ГДЕ
    ВТ_ПронумерованныеКарты.ПорядковыйНомер = 1

Этот способ значительно сокращает текст запроса и, как правило, быстрее выполняется СУБД, так как требует меньше логических чтений данных.

Рекомендации по оптимизации и типичные ошибки

Рассмотрим несколько нюансов, которые помогут сделать ваш код качественнее:

  1. Индексация полей: При использовании первого метода (через временные таблицы), убедитесь, что поле ВладелецКарты проиндексировано. В стандартных справочниках 1С поле владельца индексируется автоматически, но если вы используете произвольный реквизит, индекс крайне желателен.
  2. Обращение через точку: В примерах выше мы используем конструкцию Карты.ВидДисконтнойКарты.ПроцентСкидки. Проанализируем это: в языке SQL это превращается в дополнительное соединение таблиц (JOIN). Если справочник карт огромный, это может замедлить работу. В таких случаях лучше сначала выгрузить нужные данные (Владелец, Ссылка, Процент) в отдельную временную таблицу, а потом уже выполнять группировки.
  3. СрезПоследних: Если данные хранятся в Регистре Сведений (например, цены), всегда старайтесь использовать виртуальную таблицу СрезПоследних с передачей параметров (период, условие). Никогда не делайте СрезПоследних ко всей таблице регистра, если вам нужны данные только по конкретному списку объектов.
  4. Пустые значения: Всегда используйте ЕСТЬNULL при левых соединениях, чтобы отчет или обработка не "падали" при попытке прочитать данные там, где их нет.

Выясним причину, почему исходный вариант автора (через ВЫБРАТЬ ПЕРВЫЕ 1 во вложенном запросе) не работал. В 1С ПЕРВЫЕ 1 во вложенном запросе, который соединяется по полю, не работает "построчно". Он выберет одну единственную карту из всей базы (самую "скидочную") и попытается присоединить её ко всем физлицам. Именно поэтому использование временных таблиц с МАКСИМУМ является стандартным де-факто решением для подобных задач.

← На главную