Как передать параметры из таблицы значений в печатную форму при сопоставлении данных

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

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

Анализ исходной задачи

Рассмотрим стандартную ситуацию: на сервере выполняется функция формирования печатной формы. У нас есть первая таблица Т1, полученная из временного хранилища, и вторая таблица ТТ, выгруженная из объекта. Нам нужно пройтись циклом по первой таблице, найти соответствующую строку во второй по ключевому полю Ключ и, если совпадение найдено, заполнить параметры макета.

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


&НаСервере
Функция ПечатьНаСервере()
    Т1 = ПолучитьИзВременногоХранилища(Объект.АдресТ1);
    ТТ = Объект.ЧтениеXML.Выгрузить();
    
    ТабличныйДокумент = Новый ТабличныйДокумент;
    Макет = РеквизитФормыВЗначение("Объект").ПолучитьМакет("Макет");
    ОбластьСтрока = Макет.ПолучитьОбласть("ТЧ");

    Для Каждого СтрТ1 Из Т1 Цикл
        НайденнаяСтрока = ТТ.Найти(СтрТ1.Ключ, "Ключ");
        Если НайденнаяСтрока <> Неопределено Тогда
            // Заполнение параметров и вывод
            ОбластьСтрока.Параметры.Субконто = НайденнаяСтрока.Объект;
            ТабличныйДокумент.Вывести(ОбластьСтрока);
        КонецЕсли;
    КонецЦикла;
    
    Возврат ТабличныйДокумент;
КонецФункции

Однако на практике этот код может не сработать (строки не находятся) или работать крайне медленно. Проанализируем причины и способы оптимизации.

Проблема 1: "Невидимые" пробелы и чистота данных

Часто метод Найти() возвращает Неопределено, хотя визуально ключи в обеих таблицах идентичны. Как выяснилось в процессе обсуждения, самой частой причиной являются лишние пробелы, особенно при загрузке данных из XML или текстовых файлов. Пробел в конце строки делает значения "Яблоко" и "Яблоко " абсолютно разными для платформы 1С.

Рекомендация: Всегда используйте функцию СокрЛП() при подготовке ключей для поиска. Кроме того, стоит учитывать специфику XML, где могут встречаться неразрывные пробелы (символ с кодом 160). Для надежности используйте следующую очистку:


КлючОчищенный = СокрЛП(СтрТ1.Ключ);
КлючОчищенный = СтрЗаменить(КлючОчищенный, Символы.НПП, ""); // Удаление неразрывных пробелов

Проблема 2: Производительность при больших объемах данных

Если в таблицах тысячи строк, линейный поиск методом Найти() внутри цикла создаст колоссальную нагрузку на процессор (сложность алгоритма O(n*m)). Чтобы ускорить процесс в десятки раз, необходимо использовать индексацию таблиц значений.

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


ТТ.Индексы.Добавить("Ключ"); 
// Теперь ТТ.Найти() или ТТ.НайтиСтроки() будут работать намного быстрее

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

Решение через запрос: Самый надежный метод

Профессиональный подход заключается в использовании механизма запросов. Это позволяет "скрестить" таблицы на уровне СУБД (или во временных таблицах в памяти сервера), что исключает ошибки ручного обхода циклом и обеспечивает максимальную производительность.

Разберем по шагам, как это реализовать:

  1. Подготавливаем таблицы значений, обязательно назначая типы колонок (это критично для запроса).
  2. Передаем таблицы в запрос как параметры.
  3. Выполняем ЛЕВОЕ СОЕДИНЕНИЕ или ВНУТРЕННЕЕ СОЕДИНЕНИЕ.
  4. Обходим результат запроса и выводим данные в макет.

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_ДанныеБД ИЗ &ТЗ_БД КАК ТЗ_БД;
    |ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_ДанныеXML ИЗ &ТЗ_XML КАК ТЗ_XML;
    |
    |ВЫБРАТЬ
    |   ВТ_ДанныеXML.Ключ,
    |   ВТ_ДанныеXML.Объект,
    |   ВТ_ДанныеXML.Сумма
    |ИЗ
    |   ВТ_ДанныеXML КАК ВТ_ДанныеXML
    |ГДЕ
    |   ВТ_ДанныеXML.Ключ В (ВЫБРАТЬ ВТ_ДанныеБД.Ключ ИЗ ВТ_ДанныеБД)";

Запрос.УстановитьПараметр("ТЗ_БД", Т1);
Запрос.УстановитьПараметр("ТЗ_XML", ТТ);

Выборка = Запрос.Выполнить().Выбрать();

Пока Выборка.Следующий() Цикл
    ОбластьСтрока.Параметры.Заполнить(Выборка); // Автоматическое заполнение параметров макета
    ТабличныйДокумент.Вывести(ОбластьСтрока);
КонецЦикла;

Важное замечание: Чтобы передать ТЗ в запрос, у ее колонок должны быть явно заданы типы. Если колонки созданы динамически без описания типов, запрос выдаст ошибку "Несоответствие типов".

Оптимизация заполнения макета

В исходном коде автора параметры заполняются вручную: ТЧ.Параметры.Год = Стр.Год; и так далее. Если у вас в макете десятки параметров, такой код становится громоздким и трудноподдерживаемым.

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

Область.Параметры.Заполнить(СтрокаДанных);

Это не только сокращает объем кода, но и снижает вероятность опечаток.

Работа с объектом "Соответствие"

Если вы не хотите использовать запросы, но вам нужна максимальная скорость сопоставления, воспользуйтесь объектом Соответствие. Это аналог "словаря" (dictionary), где поиск по ключу происходит мгновенно.

Рассмотрим алгоритм:

  1. Обходим вторую таблицу (XML) один раз и заполняем соответствие, где ключ — идентификатор, а значение — сама строка ТЗ.
  2. Обходим первую таблицу и просто "выдергиваем" данные из соответствия.

КартаДанных = Новый Соответствие;
Для Каждого СтрТТ Из ТТ Цикл
    КартаДанных.Вставить(СокрЛП(СтрТТ.Ключ), СтрТТ);
КонецЦикла;

Для Каждого СтрТ1 Из Т1 Цикл
    ДанныеДляПечати = КартаДанных.Получить(СокрЛП(СтрТ1.Ключ));
    Если ДанныеДляПечати <> Неопределено Тогда
        // Выводим в печатную форму
        ОбластьСтрока.Параметры.Заполнить(ДанныеДляПечати);
        ТабличныйДокумент.Вывести(ОбластьСтрока);
    КонецЕсли;
КонецЦикла;

Контроль вывода на страницы

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

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


МассивОбластей = Новый Массив;
МассивОбластей.Добавить(ОбластьСтрока);

Если Не ТабличныйДокумент.ПроверитьВывод(МассивОбластей) Тогда
    ТабличныйДокумент.ВывестиГоризонтальныйРазделительСтраниц();
    // При необходимости здесь можно повторно вывести "Шапку" таблицы
КонецЕсли;
ТабличныйДокумент.Вывести(ОбластьСтрока);

Подведение итогов

Мы рассмотрели различные способы сопоставления данных для печатных форм. Основные выводы:

Применяя эти методики, вы сможете создавать быстрые и надежные отчеты, которые корректно обрабатывают данные из любых внешних источников.

← На главную