При разработке интерфейсов и обработок в среде 1С:Предприятие часто возникает задача фильтрации данных во встроенных таблицах. Типичный сценарий — отображение списка фоновых или регламентных заданий с возможностью выбора пользователем определенных состояний или параметров. Однако попытка применить стандартный метод НайтиСтроки() к таблице значений нередко приводит к системным ошибкам, таким как «Неизвестная колонка» или к полному отсутствию результатов при поиске по массиву.
В этой статье мы подробно разберем, почему возникают подобные ошибки, рассмотрим ограничения платформенных методов и проанализируем эффективные способы фильтрации таблиц значений.
Проанализируем две основные проблемы, с которыми сталкиваются разработчики при вызове метода ТаблицаЗначений.НайтиСтроки().
Таблица.Колонки (например, поле, предназначенное для системного вызова менеджера фоновых заданий), платформа незамедлительно прерывает выполнение с ошибкой.НайтиСтроки() проверяет строгое равенство (=) между значением в колонке и значением в структуре отбора. Если передать в качестве значения массив (например, список состояний СостояниеФоновогоЗадания), метод попытается найти строку, ячейка которой буквально содержит ссылку на этот массив, а не проверит вхождение единичного статуса в переданный список. В результате такой отбор всегда вернет пустой результат.Если мы получаем список фоновых заданий напрямую из менеджера ФоновыеЗадания, платформа использует строгую типизацию полей отбора:
Состояние принимает массив значений системного перечисления СостояниеФоновогоЗадания.РегламентноеЗадание принимает либо объект типа РегламентноеЗадание, либо его УникальныйИдентификатор. Передача обычной строки с наименованием или строковым представлением вызовет исключение несоответствия типов.Посмотрим на пример корректной подготовки структуры отбора для менеджера фоновых заданий, если на форме хранится строковый идентификатор:
Отбор = Новый Структура;
// Передача массива состояний поддерживается системным менеджером напрямую
МассивСостояний = Новый Массив;
МассивСостояний.Добавить(СостояниеФоновогоЗадания.Активно);
МассивСостояний.Добавить(СостояниеФоновогоЗадания.Завершено);
Отбор.Вставить("Состояние", МассивСостояний);
// Преобразование строкового идентификатора в УникальныйИдентификатор
Если ЗначениеЗаполнено(СтрокаGUID) Тогда
Отбор.Вставить("РегламентноеЗадание", Новый УникальныйИдентификатор(СтрокаGUID));
КонецЕсли;
Фоновые = ФоновыеЗадания.ПолучитьФоновыеЗадания(Отбор);
Если данные уже выгружены в таблицу значений, и нам требуется быстро отобрать строки по вхождению в список значений или массив, наиболее универсальным и производительным способом является использование объекта Запрос. В тексте запроса мы можем использовать конструкцию ГДЕ Поле В (&Массив).
Важное требование: чтобы передать таблицу значений в качестве параметра запроса, у колонок таблицы должно быть явно задано ОписаниеТипов. Если таблица формировалась динамически без типизации колонок, платформа выдаст ошибку.
Разберем практическую реализацию фильтрации через временную таблицу запроса:
// Предположим, что у нас есть заполненная ТаблицаДанных с типизированными колонками
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТЗ.Идентификатор КАК Идентификатор,
| ТЗ.Наименование КАК Наименование,
| ТЗ.ИмяМетода КАК ИмяМетода,
| ТЗ.Состояние КАК Состояние,
| ТЗ.Начало КАК Начало,
| ТЗ.Конец КАК Конец,
| ТЗ.Регламентное КАК Регламентное
|ИЗ
| &ТаблицаДанных КАК ТЗ
|ГДЕ
| ТЗ.Состояние В (&МассивСостояний)
| И ТЗ.Регламентное ПОДОБНО &СтрокаПоиска";
Запрос.УстановитьПараметр("ТаблицаДанных", ТаблицаФоновыеЗадания);
Запрос.УстановитьПараметр("МассивСостояний", СостоянияЗначения);
Запрос.УстановитьПараметр("СтрокаПоиска", "%" + СтрокаОтбора + "%");
ОтфильтрованнаяТаблица = Запрос.Выполнить().Выгрузить();
Если таблица значений создана без жесткого описания типов колонок, выполнение обычного запроса завершится ошибкой. В этом случае мы можем применить ПостроительЗапроса. Он автоматически строит схему по переданной таблице значений и не требует обязательной строгой типизации полей.
Рассмотрим, как настроить отбор по списку через построитель:
Построитель = Новый ПостроительЗапроса;
Построитель.ИсточникДанных = Новый ОписаниеИсточникаДанных(ТаблицаФоновыеЗадания);
// Добавляем отбор по колонке Состояние
ОтборСостояние = Построитель.Отбор.Добавить("Состояние");
ОтборСостояние.ВидСравнения = ВидСравнения.ВСписке;
ОтборСостояние.Значение.ЗагрузитьЗначения(СостоянияЗначения);
ОтборСостояние.Использование = Истина;
// При необходимости добавляем строковый отбор
Если Не ПустаяСтрока(СтрокаПоиска) Тогда
ОтборИмя = Построитель.Отбор.Добавить("Наименование");
ОтборИмя.ВидСравнения = ВидСравнения.Содержит;
ОтборИмя.Значение = СтрокаПоиска;
ОтборИмя.Использование = Истина;
КонецЕсли;
Построитель.Выполнить();
ОтфильтрованнаяТаблица = Построитель.Результат.Выгрузить();
В сценариях, когда таблица содержит небольшое количество записей (до нескольких сотен или тысяч строк, что типично для списков регламентных и фоновых заданий), перебор в цикле работает быстрее, чем инициализация запроса или построителя. Такой подход не требует типизации колонок и полностью исключает системные ошибки несоответствия структуры.
Посмотрим на пример фильтрации перебором с проверкой вхождения в массив:
НайденныеСтроки = Новый Массив;
Для Каждого СтрТЗ Из ТаблицаФоновыеЗадания Цикл
// Проверяем наличие значения в массиве разрешенных состояний
Если СостоянияЗначения.Найти(СтрТЗ.Состояние) <> Неопределено Тогда
// Проверяем дополнительное строковое условие, если оно задано
Если ЗначениеЗаполнено(СтрокаПоиска) Тогда
Если СтрНайти(СтрТЗ.Регламентное, СтрокаПоиска) > 0 Тогда
НайденныеСтроки.Добавить(СтрТЗ);
КонецЕсли;
Иначе
НайденныеСтроки.Добавить(СтрТЗ);
КонецЕсли;
КонецЕсли;
КонецЦикла;
При работе в управляемом приложении таблица на форме имеет тип ДанныеФормыКоллекция, а не ТаблицаЗначений. Обратиться к такой коллекции напрямую из запроса или построителя нельзя. Для работы с запросами необходимо предварительно выполнить преобразование данных.
Разберем по шагам алгоритм фильтрации и сохранения выделения строк на форме:
РеквизитФормыВЗначение().ЗначениеВРеквизитФормы().
&НаСервере
Процедура ПрименитьОтборНаСервере()
// 1. Сохраняем позицию курсора
ТекущийИдентификатор = Элементы.ТаблицаФоновыеЗадания.ТекущаяСтрока;
// 2. Преобразуем данные формы в Таблицу Значений
ТЗ = РеквизитФормыВЗначение("ТаблицаФоновыеЗадания", Тип("ТаблицаЗначений"));
// 3. Выполняем фильтрацию (например, через ПостроительЗапроса)
Построитель = Новый ПостроительЗапроса;
Построитель.ИсточникДанных = Новый ОписаниеИсточникаДанных(ТЗ);
ОтборСостояние = Построитель.Отбор.Добавить("Состояние");
ОтборСостояние.ВидСравнения = ВидСравнения.ВСписке;
ОтборСостояние.Значение.ЗагрузитьЗначения(СостоянияЗначения);
ОтборСостояние.Использование = Истина;
Построитель.Выполнить();
ОтфильтрованнаяТЗ = Построитель.Результат.Выгрузить();
// 4. Загружаем результат обратно в реквизит формы
ЗначениеВРеквизитФормы(ОтфильтрованнаяТЗ, "ТаблицаФоновыеЗадания");
// 5. Восстанавливаем фокус на строке, если она осталась в таблице
Если ТекущийИдентификатор <> Неопределено Тогда
СтрокаНаФорме = ТаблицаФоновыеЗадания.НайтиПоИдентификатору(ТекущийИдентификатор);
Если СтрокаНаФорме <> Неопределено Тогда
Элементы.ТаблицаФоновыеЗадания.ТекущаяСтрока = ТекущийИдентификатор;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Таким образом, для простых равенств по одному полю можно использовать НайтиСтроки() (предварительно очистив структуру отбора от лишних ключей), для сложных списочных отборов небольших таблиц отлично подходит обычный цикл перебора, а для тяжелых и типизированных наборов данных лучшим выбором будет классический Запрос или ПостроительЗапроса.