Часто перед разработчиком 1С встает задача: получить данные двумя или более запросами, а затем сопоставить их и произвести вычисления. Когда эта обработка происходит на стороне клиента, критически важным становится вопрос производительности. В каком виде лучше хранить промежуточные таблицы, чтобы дальнейший перебор строк и поиск нужных элементов занимали как можно меньше времени? Рассмотрим и сравним основные варианты: ТаблицаЗначений, Массив и Соответствие, а также проанализируем самый правильный подход к решению таких задач.
Прежде чем погружаться в оптимизацию клиентского кода, давайте рассмотрим самый эффективный подход, на который намекнули в обсуждении фразой про "полное соединение". Зачастую выгружать две большие таблицы на клиент для последующего сопоставления — не лучшая идея. Это создает нагрузку на сеть (передача данных) и на клиентский компьютер (обработка в памяти). СУБД (система управления базами данных) справляется с соединением данных на порядки быстрее, чем код 1С.
Идеальный сценарий для быстрого анализа данных — решить задачу одним пакетным запросом с использованием временных таблиц.
Как это работает по шагам:
LEFT JOIN, INNER JOIN) эти две временные таблицы по общим полям. В этом же запросе можно произвести необходимые вычисления, использовать агрегатные функции (СУММА, КОЛИЧЕСТВО) и установить отборы (ГДЕ).В итоге на клиент передается не две сырые таблицы, а уже готовый, обработанный результат. Это кардинально сокращает объем передаваемых данных и время выполнения, так как вся "тяжелая" работа выполняется оптимизированными механизмами СУБД.
Посмотрим на пример (для отладки таких задач удобно использовать консоль запросов). Допустим, нам нужно получить остатки товаров (Запрос 1) и их цены (Запрос 2), а затем для каждого товара с остатком найти его цену.
// Создаем менеджер временных таблиц
МенеджерВТ = Новый МенеджерВременныхТаблиц;
// Запрос 1: Получаем остатки
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос.Текст =
"ВЫБРАТЬ
| ТоварыНаСкладахОстатки.Номенклатура,
| ТоварыНаСкладахОстатки.КоличествоОстаток
|ПОМЕСТИТЬ ВТ_Остатки // Помещаем результат во временную таблицу
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки КАК ТоварыНаСкладахОстатки";
Запрос.Выполнить();
// Запрос 2: Финальный запрос, который использует ВТ и соединяет с ценами
Запрос.Текст =
"ВЫБРАТЬ
| ВТ_Остатки.Номенклатура,
| ВТ_Остатки.КоличествоОстаток,
| ЦеныНоменклатурыСрезПоследних.Цена
|ИЗ
| ВТ_Остатки КАК ВТ_Остатки // Используем временную таблицу как источник
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних КАК ЦеныНоменклатурыСрезПоследних
| ПО ВТ_Остатки.Номенклатура = ЦеныНоменклатурыСрезПоследних.Номенклатура";
Результат = Запрос.Выполнить().Выгрузить(); // Выгружаем уже готовый результат
// В переменной "Результат" у нас ТаблицаЗначений с тремя колонками,
// и нам не нужно ничего сопоставлять на клиенте.
Вывод: всегда старайтесь решить задачу одним запросом (вам может пригодиться шпаргалка по языку запросов). Это самый производительный путь. Но если обработка на клиенте неизбежна, давайте разберем, какой инструмент выбрать.
Если вам нужно многократно искать данные из одной таблицы (Таблица Б) по какому-то уникальному идентификатору (ключу) из другой таблицы (Таблица А), то Соответствие — ваш лучший выбор. Как было верно отмечено, это самый быстрый способ поиска.
Почему это так быстро? Соответствие — это коллекция пар "Ключ" - "Значение", реализованная как хэш-таблица. При добавлении элемента вычисляется его хэш, который напрямую указывает на ячейку в памяти. Благодаря этому поиск значения по ключу происходит практически мгновенно (со сложностью O(1)), без перебора всех остальных элементов.
Идеальный сценарий: перебираем циклом строки Таблицы А, берем из строки некий ID и по этому ID нам нужно быстро получить какие-то данные из Таблицы Б. Для этого мы заранее загружаем Таблицу Б в Соответствие.
Посмотрим на пример. Загрузим таблицу цен в Соответствие, где ключом будет номенклатура, а значением — цена.
// ТаблицаЦен - результат запроса с колонками "Номенклатура", "Цена"
СоответствиеЦен = Новый Соответствие;
// Заполняем Соответствие
Для Каждого Строка Из ТаблицаЦен Цикл
СоответствиеЦен.Вставить(Строка.Номенклатура, Строка.Цена);
КонецЦикла;
// Теперь в основной таблице (ТаблицаОстатков) мы можем быстро получать цену
Для Каждого СтрокаОстатка Из ТаблицаОстатков Цикл
ЦенаНоменклатуры = СоответствиеЦен.Получить(СтрокаОстатка.Номенклатура);
Если ЦенаНоменклатуры <> Неопределено Тогда
// Делаем какие-то вычисления...
Сумма = СтрокаОстатка.КоличествоОстаток * ЦенаНоменклатуры;
КонецЕсли;
КонецЦикла;
Важные моменты:
Соответствии должен быть уникальным. Если в исходной таблице есть дубли, в Соответствие попадет только последнее добавленное значение.Структуру или Массив.Соответствие не гарантирует порядок элементов. Его задача — быстрый доступ, а не упорядоченное хранение.ТаблицаЗначений (ТЗ) — это самый гибкий и универсальный объект. Для задачи поиска она подходит отлично, особенно если использовать ее скрытую мощь — индексирование.
Если просто использовать метод Найти() в неиндексированной ТЗ, платформа будет перебирать строки последовательно, пока не найдет нужную. Для таблиц в 1000 строк и более при многократных поисках это медленно. Однако, если создать индекс, все меняется.
Как работают индексы в ТЗ? Когда вы добавляете индекс по одной или нескольким колонкам, 1С строит за кулисами специальную быструю структуру данных (например, сбалансированное дерево). Методы Найти() и НайтиСтроки() начинают использовать этот индекс, что позволяет находить строки почти так же быстро, как в Соответствии, минуя полный перебор.
Когда использовать:
НайтиСтроки()).Посмотрим на пример:
// РезультатЗапроса - наша таблица размером 1000x10
ТЗ = РезультатЗапроса.Выгрузить();
// Добавляем индекс по двум колонкам для быстрого поиска
ТЗ.Индексы.Добавить("Номенклатура, Характеристика");
// ... далее в коде ...
// Теперь нам нужно найти все строки для определенной номенклатуры и характеристики
СтруктураПоиска = Новый Структура;
СтруктураПоиска.Вставить("Номенклатура", НужнаяНоменклатура);
СтруктураПоиска.Вставить("Характеристика", НужнаяХарактеристика);
НайденныеСтроки = ТЗ.НайтиСтроки(СтруктураПоиска); // Этот поиск будет очень быстрым!
Для Каждого НайденнаяСтрока Из НайденныеСтроки Цикл
// Обрабатываем найденные строки
Сообщить("Найдено значение: " + НайденнаяСтрока.КакоеТоПоле);
КонецЦикла;
"Подводные камни" индексирования:
Скопировать() с указанием колонок или Свернуть(). Их нужно создавать заново для новой таблицы.Массив — это самая простая и "легкая" коллекция. Он отлично подходит, если ваша задача — просто последовательно обойти все элементы от начала до конца в цикле. Итерация по массиву будет немного быстрее, чем по строкам ТаблицыЗначений.
Однако, как верно заметили на форуме, у массива нет встроенных механизмов поиска по значению. Если вам понадобится найти элемент, удовлетворяющий условию, придется писать цикл и перебирать все элементы вручную. Для таблицы в 1000 строк это будет крайне неэффективно при частом поиске.
Когда использовать: когда вам нужно выгрузить одну колонку данных и просто последовательно ее обработать без каких-либо поисков.
Проанализировав все варианты, составим четкий алгоритм выбора:
JOIN) для обработки данных на стороне СУБД.Соответствие. Это самый быстрый вариант.ТаблицуЗначений с предварительно созданными индексами по поисковым колонкам.ТаблицуЗначений, так и Массив. Массив будет чуть быстрее и займет меньше памяти, но ТаблицаЗначений более гибкая.Выбирая правильный инструмент для конкретной задачи, вы можете значительно ускорить выполнение вашего кода и улучшить отзывчивость системы для пользователя.