Как правильно отобрать строки в таблице значений по массиву или списку значений в 1С?

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

При разработке интерфейсов и обработок в среде 1С:Предприятие часто возникает задача фильтрации данных во встроенных таблицах. Типичный сценарий — отображение списка фоновых или регламентных заданий с возможностью выбора пользователем определенных состояний или параметров. Однако попытка применить стандартный метод НайтиСтроки() к таблице значений нередко приводит к системным ошибкам, таким как «Неизвестная колонка» или к полному отсутствию результатов при поиске по массиву.

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

Почему метод НайтиСтроки() вызывает ошибки?

Проанализируем две основные проблемы, с которыми сталкиваются разработчики при вызове метода ТаблицаЗначений.НайтиСтроки().

  1. Ошибка «Неизвестная колонка». Метод принимает в качестве параметра структуру, где имена ключей должны строго совпадать с именами колонок таблицы. Если структура отбора содержит хотя бы одно поле, отсутствующее в коллекции Таблица.Колонки (например, поле, предназначенное для системного вызова менеджера фоновых заданий), платформа незамедлительно прерывает выполнение с ошибкой.
  2. Поиск по списку или массиву. Метод НайтиСтроки() проверяет строгое равенство (=) между значением в колонке и значением в структуре отбора. Если передать в качестве значения массив (например, список состояний СостояниеФоновогоЗадания), метод попытается найти строку, ячейка которой буквально содержит ссылку на этот массив, а не проверит вхождение единичного статуса в переданный список. В результате такой отбор всегда вернет пустой результат.

Особенности системного отбора в ФоновыеЗадания.ПолучитьФоновыеЗадания()

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

  1. Поле Состояние принимает массив значений системного перечисления СостояниеФоновогоЗадания.
  2. Поле РегламентноеЗадание принимает либо объект типа РегламентноеЗадание, либо его УникальныйИдентификатор. Передача обычной строки с наименованием или строковым представлением вызовет исключение несоответствия типов.

Посмотрим на пример корректной подготовки структуры отбора для менеджера фоновых заданий, если на форме хранится строковый идентификатор:


Отбор = Новый Структура;

// Передача массива состояний поддерживается системным менеджером напрямую
МассивСостояний = Новый Массив;
МассивСостояний.Добавить(СостояниеФоновогоЗадания.Активно);
МассивСостояний.Добавить(СостояниеФоновогоЗадания.Завершено);
Отбор.Вставить("Состояние", МассивСостояний);

// Преобразование строкового идентификатора в УникальныйИдентификатор
Если ЗначениеЗаполнено(СтрокаGUID) Тогда
    Отбор.Вставить("РегламентноеЗадание", Новый УникальныйИдентификатор(СтрокаGUID));
КонецЕсли;

Фоновые = ФоновыеЗадания.ПолучитьФоновыеЗадания(Отбор);

Способ 1. Фильтрация Таблицы Значений с помощью Запроса

Если данные уже выгружены в таблицу значений, и нам требуется быстро отобрать строки по вхождению в список значений или массив, наиболее универсальным и производительным способом является использование объекта Запрос. В тексте запроса мы можем использовать конструкцию ГДЕ Поле В (&Массив).

Важное требование: чтобы передать таблицу значений в качестве параметра запроса, у колонок таблицы должно быть явно задано ОписаниеТипов. Если таблица формировалась динамически без типизации колонок, платформа выдаст ошибку.

Разберем практическую реализацию фильтрации через временную таблицу запроса:


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

Запрос.УстановитьПараметр("ТаблицаДанных", ТаблицаФоновыеЗадания);
Запрос.УстановитьПараметр("МассивСостояний", СостоянияЗначения);
Запрос.УстановитьПараметр("СтрокаПоиска", "%" + СтрокаОтбора + "%");

ОтфильтрованнаяТаблица = Запрос.Выполнить().Выгрузить();

Способ 2. Использование объекта ПостроительЗапроса

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

Рассмотрим, как настроить отбор по списку через построитель:


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

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

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

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

Способ 3. Фильтрация простым перебором строк

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

Посмотрим на пример фильтрации перебором с проверкой вхождения в массив:


НайденныеСтроки = Новый Массив;

Для Каждого СтрТЗ Из ТаблицаФоновыеЗадания Цикл
    // Проверяем наличие значения в массиве разрешенных состояний
    Если СостоянияЗначения.Найти(СтрТЗ.Состояние) <> Неопределено Тогда
        
        // Проверяем дополнительное строковое условие, если оно задано
        Если ЗначениеЗаполнено(СтрокаПоиска) Тогда
            Если СтрНайти(СтрТЗ.Регламентное, СтрокаПоиска) > 0 Тогда
                НайденныеСтроки.Добавить(СтрТЗ);
            КонецЕсли;
        Иначе
            НайденныеСтроки.Добавить(СтрТЗ);
        КонецЕсли;
        
    КонецЕсли;
КонецЦикла;

Нюансы управляемых форм и ДанныеФормыКоллекция

При работе в управляемом приложении таблица на форме имеет тип ДанныеФормыКоллекция, а не ТаблицаЗначений. Обратиться к такой коллекции напрямую из запроса или построителя нельзя. Для работы с запросами необходимо предварительно выполнить преобразование данных.

Разберем по шагам алгоритм фильтрации и сохранения выделения строк на форме:

  1. Сохраняем текущие выделенные строки пользователя по их уникальным идентификаторам.
  2. Преобразуем реквизит формы в полноценную таблицу значений с помощью функции РеквизитФормыВЗначение().
  3. Выполняем фильтрацию запросом или перебором.
  4. Загружаем отфильтрованные данные обратно на форму через ЗначениеВРеквизитФормы().
  5. Восстанавливаем позиционирование и выделение строк в интерфейсе.

&НаСервере
Процедура ПрименитьОтборНаСервере()
    
    // 1. Сохраняем позицию курсора
    ТекущийИдентификатор = Элементы.ТаблицаФоновыеЗадания.ТекущаяСтрока;
    
    // 2. Преобразуем данные формы в Таблицу Значений
    ТЗ = РеквизитФормыВЗначение("ТаблицаФоновыеЗадания", Тип("ТаблицаЗначений"));
    
    // 3. Выполняем фильтрацию (например, через ПостроительЗапроса)
    Построитель = Новый ПостроительЗапроса;
    Построитель.ИсточникДанных = Новый ОписаниеИсточникаДанных(ТЗ);
    
    ОтборСостояние = Построитель.Отбор.Добавить("Состояние");
    ОтборСостояние.ВидСравнения = ВидСравнения.ВСписке;
    ОтборСостояние.Значение.ЗагрузитьЗначения(СостоянияЗначения);
    ОтборСостояние.Использование = Истина;
    
    Построитель.Выполнить();
    ОтфильтрованнаяТЗ = Построитель.Результат.Выгрузить();
    
    // 4. Загружаем результат обратно в реквизит формы
    ЗначениеВРеквизитФормы(ОтфильтрованнаяТЗ, "ТаблицаФоновыеЗадания");
    
    // 5. Восстанавливаем фокус на строке, если она осталась в таблице
    Если ТекущийИдентификатор <> Неопределено Тогда
        СтрокаНаФорме = ТаблицаФоновыеЗадания.НайтиПоИдентификатору(ТекущийИдентификатор);
        Если СтрокаНаФорме <> Неопределено Тогда
            Элементы.ТаблицаФоновыеЗадания.ТекущаяСтрока = ТекущийИдентификатор;
        КонецЕсли;
    КонецЕсли;
    
КонецПроцедуры

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

← На главную