В чем лучше хранить результат запроса для быстрой обработки: ТаблицаЗначений, Массив или Соответствие?

Программист 1С v8.3 (Обычные формы)
← На главную

Часто перед разработчиком 1С встает задача: получить данные двумя или более запросами, а затем сопоставить их и произвести вычисления. Когда эта обработка происходит на стороне клиента, критически важным становится вопрос производительности. В каком виде лучше хранить промежуточные таблицы, чтобы дальнейший перебор строк и поиск нужных элементов занимали как можно меньше времени? Рассмотрим и сравним основные варианты: ТаблицаЗначений, Массив и Соответствие, а также проанализируем самый правильный подход к решению таких задач.

Главное правило: максимальная обработка на сервере

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

Идеальный сценарий для быстрого анализа данных — решить задачу одним пакетным запросом с использованием временных таблиц.

Как это работает по шагам:

  1. Создаем первый запрос и его результат помещаем во временную таблицу с помощью Менеджера временных таблиц.
  2. Создаем второй запрос и его результат также помещаем во вторую временную таблицу.
  3. Третьим, финальным запросом, мы "соединяем" (LEFT JOIN, INNER JOIN) эти две временные таблицы по общим полям. В этом же запросе можно произвести необходимые вычисления, использовать агрегатные функции (СУММА, КОЛИЧЕСТВО) и установить отборы (ГДЕ).

В итоге на клиент передается не две сырые таблицы, а уже готовый, обработанный результат. Это кардинально сокращает объем передаваемых данных и время выполнения, так как вся "тяжелая" работа выполняется оптимизированными механизмами СУБД.

Посмотрим на пример (для отладки таких задач удобно использовать консоль запросов). Допустим, нам нужно получить остатки товаров (Запрос 1) и их цены (Запрос 2), а затем для каждого товара с остатком найти его цену.


// Создаем менеджер временных таблиц
МенеджерВТ = Новый МенеджерВременныхТаблиц;

// Запрос 1: Получаем остатки
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   ТоварыНаСкладахОстатки.Номенклатура,
    |   ТоварыНаСкладахОстатки.КоличествоОстаток
    |ПОМЕСТИТЬ ВТ_Остатки // Помещаем результат во временную таблицу
    |ИЗ
    |   РегистрНакопления.ТоварыНаСкладах.Остатки КАК ТоварыНаСкладахОстатки";

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

// Запрос 2: Финальный запрос, который использует ВТ и соединяет с ценами
Запрос.Текст = 
    "ВЫБРАТЬ
    |   ВТ_Остатки.Номенклатура,
    |   ВТ_Остатки.КоличествоОстаток,
    |   ЦеныНоменклатурыСрезПоследних.Цена
    |ИЗ
    |   ВТ_Остатки КАК ВТ_Остатки // Используем временную таблицу как источник
    |       ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних КАК ЦеныНоменклатурыСрезПоследних
    |       ПО ВТ_Остатки.Номенклатура = ЦеныНоменклатурыСрезПоследних.Номенклатура";

Результат = Запрос.Выполнить().Выгрузить(); // Выгружаем уже готовый результат

// В переменной "Результат" у нас ТаблицаЗначений с тремя колонками,
// и нам не нужно ничего сопоставлять на клиенте.

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

Соответствие — для молниеносного поиска по ключу

Если вам нужно многократно искать данные из одной таблицы (Таблица Б) по какому-то уникальному идентификатору (ключу) из другой таблицы (Таблица А), то Соответствие — ваш лучший выбор. Как было верно отмечено, это самый быстрый способ поиска.

Почему это так быстро? Соответствие — это коллекция пар "Ключ" - "Значение", реализованная как хэш-таблица. При добавлении элемента вычисляется его хэш, который напрямую указывает на ячейку в памяти. Благодаря этому поиск значения по ключу происходит практически мгновенно (со сложностью O(1)), без перебора всех остальных элементов.

Идеальный сценарий: перебираем циклом строки Таблицы А, берем из строки некий ID и по этому ID нам нужно быстро получить какие-то данные из Таблицы Б. Для этого мы заранее загружаем Таблицу Б в Соответствие.

Посмотрим на пример. Загрузим таблицу цен в Соответствие, где ключом будет номенклатура, а значением — цена.


// ТаблицаЦен - результат запроса с колонками "Номенклатура", "Цена"
СоответствиеЦен = Новый Соответствие;

// Заполняем Соответствие
Для Каждого Строка Из ТаблицаЦен Цикл
    СоответствиеЦен.Вставить(Строка.Номенклатура, Строка.Цена);
КонецЦикла;

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

Важные моменты:

ТаблицаЗначений с индексами — универсальный солдат

ТаблицаЗначений (ТЗ) — это самый гибкий и универсальный объект. Для задачи поиска она подходит отлично, особенно если использовать ее скрытую мощь — индексирование.

Если просто использовать метод Найти() в неиндексированной ТЗ, платформа будет перебирать строки последовательно, пока не найдет нужную. Для таблиц в 1000 строк и более при многократных поисках это медленно. Однако, если создать индекс, все меняется.

Как работают индексы в ТЗ? Когда вы добавляете индекс по одной или нескольким колонкам, 1С строит за кулисами специальную быструю структуру данных (например, сбалансированное дерево). Методы Найти() и НайтиСтроки() начинают использовать этот индекс, что позволяет находить строки почти так же быстро, как в Соответствии, минуя полный перебор.

Когда использовать:

Посмотрим на пример:


// РезультатЗапроса - наша таблица размером 1000x10
ТЗ = РезультатЗапроса.Выгрузить();

// Добавляем индекс по двум колонкам для быстрого поиска
ТЗ.Индексы.Добавить("Номенклатура, Характеристика");

// ... далее в коде ...

// Теперь нам нужно найти все строки для определенной номенклатуры и характеристики
СтруктураПоиска = Новый Структура;
СтруктураПоиска.Вставить("Номенклатура", НужнаяНоменклатура);
СтруктураПоиска.Вставить("Характеристика", НужнаяХарактеристика);

НайденныеСтроки = ТЗ.НайтиСтроки(СтруктураПоиска); // Этот поиск будет очень быстрым!

Для Каждого НайденнаяСтрока Из НайденныеСтроки Цикл
    // Обрабатываем найденные строки
    Сообщить("Найдено значение: " + НайденнаяСтрока.КакоеТоПоле);
КонецЦикла;

"Подводные камни" индексирования:

Массив — легковес для простого перебора

Массив — это самая простая и "легкая" коллекция. Он отлично подходит, если ваша задача — просто последовательно обойти все элементы от начала до конца в цикле. Итерация по массиву будет немного быстрее, чем по строкам ТаблицыЗначений.

Однако, как верно заметили на форуме, у массива нет встроенных механизмов поиска по значению. Если вам понадобится найти элемент, удовлетворяющий условию, придется писать цикл и перебирать все элементы вручную. Для таблицы в 1000 строк это будет крайне неэффективно при частом поиске.

Когда использовать: когда вам нужно выгрузить одну колонку данных и просто последовательно ее обработать без каких-либо поисков.

Итоговые рекомендации

Проанализировав все варианты, составим четкий алгоритм выбора:

  1. Проверьте, можно ли решить задачу одним запросом. В 90% случаев это возможно и является самым производительным решением. Используйте временные таблицы и соединения (JOIN) для обработки данных на стороне СУБД.
  2. Если обработка на клиенте неизбежна и нужен многократный поиск:
    • Если поиск ведется по одному уникальному ключу для получения одного значения (или структуры значений) — ваш выбор Соответствие. Это самый быстрый вариант.
    • Если поиск ведется по нескольким колонкам или по условию может быть найдено несколько строк — используйте ТаблицуЗначений с предварительно созданными индексами по поисковым колонкам.
  3. Если вам нужен только последовательный перебор всех элементов без поиска, можно использовать как ТаблицуЗначений, так и Массив. Массив будет чуть быстрее и займет меньше памяти, но ТаблицаЗначений более гибкая.

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

← На главную