В процессе разработки внешних обработок в среде 1С:Предприятие 8 программисты часто сталкиваются с ситуацией, когда на форме присутствуют две или более табличные части, которые логически связаны между собой, но технически не имеют жестких ссылочных связей в структуре данных. Типичный пример из практики автоматизации розничной торговли: наличие таблицы Магазины и таблицы Кассы, где каждой строке первой таблицы соответствует определенная строка во второй.
Разберем детально, как правильно организовать перебор таких данных, чтобы избежать "громоздких" вложенных циклов, обеспечить корректную работу регламентных заданий и сохранить производительность системы. Проанализируем несколько подходов, начиная от самых простых и заканчивая профессиональными методами работы с коллекциями значений.
Если ваша бизнес-логика подразумевает, что таблицы заполняются строго параллельно (например, пользователь добавляет магазин в первую таблицу, а затем соответствующую ему кассу во вторую), мы можем применить обращение к строкам по их порядковому номеру (индексу). Этот метод является самым быстрым в реализации, так как не требует поиска ключей связи.
Рассмотрим пример реализации такого цикла:
КоличествоСтрок = Объект.Магазины.Количество();
Для Счетчик = 0 По КоличествоСтрок - 1 Цикл
// Получаем строку из первой таблицы
СтрокаМагазин = Объект.Магазины[Счетчик];
ТекущийМагазин = СтрокаМагазин.Магазин;
// Получаем строку из второй таблицы по тому же индексу
Попытка
СтрокаКасса = Объект.Касса[Счетчик];
ТекущаяКасса = СтрокаКасса.Касса;
Исключение
// Обработка ситуации, если во второй таблице строк меньше
Продолжить;
КонецПопытки;
// Выполняем загрузку данных для пары Магазин + Касса
ЗагрузитьДанныеПоМагазину(ТекущийМагазин, ТекущаяКасса);
КонецЦикла;
Важно помнить, что этот метод надежен только в том случае, если вы гарантируете неизменность порядка строк в обеих таблицах. Если пользователь решит отсортировать одну из таблиц вручную, связь "по индексу" нарушится.
Когда прямой индексной связи нет, а вложенный цикл (цикл в цикле) кажется неэффективным, на помощь приходит объект Соответствие. Это специальная коллекция в 1С, предназначенная для быстрого поиска значений по ключу. В нашем случае ключом будет выступать Магазин, а значением — Касса.
Разберем по шагам, как оптимизировать алгоритм:
// Шаг 1. Подготовка данных
КартаКасс = Новый Соответствие;
Для Каждого СтрКасса Из Объект.Касса Цикл
// Предполагаем, что у кассы есть скрытый реквизит или связь
КартаКасс.Вставить(СтрКасса.ВладелецМагазин, СтрКасса.Касса);
КонецЦикла;
// Шаг 2. Основной цикл
Для Каждого СтрМагазин Из Объект.Магазины Цикл
ТекущаяКасса = КартаКасс.Получить(СтрМагазин.Магазин);
Если ТекущаяКасса <> Неопределено Тогда
// Логика загрузки
КонецЕсли;
КонецЦикла;
Данный подход исключает квадратичную зависимость времени выполнения (N*M) и делает код гораздо более читаемым и производительным.
Проанализируем ситуацию, упомянутую в обсуждении: использование uuid. Если при заполнении настроек или при открытии формы вы генерируете уникальный идентификатор для каждой пары "Магазин-Касса", это дает 100% гарантию правильного сопоставления даже при сортировках таблиц.
Вы можете добавить в обе табличные части колонку ИдентификаторСвязи (тип Строка или УникальныйИдентификатор). Сделайте её невидимой для пользователя (свойство Видимость = Ложь). При заполнении таблиц записывайте в них одинаковые значения ключа. Тогда поиск будет выглядеть так:
Для Каждого СтрМагазин Из Объект.Магазины Цикл
ПараметрыОтбора = Новый Структура("ИдентификаторСвязи", СтрМагазин.ИдентификаторСвязи);
НайденныеКассы = Объект.Касса.НайтиСтроки(ПараметрыОтбора);
Если НайденныеКассы.Количество() > 0 Тогда
ТекущаяКасса = НайденныеКассы[0].Касса;
// Работаем с данными
КонецЕсли;
КонецЦикла;
Выясним важную деталь: автор темы упомянул, что загрузка должна работать как регламентное задание. В этом случае мы сталкиваемся с ограничением — на сервере нет доступа к элементам формы (Элементы, СписокВыбора и прочее).
Если ваша логика сопоставления завязана на программном заполнении списков выбора на форме, ее необходимо перенести в Модуль объекта или в Общий модуль. Рассмотрим рекомендации по переносу:
КассыККМ обычно есть реквизит Владелец (Организация) или Магазин (в зависимости от версии УТ). Проверьте метаданные: возможно, вам не нужно ничего сопоставлять вручную, так как связь Касса.Магазин уже существует в базе.Для тяжелых операций загрузки, особенно в регламентных заданиях, наиболее правильным подходом будет использование объекта Запрос. Мы можем передать наши табличные части в запрос как параметры, объединить их по ключу и получить готовый результат для обработки.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_Магазины ИЗ &ТчМагазины КАК ТчМагазины;
|ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_Кассы ИЗ &ТчКассы КАК ТчКассы;
|ВЫБРАТЬ
| ВТ_Магазины.Магазин,
| ВТ_Кассы.Касса
|ИЗ
| ВТ_Магазины КАК ВТ_Магазины
|ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Кассы КАК ВТ_Кассы
|ПО ВТ_Магазины.Индекс = ВТ_Кассы.Индекс";
Запрос.УстановитьПараметр("ТчМагазины", Объект.Магазины.Выгрузить());
Запрос.УстановитьПараметр("ТчКассы", Объект.Касса.Выгрузить());
Результат = Запрос.Выполнить().Выбрать();
Пока Результат.Следующий() Цикл
// Безопасная и быстрая загрузка данных
КонецЦикла;
Подведем итог: выбор метода зависит от архитектуры вашей обработки. Если таблицы жестко связаны по порядку — используйте индекс. Если таблицы независимы и могут сортироваться — используйте скрытые колонки-идентификаторы и объект Соответствие. Если же загрузка выполняется в фоне (регламентное задание) — переносите данные в запрос через временные таблицы.