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