Многие разработчики 1С сталкиваются с ситуацией, когда при объединении двух таблиц значений (особенно при загрузке данных из разных информационных баз) метод Свернуть() отрабатывает не так, как ожидалось. Визуально в таблице присутствуют дублирующие строки с одинаковыми значениями измерений (например, Склад, Номенклатура, Характеристика, Серия), но после выполнения сворачивания они остаются отдельными строками. Давайте подробно разберем эту проблему, проанализируем неочевидные нюансы платформы и рассмотрим рабочие способы решения.
Рассмотрим классический пример из практики. Имеется две таблицы остатков: одна из базы "Управление торговлей" (УТ), другая из "Комплексной автоматизации" (КА). Мы программно сливаем их в одну таблицу значений и пытаемся свернуть по ключевым полям:
ОбщТЗ.Свернуть("Склад,Номенклатура,Характеристика,Серия", "ОстатокУТ,ОстатокКА");
После выполнения кода мы видим, что некоторые позиции свернулись корректно, а некоторые — задублировались. Проанализируем главные причины такого поведения системы.
Свернуть(), как и оператор СГРУППИРОВАТЬ ПО в запросах, при работе со ссылочными типами данных (тип СправочникСсылка) сравнивает элементы исключительно по их 128-битному уникальному идентификатору (GUID). Платформа совершенно не обращает внимания на то, что у элементов совпадают Наименование, Артикул или Код. Если номенклатура или серия были созданы в базах независимо или перенесены без сохранения GUID, для системы это фундаментально разные объекты. Они никогда не свернутся вместе, даже если внешне выглядят как абсолютные близнецы.NULL (означает отсутствие данных в результате соединений), Неопределено (появляется в нетипизированных колонках) и ПустаяСсылка (незаполненное значение конкретного типа справочника) — для метода Свернуть() это три абсолютно разных значения. Если в первой строке пустая серия записана как пустая ссылка, а во второй как NULL, эти строки не объединятся.ОписаниеТипов), корректное сравнение может нарушиться.Если мы выяснили, что GUID у объектов разные, самый простой программный способ свернуть такие данные "в лоб" — это сравнивать не ссылки, а примитивные типы. Рассмотрим, как это сделать. Нам нужно добавить в таблицу строковые или числовые колонки (например, уникальный строковый Артикул) и группировать уже по ним, предварительно очистив их от лишних пробелов.
// Добавляем новые колонки для строкового сравнения
ОбщТЗ.Колонки.Добавить("АртикулСтрока", Новый ОписаниеТипов("Строка"));
// Заполняем строковые значения, отсекая скрытые символы и пробелы
Для Каждого Стр Из ОбщТЗ Цикл
Стр.АртикулСтрока = СокрЛП(Стр.Номенклатура.Артикул);
КонецЦикла;
// Теперь сворачиваем по строковому полю
ОбщТЗ.Свернуть("Склад, АртикулСтрока", "ОстатокУТ, ОстатокКА");
Такой подход гарантирует, что визуально идентичные позиции (с одинаковыми артикулами) свернутся, игнорируя разные внутренние идентификаторы в разных информационных базах.
Работать с сотнями тысяч строк через циклы и метод Свернуть() — это долго и неоптимально. Гораздо эффективнее объединять такие объемы данных внутри запроса, поместив таблицы во временные таблицы (ВТ). Посмотрим, как грамотно обработать пустоты, чтобы избежать конфликтов типов NULL и ПустаяСсылка.
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
// Помещаем первую ТЗ (из УТ)
Запрос.Текст =
"ВЫБРАТЬ
| ТаблицаУТ.Склад КАК Склад,
| ТаблицаУТ.Номенклатура КАК Номенклатура,
| ТаблицаУТ.Серия КАК Серия,
| ТаблицаУТ.ОстатокУТ КАК ОстатокУТ
|ПОМЕСТИТЬ ВТ_УТ
|ИЗ
| &ТЗУТ КАК ТаблицаУТ";
Запрос.УстановитьПараметр("ТЗУТ", ТЗУТ);
Запрос.Выполнить();
// Аналогично помещаем ТЗ из КА (ВТ_КА)...
// Объединяем и группируем, приводя пустоты к одному типу
Запрос.Текст =
"ВЫБРАТЬ
| ЕСТЬNULL(ВТ_УТ.Склад, ВТ_КА.Склад) КАК Склад,
| ЕСТЬNULL(ВТ_УТ.Номенклатура, ВТ_КА.Номенклатура) КАК Номенклатура,
| ЕСТЬNULL(ВТ_УТ.Серия, ЗНАЧЕНИЕ(Справочник.СерииНоменклатуры.ПустаяСсылка)) КАК Серия,
| ЕСТЬNULL(ВТ_УТ.ОстатокУТ, 0) КАК ОстатокУТ,
| ЕСТЬNULL(ВТ_КА.ОстатокКА, 0) КАК ОстатокКА
|ИЗ
| ВТ_УТ КАК ВТ_УТ
| ПОЛНОЕ СОЕДИНЕНИЕ ВТ_КА КАК ВТ_КА
| ПО ВТ_УТ.Номенклатура.Артикул = ВТ_КА.Номенклатура.Артикул
| И ВТ_УТ.Склад.Наименование = ВТ_КА.Склад.Наименование";
Результат = Запрос.Выполнить().Выгрузить();
Обратите внимание на конструкцию ЕСТЬNULL. Мы жестко указываем платформе, что если серия отсутствует (вернулся NULL из-за соединения), ее нужно заменить на типизированную пустую ссылку ЗНАЧЕНИЕ(Справочник.СерииНоменклатуры.ПустаяСсылка). Это исключает расползание строк при последующих группировках. Кроме того, соединение выполняется по строковым реквизитам (Артикул, Наименование), обходя проблему несовпадения GUID.
Разберем ситуацию, когда вы все-таки используете метод Свернуть(). Чтобы платформа корректно сравнивала значения, колонки таблицы должны быть строго типизированы. Если таблица создается программно, всегда используйте объект ОписаниеТипов.
ОбщТЗ = Новый ТаблицаЗначений;
// Жесткая типизация ссылочных полей
МассивСправочника = Новый Массив;
МассивСправочника.Добавить(Тип("СправочникСсылка.Номенклатура"));
ОписаниеТипаНоменклатура = Новый ОписаниеТипов(МассивСправочника);
ОбщТЗ.Колонки.Добавить("Номенклатура", ОписаниеТипаНоменклатура);
// Жесткая типизация числовых ресурсов (Остатков)
КвалификаторыЧисла = Новый КвалификаторыЧисла(15, 3);
ОписаниеТипаЧисло = Новый ОписаниеТипов("Число", , КвалификаторыЧисла);
ОбщТЗ.Колонки.Добавить("ОстатокУТ", ОписаниеТипаЧисло);
ОбщТЗ.Колонки.Добавить("ОстатокКА", ОписаниеТипаЧисло);
Благодаря этому, если в числовую колонку попадет значение Неопределено, платформа автоматически преобразует его в 0, а пустые значения справочников примут вид явной ПустойСсылки, что гарантирует безошибочное сворачивание.
В сложной корпоративной архитектуре сравнивать элементы по наименованию — плохая практика (могут быть переименования, опечатки). Правильным решением при рассинхронизированных базах (когда не настроен штатный обмен или COM-соединение невозможно) является создание регистра сведений "Соответствия объектов".
В этом регистре создаются две колонки: УникальныйИдентификаторИсточник (строка) и СсылкаПриемник (ссылка). Перед тем как сливать и сворачивать таблицы, мы прогоняем данные из первой базы через этот регистр, заменяя чужие GUID-ы на локальные ссылки текущей базы. После этого метод Свернуть() отработает идеально, так как все ссылки будут принадлежать одной базе данных и иметь идентичные внутренние идентификаторы.