Работа с таблицами значений (ТЗ) в платформе 1С 7.7 часто сопряжена с трудностями из-за отсутствия встроенных механизмов соединения (Join), привычных разработчикам на платформе 8.x или SQL. Рассмотрим классическую задачу: у нас есть две таблицы ТЗ1 и ТЗ2. В ТЗ2 присутствует колонка Ключ, содержащая номера строк. Нам необходимо обновить данные в ТЗ1 на основании ТЗ2, удалить лишние строки и добавить новые, ориентируясь на этот ключ.
В этой статье мы разберем несколько подходов: от стандартных алгоритмов на чистом языке 1С до использования мощных инструментов расширения платформы, таких как 1CPP.
Если в вашей конфигурации не используются внешние компоненты, задачу придется решать путем циклического обхода. Проанализируем ситуацию: простое удаление строк в цикле "сверху вниз" приведет к смещению индексов, поэтому мы применим стратегию с пометкой нужных строк или использованием вспомогательной таблицы.
Разберем логику, предложенную в обсуждении, с небольшими оптимизациями для стабильности:
// Создаем вспомогательную таблицу для хранения индексов, которые должны остаться
ТЗ_Служебная = СоздатьОбъект("ТаблицаЗначений");
ТЗ_Служебная.НоваяКолонка("НомерСтрокиТЗ1");
// 1. Проходим по ТЗ2 и обновляем существующие строки в ТЗ1 или добавляем новые
ТЗ2.ВыбратьСтроки();
Пока ТЗ2.ПолучитьСтроку() = 1 Цикл
НомерСтрокиВТЗ1 = ТЗ2.Ключ;
// Пытаемся позиционироваться на нужной строке в ТЗ1
Если ТЗ1.КоличествоСтрок() >= НомерСтрокиВТЗ1 Тогда
ТЗ1.ПолучитьСтрокуПоНомеру(НомерСтрокиВТЗ1);
// Заменяем значения в совпадающих колонках
// Здесь мы предполагаем, что имена колонок известны
ТЗ1.Товар = ТЗ2.Товар;
ТЗ1.Количество = ТЗ2.Количество;
Иначе
// Если ключ больше количества строк, добавляем новую строку
ТЗ1.НоваяСтрока();
ТЗ1.Товар = ТЗ2.Товар;
ТЗ1.Количество = ТЗ2.Количество;
НомерСтрокиВТЗ1 = ТЗ1.КоличествоСтрок(); // Обновляем индекс для служебной таблицы
КонецЕсли;
// Запоминаем, что эта строка должна остаться
ТЗ_Служебная.НоваяСтрока();
ТЗ_Служебная.НомерСтрокиТЗ1 = НомерСтрокиВТЗ1;
КонецЦикла;
// 2. Удаляем из ТЗ1 строки, которых не было в ТЗ2.Ключ
// Важно: удаление производим с конца, чтобы не нарушать нумерацию
Для Счетчик = -ТЗ1.КоличествоСтрок() По -1 Цикл
Ном = -Счетчик;
ВремСтр = 0;
Если ТЗ_Служебная.НайтиЗначение(Ном, ВремСтр, "НомерСтрокиТЗ1") = 0 Тогда
ТЗ1.УдалитьСтроку(Ном);
КонецЕсли;
КонецЦикла;
Важное замечание: использование оператора Перейти и меток (как в сообщении №10) считается в современной разработке "дурным тоном". Наш пример выше реализует ту же логику более безопасно с точки зрения структуры кода.
Наиболее эффективный и современный (для 7.7) способ — использование объекта ИндексированнаяТаблица (ИТЗ) из поставки 1CPP. Этот объект поддерживает методы ЛевоеСоединение, ВнутреннееСоединение и ПравоеСоединение, которые работают в десятки раз быстрее обычных циклов.
Рассмотрим алгоритм, который позволяет выполнить синхронизацию буквально в несколько строк кода:
ИндексированнаяТаблица.ПравоеСоединение, чтобы оставить только те данные, которые есть в ТЗ2.Проанализируем пример кода на основе сообщения №14:
_ИТЗ1 = СоздатьОбъект("ИндексированнаяТаблица");
_ИТЗ1.Загрузить(ТЗ1);
_ИТЗ1.НоваяКолонка("НомерСтрокиТЗ");
// Заполняем техническую колонку индексами
_ИТЗ1.ВыбратьСтроки();
Пока _ИТЗ1.ПолучитьСтроку() = 1 Цикл
_ИТЗ1.НомерСтрокиТЗ = _ИТЗ1.НомерСтроки;
КонецЦикла;
_ИТЗ2 = СоздатьОбъект("ИндексированнаяТаблица");
_ИТЗ2.Загрузить(ТЗ2);
// В ТЗ2 связь идет через существующую колонку "Ключ"
_ИТЗ2.ДобавитьИндекс("ИндКлюч", "Ключ");
// Выполняем ПравоеСоединение.
// Оно оставит в ИТЗ1 только те записи, которые сопоставлены с ИТЗ2 по ключу,
// и добавит те, что есть в ИТЗ2, но отсутствуют в ИТЗ1.
_ИТЗ1.ПравоеСоединение("НомерСтрокиТЗ", _ИТЗ2, "Ключ");
// Возвращаем результат в обычную ТЗ
_ИТЗ1.Выгрузить(ТЗ1);
Как выяснилось в ходе обсуждения (сообщения №14-17), цикл по заполнению колонки НомерСтрокиТЗ может быть узким местом на очень больших таблицах. Чтобы ускорить процесс, можно использовать метод ЗаполнитьКолонку(). Рассмотрим, как подготовить список значений для мгновенного заполнения:
_аВрем = "";
Для _СчетчикЦикла = 1 По _ИТЗ1.КоличествоСтрок() Цикл
_аВрем = _аВрем + _СчетчикЦикла + ",";
КонецЦикла;
_СписокИндексов = СоздатьОбъект("СписокЗначений");
_СписокИндексов.ИзСтрокиСРазделителями(Лев(_аВрем, СтрДлина(_аВрем) - 1));
_ИТЗ1.ЗаполнитьКолонку(, "НомерСтрокиТЗ", _СписокИндексов);
Хотя этот метод кажется сложнее из-за работы со строками, на таблицах в десятки тысяч строк он может показать лучший результат, чем прямой перебор через ПолучитьСтроку(), так как минимизирует количество обращений к контексту объекта внутри цикла.
Если задача объединения таблиц становится еще сложнее (например, нужно объединять по нескольким условиям с агрегатными функциями), рекомендуется посмотреть в сторону компоненты 1SQLite. Она позволяет зарегистрировать таблицы значений как виртуальные таблицы в памяти и использовать чистый SQL:
Рассмотрим вкратце схему действий:
ТЗ1 и ТЗ2 как таблицы в базе SQLite.SELECT * FROM ТЗ2 LEFT JOIN ТЗ1 ON ТЗ2.Ключ = ТЗ1.RowID.Этот подход избавляет от необходимости вручную следить за индексами и колонками, перекладывая всю работу на оптимизатор SQL-запросов.
Мы выяснили, что для решения задачи синхронизации таблиц в 1С 7.7 существует три основных пути:
ИндексированнойТаблицы и методов соединений. Это "золотой стандарт" для высоконагруженных систем на 7.7.Join.Использование ИндексированнойТаблицы с методом ПравоеСоединение позволяет сократить объем кода в несколько раз, при этом значительно повысив скорость работы алгоритма (до 150 мс на таблицах в 10 000 строк).