При разработке интерфейсов на обычных формах в платформе 1С:Предприятие 8 часто возникает задача: получить программный доступ именно к тем строкам динамического списка (справочника или документов), которые пользователь видит на экране в данный момент. Пользователь может наложить сложные интерактивные фильтры, используя стандартную панель отборов (например, отбор по группе, по части наименования, по дате или по специфическому реквизиту). Нам же требуется выгрузить этот отфильтрованный результат в таблицу значений или массив для дальнейшей программной обработки, проведения документов, анализа или формирования отчетов.
В рамках этой статьи мы разберем два основных метода решения этой задачи: элегантный автоматический способ с использованием механизма построителя запроса и классический ручной способ переноса отборов. Также мы детально проанализируем критически важные технические нюансы, связанные с иерархическими справочниками, системными ошибками СУБД и особенностями работы с таблицами значений.
Малоизвестный, но крайне изящный лайфхак платформы 1С заключается в том, что при передаче динамического списка в качестве источника данных для объекта ПостроительЗапроса все активные пользовательские отборы наследуются и накладываются на запрос автоматически. Платформа самостоятельно анализирует состояние элемента управления на форме и транслирует его настройки в структуру построителя. Это избавляет разработчика от необходимости вручную обходить коллекцию элементов отбора.
Рассмотрим пример кода, реализующий этот подход для списка документов (переменная ДокументСписок):
Построитель = Новый ПостроительЗапроса;
// Передаем динамический список формы в качестве источника данных
Построитель.ИсточникДанных = Новый ОписаниеИсточникаДанных(ДокументСписок);
// Выполняем запрос с учетом автоматически унаследованных отборов
Построитель.Выполнить();
// Выгружаем полученный результат в таблицу значений
ТаблицаРезультата = Построитель.Результат.Выгрузить();
Проанализируем этот код. Объект ОписаниеИсточникаДанных инициализируется переменной динамического списка (это может быть СправочникСписок или ДокументСписок, связанный с табличным полем формы). При вызове метода Выполнить() система формирует SQL-запрос к базе данных, автоматически добавляя в секцию ГДЕ все условия, которые пользователь задал интерактивно. На выходе в переменной ТаблицаРезультата мы получаем готовую плоскую таблицу значений, содержащую только видимые пользователю строки.
Если автоматический способ по каким-то причинам не подходит (например, требуется тонкая предварительная модификация условий отбора или отборы нужно наложить на другой объект базы данных), мы можем использовать классический ручной обход коллекции. Для этого мы явным образом считываем настройки из свойства списка ОтборСтрок и переносим их в коллекцию отборов построителя.
Разберем по шагам, как это реализовать программно:
// Получаем коллекцию текущих отборов из табличного поля формы
ТекОтбор = ЭлементыФормы.СписокСправочника.ОтборСтрок;
Построитель = Новый ПостроительОтчета;
Построитель.ИсточникДанных = Новый ОписаниеИсточникаДанных(СписокСправочника);
// Переносим только активные отборы пользователя
Для каждого ЭлементОтбора Из ТекОтбор Цикл
Если ЭлементОтбора.Использование Тогда
НовыйЭлемент = Построитель.Отбор.Добавить(ЭлементОтбора.ПутьКДанным, ЭлементОтбора.Имя, ЭлементОтбора.Представление);
НовыйЭлемент.ВидСравнения = ЭлементОтбора.ВидСравнения;
НовыйЭлемент.Значение = ЭлементОтбора.Значение;
НовыйЭлемент.ЗначениеС = ЭлементОтбора.ЗначениеС;
НовыйЭлемент.ЗначениеПо = ЭлементОтбора.ЗначениеПо;
НовыйЭлемент.Использование = Истина;
КонецЕсли;
КонецЦикла;
// Получаем и выгружаем результат
РезультатЗапроса = Построитель.Результат;
ТаблицаРезультата = РезультатЗапроса.Выгрузить();
Этот метод более громоздкий, но он гарантирует полный контроль над процессом переноса каждого отдельного параметра фильтрации. Мы можем переопределять виды сравнения, добавлять собственные жестко заданные условия или исключать ненужные пользователю поля из итогового запроса.
Несмотря на то, что автоматический способ кажется идеальным, на практике разработчик сталкивается с рядом серьезных ограничений платформы, о которых важно знать заранее.
СправочникСписок для иерархического справочника, и на форме включен режим отображения «Иерархический просмотр», могут возникнуть расхождения в данных. Построитель запроса по умолчанию не знает, в какой именно группе (папке) сейчас находится пользователь на форме, и какие группы в данный момент развернуты или свернуты (если только этот отбор по родителю не задан явно в свойствах отбора списка). В результате запрос может выгрузить элементы из соседних папок, которые пользователь на экране в данный момент физически не видит.
ОписаниеИсточникаДанных(СправочникСписок) для некоторых видов справочников (особенно неиерархических или имеющих сложную структуру реквизитов) платформа 1С может выдать системную ошибку СУБД (ошибка SDBL). Это происходит потому, что при генерации запроса «под капотом» транслятор пытается развернуть все доступные поля объекта (аналог ВЫБРАТЬ *). Для неиерархического справочника транслятор запросов иногда ошибочно пытается запросить системное поле Родитель, которого физически не существует в таблице базы данных, что приводит к аварийному завершению работы. Если вы столкнулись с этой ошибкой, вам придется отказаться от автоматического метода и вернуться к ручному наложению отборов с явным указанием полей выборки в построителе.
ТаблицаЗначений или табличная часть документа, и пользователь наложил на нее визуальный фильтр через встроенный механизм поиска или свойство ОтборСтрок, простой запуск ОписаниеИсточникаДанных(ТЗ) не передаст эти отборы в построитель. Сама таблица значений в памяти всегда остается полной и неотфильтрованной. В этом случае необходимо вручную скопировать элементы отбора из табличного поля формы в коллекцию отборов построителя запроса, как это показано ниже:
Построитель = Новый ПостроительЗапроса;
Построитель.ИсточникДанных = Новый ОписаниеИсточникаДанных(ТаблицаДанных);
// Вручную переносим отбор строк табличного поля
Для каждого ЭлементОтбора Из ЭлементыФормы.ТабличноеПолеДанных.ОтборСтрок Цикл
Если ЭлементОтбора.Использование Тогда
НовыйЭлемент = Построитель.Отбор.Добавить(ЭлементОтбора.Имя);
НовыйЭлемент.ВидСравнения = ЭлементОтбора.ВидСравнения;
НовыйЭлемент.Значение = ЭлементОтбора.Значение;
НовыйЭлемент.ЗначениеС = ЭлементОтбора.ЗначениеС;
НовыйЭлемент.ЗначениеПо = ЭлементОтбора.ЗначениеПо;
НовыйЭлемент.Использование = Истина;
КонецЕсли;
КонецЦикла;
Построитель.Выполнить();
ТаблицаРезультата = Построитель.Результат.Выгрузить();
Если перед нами стоит задача отфильтровать именно Таблицу Значений, находящуюся в оперативной памяти, то использование тяжеловесного построителя запросов не всегда оправдано с точки зрения производительности.
Выясним, какие альтернативы у нас есть:
ТаблицаЗначений.НайтиСтроки(СтруктураПараметров). Он отрабатывает напрямую в оперативной памяти и работает значительно быстрее построителя, так как не инициирует создание дополнительных тяжелых объектов.
НайтиСтроки здесь не поможет, так как он поддерживает только строгое равенство. В таких случаях связка ПостроительЗапроса + ОписаниеИсточникаДанных(ТЗ) с ручным заполнением коллекции Построитель.Отбор действительно является лучшим, самым гибким и надежным решением.
Очень часто разработчики используют описанные приемы исключительно для того, чтобы вывести на форму информационную надпись для пользователя (например, «Отображено строк: 15 из 150»). Делать полную выгрузку результата построителя в таблицу значений только ради получения количества записей крайне неоптимально, так как на это тратятся ресурсы процессора и оперативной памяти для создания копии данных.
Проанализируем, как сделать это гораздо быстрее. Мы можем получить количество строк напрямую из выборки без детальной выгрузки данных в память:
// Быстрый способ получения количества отфильтрованных строк
КоличествоСтрок = Построитель.Результат.Выбрать().Количество();
Такой подход работает в разы быстрее, особенно на больших списках данных, так как платформа не выполняет ресурсоемких операций по формированию новой таблицы значений на клиенте или сервере.