Как вывести элементы табличной части 1С с отбором по подстроке в наименовании?

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

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

Разбор ошибок исходного кода: почему массив не выводился?

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


&НаСервере
Процедура ВыгрузитьВМассивНаСервере()
    ТЗ = Объект.Исполнители.Выгрузить();
    Массив1= ТЗ.ВыгрузитьКолонку("НомерСтроки");
    Массив1= ТЗ.ВыгрузитьКолонку("Исполнитель");
    Для каждого ЭлементМассива из Массив1 Цикл
        Индекс = Массив1.Найти("иван");
        Если Индекс <> Неопределено Тогда
            Сообщить(ЭлементМассива[Индекс]);
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

В этом небольшом фрагменте кода сосредоточено сразу несколько критических ошибок:

  1. Перезапись переменной: Строка Массив1 = ТЗ.ВыгрузитьКолонку("НомерСтроки") полностью теряет свой смысл на следующем шаге, так как во вторую строку Массив1 = ТЗ.ВыгрузитьКолонку("Исполнитель") записывается совершенно другое значение, заменяя собой старый массив.
  2. Несоответствие типов данных: Функция ТЗ.ВыгрузитьКолонку("Исполнитель") возвращает массив, элементами которого являются ссылки на элементы справочника (тип данных СправочникСсылка.Менеджеры или аналогичный). В цикле выполняется метод Массив1.Найти("иван"). Этот метод пытается найти точное совпадение строкового значения "иван" среди элементов массива, которые являются ссылками. Разумеется, сравнение типа СправочникСсылка со строкой "иван" всегда возвращает ложь, поэтому поиск выдает значение Неопределено.
  3. Ошибочное обращение по индексу: Конструкция ЭлементМассива[Индекс] некорректна. Переменная ЭлементМассива является конкретным элементом коллекции (в данном случае ссылкой на справочник), а не самим массивом. Обращение по индексу должно производиться к исходному массиву: Массив1[Индекс].

Скрытая архитектурная угроза: Проблема N+1 запросов

В процессе обсуждения на форуме участники предложили следующее решение проблемы:


Для а = 0 по Массив1.ВГраница() Цикл
    Если СтрНайти(НРег(Массив1[а].Наименование), "иван") Тогда
        Сообщить("Получилось " + а);
    КонецЕсли;
КонецЦикла;

Рассмотрим подробнее этот вариант. Он будет работать и решать задачу автора, но этот код таит в себе серьезную опасность для производительности системы — так называемую проблему N+1 запросов к базе данных.

Переменная Массив1[а] содержит ссылку на объект в базе данных. Ссылка в 1С — это всего лишь уникальный идентификатор (GUID). Она не хранит в себе текстовые реквизиты, такие как наименование или код. Когда программа выполняет конструкцию Массив1[а].Наименование, платформа 1С вынуждена в фоновом режиме совершить отдельное неявное обращение (запрос) к таблице СУБД, чтобы получить наименование этого конкретного элемента справочника.

Если в табличной части вашего документа будет 100 строк, система сделает 100 отдельных микрозапросов к базе данных. Если строк будет 1000 — система сделает 1000 запросов. На реальной базе данных в многопользовательском режиме это вызовет колоссальные задержки и приведет к зависанию интерфейса пользователя. Ниже мы разберем профессиональные методы, которые полностью лишены этого недостатка.

Способ 1. Оптимальное решение через механизм запросов

В экосистеме «1С:Предприятие» для любых выборок, фильтраций и поиска данных по текстовым реквизитам объектов всегда следует использовать Запросы. Запрос выполняется целиком на стороне СУБД за одно обращение и возвращает уже готовый отфильтрованный результат.

Вариант А: Если документ уже записан в базу данных

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


&НаСервере
Процедура НайтиИсполнителейЗапросомНаСервере()
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	ДокИсполнители.Исполнитель КАК Исполнитель
        |ИЗ
        |	Документ.ИмяВашегоДокумента.Исполнители КАК ДокИсполнители
        |ГДЕ
        |	ДокИсполнители.Ссылка = &Ссылка
        |	И ДокИсполнители.Исполнитель.Наименование ПОДОБНО &ШаблонПоиска";
    
    Запрос.УстановитьПараметр("Ссылка", Объект.Ссылка);
    // Использование оператора ПОДОБНО и спецсимволов % позволяет искать подстроку в любом месте наименования без учета регистра
    Запрос.УстановитьПараметр("ШаблонПоиска", "%иван%"); 
    
    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        Сообщить("Найдено через запрос: " + Выборка.Исполнитель);
    КонецЦикла;
    
КонецПроцедуры

Вариант Б: Если документ еще не записан (данные находятся только на форме)

Если пользователь только что открыл документ, внес изменения в табличную часть на форме и еще не нажал кнопку «Записать», физически этих данных в базе данных еще нет. Но мы все равно можем использовать эффективный механизм запросов! Для этого мы выгрузим табличную часть в Таблицу Значений и передадим ее в запрос как параметр временной таблицы:


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

Способ 2. Быстрый прямой обход табличной части (без создания промежуточных коллекций)

Если вы абсолютно уверены, что табличная часть документа содержит незначительное количество строк (например, до 15–20 позиций) и использование запроса кажется вам избыточным, алгоритм обхода можно существенно упростить. Совсем не обязательно выгружать данные сначала в таблицу значений, затем в массив, тратя оперативную память сервера. Мы можем обходить строки табличной части напрямую.

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

Разберем данный метод по шагам:


&НаСервере
Процедура ВыгрузитьНапрямуюБыстро()
    
    Для Каждого СтрокаТЧ Из Объект.Исполнители Цикл
        
        // Обязательно проверяем заполненность, чтобы избежать ошибок при пустой строке
        Если ЗначениеЗаполнено(СтрокаТЧ.Исполнитель) Тогда 
            
            // Получаем текстовое представление ссылки. Это работает быстрее обращения к .Наименование
            ИмяИсполнителя = Строка(СтрокаТЧ.Исполнитель); 
            
            // Приводим строку к нижнему регистру с помощью функции НРег() для независимости от регистра
            Если СтрНайти(НРег(ИмяИсполнителя), "иван") > 0 Тогда
                Сообщить("Успешно найдено напрямую: " + ИмяИсполнителя);
            КонецЕсли;
            
        КонецЕсли;
        
    КонецЦикла;
    
КонецПроцедуры

В чем разница между СтрНайти() и Найти() в 1С?

Обратите внимание на использование функции СтрНайти() в коде выше. Начинающие разработчики часто путают ее с устаревшей функцией Найти(). Разберем ключевые различия:

Резюме и рекомендации

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

  1. Никогда не обращайтесь к реквизитам через точку (например, Ссылка.Наименование) внутри циклов. Это порождает критическую проблему производительности N+1 запросов.
  2. Если требуется выполнить отбор по сложному текстовому условию (например, поиск по фрагменту наименования), всегда отдавайте предпочтение Запросам с использованием оператора ПОДОБНО. Это гарантирует максимальное быстродействие системы.
  3. Для простых задач на небольших коллекциях данных используйте прямой обход коллекции и встроенную функцию Строка() для быстрого получения текстового представления ссылочных данных без обращения к базе данных.
← На главную