В процессе разработки на платформе 1С:Предприятие часто возникает задача объединения данных из двух разных таблиц значений (ТЗ). Типичный сценарий: мы перебираем одну таблицу в цикле и для каждой строки должны найти соответствующие данные в другой таблице, после чего выполнить над ними определенные действия, включая фильтрацию и сортировку. Рассмотрим, какими способами можно реализовать этот функционал, и проанализируем эффективность каждого подхода.
Самый простой и очевидный способ найти строки в таблице по определенному значению — это использование метода НайтиСтроки(). Этот метод принимает на вход структуру, где ключом является имя колонки, а значением — искомый объект.
Разберем пример кода, предложенный участниками обсуждения:
ОтборСтрок = Новый Структура("Номенклатура", Справочники.Номенклатура.ПустаяСсылка());
Для каждого СтрТЗ1 из ТЗ_1 Цикл
// Устанавливаем значение для поиска из текущей строки первой таблицы
ОтборСтрок.Номенклатура = СтрТЗ1.Номенклатура;
// Получаем массив строк из второй таблицы
НайденныеСтроки = ТЗ_2.НайтиСтроки(ОтборСтрок);
Для каждого СтрТЗ2 из НайденныеСтроки Цикл
// Обработка найденных данных
КонецЦикла;
КонецЦикла;
Важный нюанс: Метод НайтиСтроки() возвращает не новые объекты, а массив ссылок (указателей) на строки оригинальной таблицы ТЗ_2. Если вы измените данные в СтрТЗ2, они изменятся и в исходной таблице ТЗ_2. Однако у массива нет метода Сортировать(), поэтому, если вам нужен строгий порядок вывода отобранных строк, этот метод потребует дополнительных манипуляций.
Проанализируем ситуацию с производительностью. Если в ТЗ_2 содержатся тысячи строк, поиск перебором при каждой итерации цикла по ТЗ_1 будет крайне медленным. Чтобы ускорить процесс, необходимо использовать индексы.
Перед началом цикла добавим индекс по колонке поиска:
ТЗ_2.Индексы.Добавить("Номенклатура");
Платформа 1С автоматически задействует этот индекс при вызове НайтиСтроки(), что превращает линейный поиск в практически мгновенную операцию. Мы рекомендуем всегда использовать индексацию, если количество строк в таблице превышает несколько сотен.
Если после отбора данных по конкретной номенклатуре нам критически важно отсортировать результат по нескольким полям (например, по дате или цене), метод с массивом строк не подойдет. В этом случае мы воспользуемся методом Скопировать().
Рассмотрим реализацию этого подхода:
Отбор = Новый Структура("Номенклатура");
Для каждого СтрТЗ1 из ТЗ_1 Цикл
Отбор.Номенклатура = СтрТЗ1.Номенклатура;
// Создаем новую физическую таблицу из отобранных строк
КопияТЗ_2 = ТЗ_2.Скопировать(Отбор);
// Теперь нам доступна полноценная сортировка
КопияТЗ_2.Сортировать("Дата Возр, Цена Убыв");
Для каждого СтрТЗ2 из КопияТЗ_2 Цикл
// Работаем с отсортированными данными
КонецЦикла;
КонецЦикла;
Выясним причину, по которой этот метод стоит использовать с осторожностью. На каждой итерации цикла Для каждого создается новый объект таблицы значений в оперативной памяти. Если ТЗ_1 содержит тысячи строк, это приведет к избыточному потреблению ресурсов и фрагментации памяти. Мы советуем использовать этот способ только в тех случаях, когда объем данных в выборке невелик.
Для решения задач со сложной фильтрацией, соединением данных и гарантированной сортировкой наиболее профессиональным подходом является использование объекта Запрос. Вместо того чтобы мучительно перебирать таблицы в циклах, мы можем передать их в запрос как внешние источники данных.
Разберем по шагам, как это сделать:
ЛЕВОЕ СОЕДИНЕНИЕ или ВНУТРЕННЕЕ СОЕДИНЕНИЕ по ключевому полю (в нашем случае — "Номенклатура").УПОРЯДОЧИТЬ ПО для получения нужной сортировки.Это позволит базе данных за одну операцию выполнить всю работу, которую мы пытались сделать вручную в циклах. Это значительно быстрее и надежнее с точки зрения архитектуры кода.
Если связь между таблицами — "один-ко-многим" и нам нужна максимальная скорость без использования запросов, мы можем применить объект Соответствие.
Проанализируем алгоритм:
КартаСтрок = Новый Соответствие;
Для каждого Стр из ТЗ_2 Цикл
Ключ = Стр.Номенклатура;
МассивОбъектов = КартаСтрок.Получить(Ключ);
Если МассивОбъектов = Неопределено Тогда
МассивОбъектов = Новый Массив;
КартаСтрок.Вставить(Ключ, МассивОбъектов);
КонецЕсли;
МассивОбъектов.Добавить(Стр);
КонецЦикла;
// Теперь в основном цикле поиск будет мгновенным
Для каждого СтрТЗ1 из ТЗ_1 Цикл
НайденныеДанные = КартаСтрок.Получить(СтрТЗ1.Номенклатура);
Если НайденныеДанные <> Неопределено Тогда
// Обработка массива
КонецЕсли;
КонецЦикла;
Поиск в Соответствии работает через хеш-таблицы, что зачастую быстрее, чем поиск по индексированной таблице значений, особенно при огромных объемах данных.
В некоторых случаях, когда фильтры и сортировки задаются динамически (например, пользователем в интерфейсе), удобно использовать ПостроительЗапроса. Ему можно назначить таблицу значений в качестве источника данных: Построитель.ИсточникДанных = Новый ОписаниеИсточникаДанных(ТЗ_2). Это дает гибкость в управлении отборами без ручного написания сложных условий.
Также стоит отметить мнение экспертов о высокой производительности Системы компоновки данных (СКД). Для обработки больших массивов ТЗ в новых релизах платформы механизмы СКД показывают лучшие результаты по распределению памяти, чем классические запросы к временным таблицам. Если ваша задача предполагает сложную группировку и агрегацию данных из ТЗ, попробуйте реализовать ее через программный вызов СКД.
Подведем итог: для простых задач достаточно НайтиСтроки() с обязательным индексированием. Если нужна сложная сортировка — используйте Запрос или Скопировать() с отбором, если итераций немного. Для высоконагруженных систем лучшим выбором будет предварительная подготовка данных в Соответствие или использование временных таблиц в запросе.