Работа с динамическими списками в платформе «1С:Предприятие» — одна из самых частых задач при разработке интерфейсов. Динамический список обеспечивает быстрое и отзывчивое отображение больших объемов данных за счет порционной выборки из базы данных. Однако при попытке программно изменить его текст запроса разработчики регулярно сталкиваются с неочевидным поведением системы и критическими ошибками.
В этой статье мы подробно разберем, почему возникают ошибки вида «Параметр с указанным именем не найден» и «Основная таблица динамического списка задана неверно», как их правильно локализовать и исправить, а также рассмотрим лучшие практики оптимизации таких запросов.
Рассмотрим классическую ситуацию. Разработчик пытается изменить текст запроса динамического списка на клиенте при изменении какого-либо реквизита на форме (например, фильтра по Центру финансовой ответственности — ЦФУ):
&НаКлиенте
Процедура ЦФУПриИзменении(Элемент)
Если ЗначениеЗаполнено(ЦФУ) Тогда
Список.ТекстЗапроса = ПолучитьТекстЗапросаСФильтром();
Список.Параметры.УстановитьЗначениеПараметра("ЦФУ", ЦФУ);
Иначе
Список.ТекстЗапроса = ПолучитьТиповойТекстЗапроса();
КонецЕсли;
Элементы.Список.Обновить();
ОбновитьОтображениеДанных();
КонецПроцедуры
При выполнении этого кода на строке установки параметра система генерирует исключение: «Ошибка при вызове метода контекста (УстановитьЗначениеПараметра) по причине: Параметр с указанным именем не найден».
Почему это происходит? Дело в том, что на клиенте платформа работает с «облегченной» копией данных. Когда мы присваиваем свойству Список.ТекстЗапроса новую строку, синтаксический анализ запроса на клиенте не происходит мгновенно в ту же миллисекунду. Платформа еще «не знает», какие новые параметры появились в тексте запроса, так как компиляция и проверка схемы запроса выполняются на стороне сервера. Пытаясь установить параметр &ЦФУ на клиенте сразу после изменения строки запроса, мы обращаемся к старому набору параметров, где этого имени действительно могло не быть.
Решение: Любые манипуляции со структурой динамического списка (изменение текста запроса, добавление новых параметров, изменение состава колонок) должны выполняться строго в серверном контексте. При выполнении кода на сервере платформа корректно и последовательно инициализирует новую структуру запроса.
Перенесем логику в серверную процедуру:
&НаКлиенте
Процедура ЦФУПриИзменении(Элемент)
УстановитьЗапросСпискаНаСервере();
Элементы.Список.Обновить();
ОбновитьОтображениеДанных();
КонецПроцедуры
&НаСервере
Процедура УстановитьЗапросСпискаНаСервере()
Если ЗначениеЗаполнено(ЦФУ) Тогда
Список.ТекстЗапроса = ПолучитьТекстЗапросаСФильтром();
Список.Параметры.УстановитьЗначениеПараметра("ЦФУ", ЦФУ);
Иначе
Список.ТекстЗапроса = ПолучитьТиповойТекстЗапроса();
КонецЕсли;
КонецПроцедуры
Этот шаг успешно решает проблему с видимостью параметров, но при этом мы можем столкнуться со следующей системной преградой.
После переноса кода на сервер система может выдать новую ошибку: «Основная таблица динамического списка задана неверно». При этом в конфигураторе для динамического списка четко определена основная таблица (например, Документ.ЗаказНаВнутреннееПотребление), и мы её программно не затирали.
Разберем по шагам, почему платформа теряет связь с основной таблицей. Обратим внимание на первоначальный текст динамического запроса, который разработчик пытался применить:
ВЫБРАТЬ
ЗаказНаВнутреннееПотреблениеТовары.Ссылка КАК Заказ,
ЗаказНаВнутреннееПотреблениеТовары.ЦФУ
ПОМЕСТИТЬ ФильтрПоТабличнойЧасти
ИЗ
Документ.ЗаказНаВнутреннееПотребление.Товары КАК ЗаказНаВнутреннееПотреблениеТовары
ГДЕ
ЗаказНаВнутреннееПотреблениеТовары.ЦФУ = &ЦФУ
СГРУППИРОВАТЬ ПО
ЗаказНаВнутреннееПотреблениеТовары.Ссылка,
ЗаказНаВнутреннееПотреблениеТовары.ЦФУ
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ДокументЗаказНаВнутреннееПотребление.Ссылка,
ДокументЗаказНаВнутреннееПотребление.Проведен,
ДокументЗаказНаВнутреннееПотребление.Номер,
ДокументЗаказНаВнутреннееПотребление.Дата
ИЗ
Документ.ЗаказНаВнутреннееПотребление КАК ДокументЗаказНаВнутреннееПотребление
ГДЕ
ДокументЗаказНаВнутреннееПотребление.Ссылка В
(ВЫБРАТЬ
ФильтрПоТабличнойЧасти.Заказ
ИЗ
ФильтрПоТабличнойЧасти)
Здесь используется пакетный запрос с созданием временной таблицы ФильтрПоТабличнойЧасти.
Причина сбоя: Ограничение режима совместимости. Если в вашей информационной базе установлен режим совместимости с версией платформы 8.2.16 или ниже, использование временных таблиц в динамических списках не поддерживается на уровне ядра системы. Парсер запросов динамического списка в режиме совместимости не может обработать пакетную структуру, теряет метаданные выборки и выдает ошибку о некорректности основной таблицы.
Кроме того, даже на современных версиях платформы (начиная с 8.3.8, где временные таблицы в ДС разрешены), использовать их не рекомендуется из-за проблем с производительностью. Временные таблицы в динамических списках не кэшируются. СУБД будет заново создавать и наполнять временную таблицу при каждом сдвиге экрана пользователем, поиске или интерактивном обновлении списка.
Решение: Необходимо отказаться от использования временных таблиц (конструкции ПОМЕСТИТЬ) и переписать логику на классический вложенный запрос.
Посмотрим на пример правильного, совместимого запроса:
ВЫБРАТЬ
ДокументЗаказНаВнутреннееПотребление.Ссылка,
ДокументЗаказНаВнутреннееПотребление.ПометкаУдаления,
ДокументЗаказНаВнутреннееПотребление.Номер,
ДокументЗаказНаВнутреннееПотребление.Дата,
ДокументЗаказНаВнутреннееПотребление.Проведен,
ДокументЗаказНаВнутреннееПотребление.Организация,
ДокументЗаказНаВнутреннееПотребление.Ответственный,
ДокументЗаказНаВнутреннееПотребление.Подразделение,
ДокументЗаказНаВнутреннееПотребление.Склад
ИЗ
Документ.ЗаказНаВнутреннееПотребление КАК ДокументЗаказНаВнутреннееПотребление
ГДЕ
ДокументЗаказНаВнутреннееПотребление.Ссылка В
(ВЫБРАТЬ
ЗаказНаВнутреннееПотреблениеТовары.Ссылка
ИЗ
Документ.ЗаказНаВнутреннееПотребление.Товары КАК ЗаказНаВнутреннееПотреблениеТовары
ГДЕ
ЗаказНаВнутреннееПотреблениеТовары.ЦФУ = &ЦФУ)
Такой синтаксис поддерживается абсолютно всеми версиями платформы «1С:Предприятие 8», корректно считывается парсером динамического списка и не вызывает сбоев с потерей основной таблицы.
Когда мы программно меняем свойство ТекстЗапроса, мы заставляем платформу полностью перестроить модель отображения данных на форме. Это чревато следующими проблемами:
Давайте разберем более профессиональные архитектурные решения, которые позволяют решить задачу фильтрации документов по табличной части без динамической перезаписи всего текста запроса.
Динамический список работает на базе Системы компоновки данных (СКД). Мы можем заложить возможность фильтрации по табличной части прямо в исходный текст запроса в конфигураторе, используя фигурные скобки {}. Это так называемые расширения языка запросов СКД.
Пропишем в конфигураторе следующий текст запроса динамического списка:
ВЫБРАТЬ
ДокументЗаказНаВнутреннееПотребление.Ссылка КАК Ссылка,
ДокументЗаказНаВнутреннееПотребление.Проведен КАК Проведен,
ДокументЗаказНаВнутреннееПотребление.Номер КАК Номер,
ДокументЗаказНаВнутреннееПотребление.Дата КАК Дата,
ДокументЗаказНаВнутреннееПотребление.Склад КАК Склад
ИЗ
Документ.ЗаказНаВнутреннееПотребление КАК ДокументЗаказНаВнутреннееПотребление
{ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаказНаВнутреннееПотребление.Товары КАК ЗаказНаВнутреннееПотреблениеТовары
ПО ДокументЗаказНаВнутреннееПотребление.Ссылка = ЗаказНаВнутреннееПотреблениеТовары.Ссылка}
{ГДЕ
(ЗаказНаВнутреннееПотреблениеТовары.ЦФУ = &ЦФУ)}
В чем магия этого запроса? Конструкции, заключенные в фигурные скобки, являются необязательными. Если параметр &ЦФУ не установлен или отключен в настройках компоновщика, платформа вообще исключит левое соединение с табличной частью из результирующего SQL-запроса к базе данных! Запрос будет выполняться максимально быстро, как обычный плоский список.
Чтобы активировать или деактивировать этот отбор программно, нам больше не нужно перезаписывать весь текст запроса. Достаточно управлять параметром через компоновщик настроек списка:
&НаСервере
Процедура УстановитьОтборЦФУНаСервере(ЗначениеФильтра)
// Находим параметр в настройках динамического списка
ПараметрЦФУ = Список.КомпоновщикНастроек.Настройки.ПараметрыДанных.Элементы.Найти("ЦФУ");
Если ПараметрЦФУ <> Неопределено Тогда
Если ЗначениеЗаполнено(ЗначениеФильтра) Тогда
ПараметрЦФУ.Значение = ЗначениеФильтра;
ПараметрЦФУ.Использование = Истина;
Else
ПараметрЦФУ.Использование = Ложь;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
При таком подходе структура колонок остается абсолютно неизменной, пользовательские настройки не сбрасываются, а SQL-запрос оптимизируется платформой автоматически.
Если документов в базе данных сотни тысяч или миллионы, то любая связь с табличной частью (даже через вложенный запрос или соединение в СКД) может вызывать серьезные задержки при поиске и листании списка. Это связано со спецификой построения планов запросов СУБД при работе с подчиненными таблицами.
Стандарты разработки фирмы «1С» в таких случаях рекомендуют использовать архитектурный паттерн «Индексный регистр»:
ЦФУДокументов).Документ (тип ДокументСсылка.ЗаказНаВнутреннееПотребление) и ЦФУ.Выбор конкретного метода зависит от масштаба вашей базы данных и требований к производительности, но базовое правило остается неизменным: старайтесь избегать прямой перезаписи свойства ТекстЗапроса в пользу гибкой настройки компоновщика данных динамического списка.