Почему метод Свернуть() не объединяет визуально одинаковые строки в Таблице Значений 1С и как это исправить?

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

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

Анализ ситуации: почему строки не сворачиваются?

Рассмотрим классический пример из практики. Имеется две таблицы остатков: одна из базы "Управление торговлей" (УТ), другая из "Комплексной автоматизации" (КА). Мы программно сливаем их в одну таблицу значений и пытаемся свернуть по ключевым полям:


ОбщТЗ.Свернуть("Склад,Номенклатура,Характеристика,Серия", "ОстатокУТ,ОстатокКА");

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

  1. Разные внутренние идентификаторы (GUID) при визуальном совпадении. Это самая частая причина при переносе данных из разных баз. Метод Свернуть(), как и оператор СГРУППИРОВАТЬ ПО в запросах, при работе со ссылочными типами данных (тип СправочникСсылка) сравнивает элементы исключительно по их 128-битному уникальному идентификатору (GUID). Платформа совершенно не обращает внимания на то, что у элементов совпадают Наименование, Артикул или Код. Если номенклатура или серия были созданы в базах независимо или перенесены без сохранения GUID, для системы это фундаментально разные объекты. Они никогда не свернутся вместе, даже если внешне выглядят как абсолютные близнецы.
  2. Конфликт пустот: NULL, Неопределено и ПустаяСсылка. При объединении данных из разных источников часто возникают несовпадения типов пустых значений. Если одна таблица строится программно в цикле, а вторая выгружается из результата запроса, пустые ячейки могут иметь разную природу. NULL (означает отсутствие данных в результате соединений), Неопределено (появляется в нетипизированных колонках) и ПустаяСсылка (незаполненное значение конкретного типа справочника) — для метода Свернуть() это три абсолютно разных значения. Если в первой строке пустая серия записана как пустая ссылка, а во второй как NULL, эти строки не объединятся.
  3. Скрытые невидимые символы. Если вы пытаетесь сворачивать данные по строковым представлениям (например, по наименованиям или артикулам), проблема может скрываться в невидимых символах. Пробел в конце строки или неразрывный пробел (который выглядит так же, но имеет другой код символа) сделают строки уникальными с точки зрения платформы.
  4. Отсутствие строгой типизации колонок. Если колонка таблицы значений имеет составной тип или тип "ЛюбаяСсылка" (создана без явного указания ОписаниеТипов), корректное сравнение может нарушиться.

Решение 1: Замена ссылок на примитивные типы (Артикулы, Коды, Строки)

Если мы выяснили, что GUID у объектов разные, самый простой программный способ свернуть такие данные "в лоб" — это сравнивать не ссылки, а примитивные типы. Рассмотрим, как это сделать. Нам нужно добавить в таблицу строковые или числовые колонки (например, уникальный строковый Артикул) и группировать уже по ним, предварительно очистив их от лишних пробелов.


// Добавляем новые колонки для строкового сравнения
ОбщТЗ.Колонки.Добавить("АртикулСтрока", Новый ОписаниеТипов("Строка"));

// Заполняем строковые значения, отсекая скрытые символы и пробелы
Для Каждого Стр Из ОбщТЗ Цикл
    Стр.АртикулСтрока = СокрЛП(Стр.Номенклатура.Артикул);
КонецЦикла;

// Теперь сворачиваем по строковому полю
ОбщТЗ.Свернуть("Склад, АртикулСтрока", "ОстатокУТ, ОстатокКА");

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

Решение 2: Перенос логики в язык запросов и использование ЕСТЬNULL

Работать с сотнями тысяч строк через циклы и метод Свернуть() — это долго и неоптимально. Гораздо эффективнее объединять такие объемы данных внутри запроса, поместив таблицы во временные таблицы (ВТ). Посмотрим, как грамотно обработать пустоты, чтобы избежать конфликтов типов NULL и ПустаяСсылка.


Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;

// Помещаем первую ТЗ (из УТ)
Запрос.Текст = 
"ВЫБРАТЬ
|   ТаблицаУТ.Склад КАК Склад,
|   ТаблицаУТ.Номенклатура КАК Номенклатура,
|   ТаблицаУТ.Серия КАК Серия,
|   ТаблицаУТ.ОстатокУТ КАК ОстатокУТ
|ПОМЕСТИТЬ ВТ_УТ
|ИЗ
|   &ТЗУТ КАК ТаблицаУТ";
Запрос.УстановитьПараметр("ТЗУТ", ТЗУТ);
Запрос.Выполнить();

// Аналогично помещаем ТЗ из КА (ВТ_КА)...

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

Результат = Запрос.Выполнить().Выгрузить();

Обратите внимание на конструкцию ЕСТЬNULL. Мы жестко указываем платформе, что если серия отсутствует (вернулся NULL из-за соединения), ее нужно заменить на типизированную пустую ссылку ЗНАЧЕНИЕ(Справочник.СерииНоменклатуры.ПустаяСсылка). Это исключает расползание строк при последующих группировках. Кроме того, соединение выполняется по строковым реквизитам (Артикул, Наименование), обходя проблему несовпадения GUID.

Решение 3: Строгая типизация создаваемых таблиц

Разберем ситуацию, когда вы все-таки используете метод Свернуть(). Чтобы платформа корректно сравнивала значения, колонки таблицы должны быть строго типизированы. Если таблица создается программно, всегда используйте объект ОписаниеТипов.


ОбщТЗ = Новый ТаблицаЗначений;

// Жесткая типизация ссылочных полей
МассивСправочника = Новый Массив;
МассивСправочника.Добавить(Тип("СправочникСсылка.Номенклатура"));
ОписаниеТипаНоменклатура = Новый ОписаниеТипов(МассивСправочника);

ОбщТЗ.Колонки.Добавить("Номенклатура", ОписаниеТипаНоменклатура);

// Жесткая типизация числовых ресурсов (Остатков)
КвалификаторыЧисла = Новый КвалификаторыЧисла(15, 3);
ОписаниеТипаЧисло = Новый ОписаниеТипов("Число", , КвалификаторыЧисла);

ОбщТЗ.Колонки.Добавить("ОстатокУТ", ОписаниеТипаЧисло);
ОбщТЗ.Колонки.Добавить("ОстатокКА", ОписаниеТипаЧисло);

Благодаря этому, если в числовую колонку попадет значение Неопределено, платформа автоматически преобразует его в 0, а пустые значения справочников примут вид явной ПустойСсылки, что гарантирует безошибочное сворачивание.

Решение 4: Создание промежуточной таблицы соответствий (Мэппинг)

В сложной корпоративной архитектуре сравнивать элементы по наименованию — плохая практика (могут быть переименования, опечатки). Правильным решением при рассинхронизированных базах (когда не настроен штатный обмен или COM-соединение невозможно) является создание регистра сведений "Соответствия объектов".

В этом регистре создаются две колонки: УникальныйИдентификаторИсточник (строка) и СсылкаПриемник (ссылка). Перед тем как сливать и сворачивать таблицы, мы прогоняем данные из первой базы через этот регистр, заменяя чужие GUID-ы на локальные ссылки текущей базы. После этого метод Свернуть() отработает идеально, так как все ссылки будут принадлежать одной базе данных и иметь идентичные внутренние идентификаторы.

← На главную