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