Как найти и сгруппировать документы по уникальным комбинациям элементов в табличной части?

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

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

Метод 1: Использование строкового ключа (Денормализация)

Это самый простой и быстрый в реализации метод. Его суть заключается в том, чтобы хранить «отпечаток» табличной части непосредственно в шапке документа. Проанализируем алгоритм реализации этого подхода:

  1. Добавим в документ дополнительный реквизит ХешСостава (тип Строка, длина должна быть достаточной для хранения списка или хеш-суммы).
  2. В модуле объекта документа, в обработчике ПередЗаписью, сформируем строку, состоящую из элементов табличной части.
  3. Критически важный момент: чтобы комбинации «Красный, Синий» и «Синий, Красный» считались одинаковыми, элементы перед сборкой строки необходимо отсортировать.

Рассмотрим пример кода для формирования такого ключа:


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    // Выгружаем ТЧ в таблицу значений для сортировки
    ТаблицаСостава = ТабличнаяЧасть1.Выгрузить();
    // Сортируем по номенклатуре (или цвету), чтобы порядок всегда был одинаковым
    ТаблицаСостава.Сортировать("Цвет Возр, Количество Возр");
    
    КлючСтрокой = "";
    Для Каждого СтрокаТЗ Из ТаблицаСостава Цикл
        КлючСтрокой = КлючСтрокой + СтрокаТЗ.Цвет.Наименование + ":" + СтрокаТЗ.Количество + "; ";
    КонецЦикла;
    
    // Сохраняем результат в реквизит шапки
    ХешСостава = СокрЛП(КлючСтрокой);
КонецПроцедуры

После того как данные подготовлены, итоговый запрос для отчета становится тривиальным. Мы просто группируем документы по полю ХешСостава и рассчитываем их количество (КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Ссылка)).

Метод 2: Справочник «Наборы» (Ключи аналитики)

Если документов очень много, а составы часто повторяются, хранение длинных строк в каждом документе становится неэффективным. В этом случае разберем более профессиональный подход — использование вспомогательного справочника. Этот метод часто применяется в типовых конфигурациях 1С (например, «Ключи аналитики номенклатуры»).

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

Проанализируем функцию, которая возвращает ссылку на существующий набор или создает новый, если такая комбинация встретилась впервые:


Функция ПолучитьНабор(Знач ТаблицаНабора) Экспорт
    
    // Если таблица пуста, возвращаем пустую ссылку
    Если ТаблицаНабора.Количество() = 0 Тогда
        Возврат Справочники.НаборыЦветов.ПустаяСсылка();
    КонецЕсли;
    
    УстановитьПривилегированныйРежим(Истина);
    
    Запрос = Новый Запрос;
    Запрос.УстановитьПараметр("ТаблицаНабора", ТаблицаНабора);    
    
    // Логика запроса: ищем набор, у которого состав полностью совпадает с переданной таблицей
    // Используем метод исключения: ищем наборы, где количество строк совпадает 
    // и нет расхождений в элементах.
    Запрос.Текст = 
    "ВЫБРАТЬ
    |    ТаблицаНабора.Цвет КАК Цвет,
    |    ТаблицаНабора.Количество КАК Количество
    |ПОМЕСТИТЬ втНовыйСостав
    |ИЗ
    |    &ТаблицаНабора КАК ТаблицаНабора
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |    СоставНабора.Ссылка КАК Ссылка
    |ИЗ
    |    Справочник.НаборыЦветов.Состав КАК СоставНабора
    |ГДЕ
    |    СоставНабора.Ссылка В (
    |        ВЫБРАТЬ Ссылка ИЗ Справочник.НаборыЦветов.Состав 
    |        СГРУППИРОВАТЬ ПО Ссылка ХАВИНГ КОЛИЧЕСТВО(*) = &КоличествоСтрок)
    |СГРУППИРОВАТЬ ПО
    |    СоставНабора.Ссылка
    |ИМЕЮЩИЕ
    |    КОЛИЧЕСТВО(ВЫБРАТЬ вт.Цвет ИЗ втНовыйСостав КАК вт 
    |               ГДЕ вт.Цвет = СоставНабора.Цвет И вт.Количество = СоставНабора.Количество) = &КоличествоСтрок";
    
    // Примечание: В реальной задаче лучше использовать запрос через ПОЛНОЕ СОЕДИНЕНИЕ 
    // для проверки отсутствия лишних строк в обоих множествах.
    
    РезультатЗапроса = Запрос.Выполнить();
    Выборка = РезультатЗапроса.Выбрать();
    
    Если Выборка.Следующий() Тогда
        Возврат Выборка.Ссылка;
    Иначе
        // Создаем новый элемент, если не нашли
        НовыйНабор = Справочники.НаборыЦветов.СоздатьЭлемент();
        НовыйНабор.Наименование = "Набор от " + ТекущаяДата();
        Для Каждого Строка Из ТаблицаНабора Цикл
            НоваяСтрока = НовыйНабор.Состав.Добавить();
            ЗаполнитьЗначенияСвойств(НоваяСтрока, Строка);
        КонецЦикла;
        НовыйНабор.Записать();
        Возврат НовыйНабор.Ссылка;
    КонецЕсли;
    
    Возврат Неопределено;
КонецФункции

Метод 3: Реляционное деление в запросе

Если необходимо получить данные «здесь и сейчас» без изменения структуры конфигурации, придется использовать сложные запросы. Посмотрим на логику сравнения двух множеств (Табличная часть Документа А и Табличная часть Документа Б):

Два состава идентичны, если:

  1. Количество строк в Документе А равно количеству строк в Документе Б.
  2. Количество совпадений по полям (Цвет, Количество) при соединении таблиц равно общему количеству строк в этих таблицах.

Этот метод крайне ресурсозатратный для СУБД, так как требует соединения табличных частей документов самих с собой по условиям «равенства наборов». В запросе 1С это обычно реализуется через создание временной таблицы всех строк всех документов, их нумерацию и последующее соединение по принципу «где не существует строки в А, которой нет в Б».

Сравнение методов и рекомендации

Выясним, какой метод лучше выбрать в зависимости от ситуации:

1. Строковый ключ (Метод 1): Идеально подходит для небольших баз и простых отчетов. Важно: если длина строки-ключа превышает 1024 символа (или другое ограничение СУБД), индекс по этому полю работать не будет, и группировка замедлится. Также помните, что типы СтрокаНеограниченнойДлины нельзя использовать в операциях СГРУППИРОВАТЬ в запросе.

2. Хеширование: Разновидность первого метода. Вместо хранения списка названий «Синий, Красный...», мы можем вычислить MD5-хеш от этой строки. Хеш всегда имеет фиксированную длину (например, 32 символа), что делает индексацию и поиск максимально быстрыми.

3. Справочник аналитики (Метод 2): Самый надежный архитектурный подход. Он позволяет не только быстро группировать документы, но и строить отчеты в разрезе «наборов», добавлять свойства самим наборам и поддерживать порядок в базе данных.

4. Сериализация в XML/JSON: Если задача стоит разово сравнить две табличные части программно, мы можем использовать следующий прием:


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

Подводя итог, для реализации задачи автора наиболее эффективным будет Метод 1 с обязательной сортировкой строк перед формированием ключа, либо Метод 2, если требуется масштабируемое решение для крупной системы.

← На главную