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