Как эффективно выполнять поиск в Таблице Значений по сложным условиям и реквизитам объектов в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В процессе разработки в среде 1С:Предприятие одной из самых частых прикладных задач является обработка коллекций данных в оперативной памяти. Объект ТаблицаЗначений предоставляет удобный контейнер для хранения строк и колонок, однако стандартные механизмы поиска в нем имеют ряд существенных ограничений. Разберем типичную ситуацию: у нас есть таблица значений с колонками Договор и Номенклатура. Нам необходимо решить две задачи:

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

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

Проблема стандартных методов поиска в Таблице Значений

Платформа 1С предлагает встроенные методы поиска, такие как Найти() и НайтиСтроки(). Метод Найти() возвращает только первую найденную строку и работает по одному значению. Метод НайтиСтроки() принимает структуру отбора и возвращает массив всех подходящих строк. Например:


СтруктураПоиска = Новый Структура;
СтруктураПоиска.Вставить("Номенклатура", ВыбраннаяНоменклатура);
НайденныеСтроки = ТаблицаЗначений.НайтиСтроки(СтруктураПоиска);

Однако этот подход сталкивается с непреодолимыми препятствиями в следующих случаях:

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

Решение 1: Добавление колонок и заполнение их перед поиском (Простой путь)

Если структура таблицы значений создается нами динамически или мы можем ее модифицировать, самым простым решением будет добавление вспомогательных колонок. Чтобы найти контрагента (владельца договора) стандартным методом, мы можем при заполнении таблицы или отдельным циклом вытащить это свойство в отдельную колонку.

Рассмотрим пример реализации этого подхода по шагам:


// Шаг 1. Добавляем колонку для контрагента, если ее еще нет
Если ТаблицаЗначений.Колонки.Найти("Контрагент") = Неопределено Тогда
    ТаблицаЗначений.Колонки.Добавить("Контрагент", Новый ОписаниеТипов("СправочникСсылка.Контрагенты"));
КонецЕсли;

// Шаг 2. Заполняем колонку данными (если они не были заполнены ранее)
Для Каждого СтрокаТЗ Из ТаблицаЗначений Цикл
    Если ЗначениеЗаполнено(СтрокаТЗ.Договор) Тогда
        СтрокаТЗ.Контрагент = СтрокаТЗ.Договор.Владелец;
    КонецЕсли;
КонецЦикла;

// Шаг 3. Выполняем быстрый поиск с помощью НайтиСтроки
СтруктураОтбора = Новый Структура;
СтруктураОтбора.Вставить("Контрагент", ИскомыйКонтрагент);
НайденныеСтроки = ТаблицаЗначений.НайтиСтроки(СтруктураОтбора);

Важнейший нюанс оптимизации: Индексирование! Если в вашей таблице значений содержится более 1000 строк или поиск выполняется многократно внутри другого цикла, этот код начнет сильно тормозить. Причина в том, что по умолчанию 1С выполняет последовательный перебор всей таблицы — сложность алгоритма составляет $O(N)$.

Чтобы ускорить поиск в разы, необходимо использовать встроенные индексы таблицы значений. Добавим индекс по колонке поиска:

ТаблицаЗначений.Индексы.Добавить("Контрагент");

Теперь платформа построит в оперативной памяти хэш-таблицу, и поиск через НайтиСтроки() будет выполняться практически мгновенно (сложность близка к $O(1)$). Помните важное правило: для составного поиска по нескольким колонкам (например, Контрагент и Номенклатура) нужно создавать составной индекс строго по тем же полям:

ТаблицаЗначений.Индексы.Добавить("Контрагент, Номенклатура");

Решение 2: Помещение Таблицы Значений во Временную Таблицу запроса (Универсальный путь)

Когда условия поиска становятся по-настоящему сложными (требуется соединение с таблицами базы данных, получение реквизитов через несколько уровней вложенности, проверка дат и номеров), лучшим и наиболее профессиональным решением является использование механизма запросов.

Платформа 1С позволяет передать таблицу значений в качестве параметра запроса и поместить ее во временную таблицу (ВТ). Это дает нам возможность использовать всю мощь языка запросов SQL для фильтрации и поиска.

Критически важное требование типизации: Чтобы передать ТЗ в запрос, все используемые в запросе колонки ТЗ должны иметь строго определенные типы данных. Если колонки созданы без явного указания типа (имеют тип Произвольный), платформа вызовет ошибку времени выполнения.

Посмотрим на пример правильного создания типизированной таблицы значений, заполнения ее данными и последующего поиска по сложным критериям:


// 1. Создаем строго типизированную Таблицу Значений
ТЗДляПоиска = Новый ТаблицаЗначений;

// Описываем типы для колонок
МассивТиповДоговор = Новый Массив;
МассивТиповДоговор.Добавить(Тип("СправочникСсылка.ДоговорыКонтрагентов"));
ОписаниеТиповДоговор = Новый ОписаниеТипов(МассивТиповДоговор);

МассивТиповНоменклатура = Новый Массив;
МассивТиповНоменклатура.Добавить(Тип("СправочникСсылка.Номенклатура"));
ОписаниеТиповНоменклатура = Новый ОписаниеТипов(МассивТиповНоменклатура);

ТЗДляПоиска.Колонки.Добавить("Договор", ОписаниеТиповДоговор);
ТЗДляПоиска.Колонки.Добавить("Номенклатура", ОписаниеТиповНоменклатура);

// (Здесь происходит заполнение таблицы ТЗДляПоиска данными)

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

// Передаем таблицу значений в качестве параметра
Запрос.УстановитьПараметр("ВходнаяТаблица", ТЗДляПоиска);

// Устанавливаем параметры отбора
Запрос.УстановитьПараметр("ИскомыйКонтрагент", ПараметрКонтрагент);
Запрос.УстановитьПараметр("ДатаНачала", НачалоГода(ТекущаяДата()));
Запрос.УстановитьПараметр("ШаблонНомера", "%исх%");
Запрос.УстановитьПараметр("ИскомаяНоменклатура", ПараметрНоменклатура);

// Выполняем запрос
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();

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

Этот способ позволяет решить обе задачи автора темы одновременно. Мы легко дотягиваемся через точку до любых свойств объектов (дата договора, его номер, владелец), не перегружая код лишними циклами трансформации данных в оперативной памяти.

Решение 3: Использование Построителя Запроса (Альтернатива без Временных Таблиц)

Если по какой-то причине вы не хотите создавать временные таблицы в СУБД (например, при работе с очень часто вызываемыми мелкими функциями на сервере, где накладные расходы на создание ВТ могут быть заметны), можно использовать объект ПостроительЗапроса. Он умеет накладывать произвольные отборы на таблицы значений непосредственно средствами сервера приложений 1С.

Проанализируем пример использования построителя для фильтрации ТЗ:


Построитель = Новый ПостроительЗапроса;
// Передаем таблицу значений как источник данных
Построитель.ИсточникДанных = Новый ОписаниеИсточникаДанных(ТаблицаЗначений);

// Добавляем отборы динамически
ОтборНоменклатура = Построитель.Отбор.Добавить("Номенклатура");
ОтборНоменклатура.Использование = Истина;
ОтборНоменклатура.ВидСравнения = ВидСравнения.Равно;
ОтборНоменклатура.Значение = ИскомаяНоменклатура;

// Выполняем построитель и получаем результат в виде новой таблицы значений
Построитель.Выполнить();
РезультатТаблица = Построитель.Результат.Выгрузить();

Этот способ невероятно удобен тем, что позволяет пользователю или разработчику настраивать гибкие условия (например, "В списке", "Содержит", "Не равно") программно, не переписывая текст SQL-запроса.

Решение 4: Использование объекта "Соответствие" для мгновенного кэшированного поиска

В высоконагруженных системах, когда поиск по таблице значений происходит внутри огромного цикла обработки (например, при проведении документов или импорте файлов на сотни тысяч строк), даже индексированная таблица значений может работать недостаточно быстро. В этом случае идеальным решением будет предварительное свертывание данных в объект Соответствие.

Поскольку Соответствие реализовано на уровне платформы как классическая хэш-таблица, доступ к значению по ключу происходит за константное время $O(1)$.

Спроектируем алгоритм быстрого поиска по составному ключу (Договор + Номенклатура) с использованием соответствия:


// Создаем хэш-карту для быстрого поиска
КартаПоиска = Новый Соответствие;

Для Каждого СтрокаТЗ Из ТаблицаЗначений Цикл
    // Формируем уникальный комбинированный ключ поиска.
    // Можно использовать структуру, но строка-хэш работает быстрее.
    КлючПоиска = Строка(СтрокаТЗ.Договор.УникальныйИдентификатор()) + "_" + Строка(СтрокаТЗ.Номенклатура.УникальныйИдентификатор());
    
    // Если по ключу может быть несколько строк, сохраняем их в массив
    МассивСтрок = КартаПоиска[КлючПоиска];
    Если МассивСтрок = Неопределено Тогда
        МассивСтрок = Новый Массив;
        КартаПоиска.Вставить(КлючПоиска, МассивСтрок);
    КонецЕсли;
    МассивСтрок.Добавить(СтрокаТЗ);
КонецЦикла;

// Теперь непосредственно поиск в цикле обработки:
КлючДляПоиска = Строка(ИскомыйДоговор.УникальныйИдентификатор()) + "_" + Строка(ИскомаяНоменклатура.УникальныйИдентификатор());
НайденныеСтроки = КартаПоиска[КлючДляПоиска];

Если НайденныеСтроки <> Неопределено Тогда
    Для Каждого СтрокаРезультата Из НайденныеСтроки Цикл
        // Обработка быстро найденной строки
    КонецЦикла;
КонецЕсли;

Данный подход незаменим при оптимизации узких мест (bottle-necks) в алгоритмах проведения документов и интеграционных шлюзах.

Решение 5: Использование Системы Компоновки Данных (СКД) для сложных отборов

Для реализации сложных сценариев поиска с иерархией условий (группировки условий "И", "ИЛИ", "НЕ") можно применить возможности Системы компоновки данных. СКД умеет работать с внешними наборами данных, в качестве которых отлично выступает наша таблица значений.

Этот метод сложнее в реализации, но предоставляет непревзойденную гибкость:

Для решения базовых повседневных задач программирования СКД может показаться избыточной, однако при создании сложных динамических отчетов на управляемых формах этот вариант является наиболее архитектурно верным.

Сводная таблица выбора метода поиска

Чтобы вам было проще определиться с выбором архитектурного решения, мы подготовили краткую шпаргалку по применимости каждого метода:

  1. Простой поиск по равенству одного или нескольких полей: Используйте метод НайтиСтроки(), но обязательно добавляйте индекс с помощью ТаблицаЗначений.Индексы.Добавить().
  2. Поиск по реквизитам ссылочных полей (через точку) или диапазонам дат: Помещайте ТЗ во временную таблицу запроса. Не забывайте предварительно типизировать колонки вашей ТЗ!
  3. Динамические пользовательские фильтры без использования БД: Используйте ПостроительЗапроса и его коллекцию Отбор.
  4. Сверхбыстрый многократный поиск в циклах (оптимизация производительности): Перекладывайте данные из ТЗ в объект Соответствие с генерацией уникального ключа.

Выбирайте наиболее подходящий инструмент под конкретные требования вашей системы, соблюдайте требования платформы по типизации и не забывайте про индексацию для обеспечения максимальной скорости работы ваших алгоритмов!

← На главную