Как в запросе 1С 7.7 получить уникальный список контрагентов, если у них несколько документов?

Программист 1С v7.7 Управленческий учет Торговля и дистрибуция
← На главную

При работе с запросами в 1С 7.7 часто возникает задача получить уникальный список каких-либо данных, например, контрагентов, которым были продажи за определенный период. Проблема может возникнуть, когда на одного контрагента приходится несколько документов — в результате обхода запроса его наименование выводится многократно. Разберем, почему так происходит и как этого избежать.

Проанализируем ситуацию на конкретном примере. Задача — выбрать всех клиентов, которым продавали определенную номенклатуру, и получить их список без дублей.

Исходный код автора выглядел так:


Процедура Clients()
    // ... пропущена подготовка параметров ...
    ТекстЗапроса = 
    "
    |Период с НачДата по КонДата;
    |Обрабатывать НеПомеченныеНаУдаление;
    |Номенклатура = Документ.Реализация.Номенклатура;
    |РеализацияТоваров = Документ.Реализация.ТекущийДокумент;
    |Контрагент = Документ.Реализация.Контрагент;
    |ТочкаДоставки = Документ.Реализация.ТочкаДоставки;
    |Условие(Номенклатура в грУвелка);
    |Группировка Контрагент Упорядочить по Контрагент.Наименование;
    |Группировка РеализацияТоваров; ";
    Запрос = СоздатьОбъект("Запрос");
    Если Запрос.Выполнить(ТекстЗапроса) = 0 Тогда
        // ... обработка ошибки ...
        Возврат;
    КонецЕсли;

    // Предполагаемый обход результата, который приводит к дублям
    Пока Запрос.Группировка("Контрагент") = 1 Цикл
        Пока Запрос.Группировка("РеализацияТоваров") = 1 Цикл
            // В этом цикле Запрос.Контрагент будет повторяться
            // для каждого документа одного и того же клиента
            Сообщить("Клиент: " + Запрос.Контрагент.Наименование); 
        КонецЦикла;
    КонецЦикла;
КонецПроцедуры

Выясним причину появления дублей и рассмотрим несколько способов решения этой задачи.

Причина появления дублей: лишняя вложенная группировка

Основная причина проблемы кроется в структуре самого запроса и способе его обхода. Давайте посмотрим на строки группировок:


|Группировка Контрагент Упорядочить по Контрагент.Наименование;
|Группировка РеализацияТоваров;

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

Важный момент: автор добавил группировку по документам не случайно. Ему нужно было получить из документа дополнительный реквизит — ТочкаДоставки. Это распространенное заблуждение, что для доступа к реквизиту документа обязательно нужна группировка по самому документу.

Решение 1: Корректировка текста запроса

Самый правильный и производительный способ — это исправить сам запрос так, чтобы он сразу возвращал нужный результат без избыточных данных.

Случай 1: Нужны только уникальные контрагенты

Если ваша задача — получить только список уникальных клиентов, то вторая группировка по документам просто не нужна. Достаточно оставить одну группировку по контрагентам. Запрос вернет уникальный перечень, и дублей не будет.


ТекстЗапроса = 
"
|Период с НачДата по КонДата;
|Контрагент = Документ.Реализация.Контрагент;
|Номенклатура = Документ.Реализация.Номенклатура;
|Условие(Номенклатура в грУвелка);
|Группировка Контрагент Упорядочить по Контрагент.Наименование;
";

При обходе такого запроса достаточно одного цикла, и каждый контрагент встретится лишь один раз.

Случай 2: Нужны уникальные пары "Контрагент – ТочкаДоставки"

Это как раз ситуация автора. Ему, помимо контрагента, нужна была и точка доставки. Чтобы решить эту задачу, не нужно группировать по всему объекту Документ. Вместо этого, нужно получить нужный реквизит как переменную запроса и добавить группировку именно по нему.

Рассмотрим, как это сделать по шагам:

  1. Объявим в запросе переменную для точки доставки и свяжем ее с реквизитом документа. Обратите внимание, что для этого не нужно получать весь документ (ТекущийДокумент).
  2. Добавим группировку по этой новой переменной.

Вот как будет выглядеть исправленный и корректный текст запроса:


ТекстЗапроса = 
"
|Период с НачДата по КонДата;
|Обрабатывать НеПомеченныеНаУдаление;
|Номенклатура = Документ.Реализация.Номенклатура;
|Контрагент = Документ.Реализация.Контрагент;
|ТочкаДоставки = Документ.Реализация.ТочкаДоставки; // Получаем реквизит напрямую
|Условие(Номенклатура в грУвелка);
|Группировка Контрагент Упорядочить по Контрагент.Наименование;
|Группировка ТочкаДоставки; // Группируем по нужному реквизиту
";

Теперь результат запроса будет содержать уникальные пары КонтрагентТочкаДоставки. При обходе такого запроса вы сможете получить все необходимые данные без лишних дублей на уровне контрагентов.

Решение 2: Программная обработка результата запроса

Иногда запрос намеренно строится так, что возвращает избыточные данные, а их последующая обработка и "сворачивание" выполняются программно. Это может быть полезно в сложных сценариях, но для простой задачи получения уникальных значений является менее оптимальным путем. Тем не менее, рассмотрим эти методы, так как они являются важной частью инструментария разработчика 1С 7.7.

Метод А: Использование объекта СписокЗначений

Объект СписокЗначений хранит коллекцию значений и автоматически обеспечивает их уникальность. Можно выполнить исходный, "неправильный" запрос, а затем в цикле добавлять контрагентов в список значений. Повторные добавления одного и того же элемента будут проигнорированы.


// ... после Запрос.Выполнить() ...

СписокКлиентов = СоздатьОбъект("СписокЗначений");

Пока Запрос.Группировка("Контрагент") = 1 Цикл
    // Добавляем контрагента в список. 
    // Если он там уже есть, ничего не произойдет.
    СписокКлиентов.ДобавитьЗначение(Запрос.Контрагент);
КонецЦикла;

// Теперь в СписокКлиентов содержатся только уникальные клиенты
// Можно обойти его и вывести результат
Для сч = 1 по СписокКлиентов.РазмерСписка() Цикл
    ТекКонтрагент = СписокКлиентов.ПолучитьЗначение(сч);
    Сообщить(ТекКонтрагент.Наименование);
КонецЦикла;

Этот метод прост и эффективен, если вам нужен только плоский список уникальных значений одного реквизита.

Метод Б: Выгрузка в ТаблицуЗначений и свёртка

Это более мощный и универсальный подход. Результат запроса можно выгрузить в объект ТаблицаЗначений, а затем использовать его метод Свернуть(). Этот метод группирует строки по указанным колонкам, опционально суммируя значения в других числовых колонках.

Посмотрим на пример. Предположим, мы хотим получить не только уникальных контрагентов, но и общую сумму продаж для каждого.


// В запросе нужно добавить получение числового реквизита, например, Суммы
ТекстЗапроса = 
"
|...
|Сумма = Документ.Реализация.Сумма;
|Контрагент = Документ.Реализация.Контрагент;
|...
|// Группировки здесь не так важны, так как мы выгружаем детальные данные
|Группировка Документ.Реализация; 
";
Запрос = СоздатьОбъект("Запрос");
Если Запрос.Выполнить(ТекстЗапроса) = 0 Тогда
    Возврат;
КонецЕсли;

ТЗ_Результат = СоздатьОбъект("ТаблицаЗначений");
// Выгружаем результат "как есть", со всеми строками и дублями контрагентов
Запрос.Выгрузить(ТЗ_Результат, 0, 0); 

// Теперь "сворачиваем" таблицу.
// 1-й параметр: колонки для группировки (уникальные значения).
// 2-й параметр: колонки для суммирования.
ТЗ_Результат.Свернуть("Контрагент", "Сумма");

// После выполнения метода Свернуть() в ТЗ_Результат останутся только
// строки с уникальными контрагентами и просуммированными для них суммами.
ТЗ_Результат.ВыбратьСтроки();
Пока ТЗ_Результат.ПолучитьСтроку() = 1 Цикл
    Сообщить("Клиент: " + ТЗ_Результат.Контрагент.Наименование + ", Сумма: " + ТЗ_Результат.Сумма);
КонецЦикла;

Метод Свернуть() — чрезвычайно полезный инструмент для агрегации данных, который позволяет избежать сложных конструкций с функциями Сумма() внутри самого текста запроса 1С 7.7.

Подведем итог: для решения задачи по устранению дублей всегда в первую очередь старайтесь оптимизировать сам запрос. Это наиболее эффективный путь. Если же требуется сложная последующая обработка данных, используйте программные методы с объектами СписокЗначений или ТаблицаЗначений.

← На главную