При работе с динамическими списками в 1С:Предприятие 8.3 программисты часто сталкиваются с ситуацией, когда платформа генерирует крайне тяжелые и неоптимальные SQL-запросы. Типичный пример такой "дичи" — это самосоединение основной таблицы (LEFT OUTER JOIN таблицы самой с собой) и использование функции ISNULL в секции WHERE. Это приводит к тому, что SQL Server не может эффективно использовать индексы (проблема SARGability), выполняет полное сканирование таблиц и вызывает значительные задержки при скроллинге списка. Разберем подробно, почему это происходит и как привести запросы к адекватному виду.
Проанализируем структуру запроса, который генерирует платформа. Если мы видим конструкцию вида FROM dbo._Document272 T1 LEFT OUTER JOIN dbo._Document272 T2 ON T1._IDRRef = T2._IDRRef, это означает, что система компоновки данных (СКД), лежащая в основе динамического списка, по какой-то причине решила извлечь данные одного и того же объекта из двух разных "источников".
Основная причина такого поведения в 1С — сортировка по полю Ссылка. В платформе 1С поле Ссылка документа не является просто идентификатором. При сортировке по ссылке система фактически выполняет сортировку по двум полям: Дата + Ссылка. Чтобы обеспечить корректный порядок вывода данных (особенно при динамическом считывании порциями), СКД добавляет левое соединение для получения актуальных значений даты и ссылки, даже если эти поля уже есть в выборке. Использование ISNULL в условиях WHERE (например, ISNULL(T2._Date_Time, '2001-01-01') < @P2) — это "страховка" компоновщика на случай, если в результате LEFT JOIN правая часть окажется пустой. Для SQL-сервера это фатально: индекс по дате перестает работать, так как значение каждого поля предварительно обрабатывается функцией.
Прежде чем менять код, проанализируем настройки списка в пользовательском режиме. Часто проблема кроется не в конфигурации, а в том, что пользователь (или программист в процессе отладки) установил "кривую" сортировку.
Ссылка, это и есть источник проблемы.Ссылка из сортировки и заменить её на конкретное поле, например, Дата.Как показывает практика, удаление ссылки из активной сортировки мгновенно заставляет движок 1С генерировать чистый запрос без лишних соединений.
Если мы хотим запретить пользователям совершать такие ошибки или принудительно очистить запрос от лишних данных, воспользуемся расширением языка запросов для СКД. Рассмотрим пример, как правильно оформить текст запроса в динамическом списке, используя фигурные скобки {}.
Проанализируем ситуацию: нам нужно явно указать платформе, какие поля можно использовать для отборов и компоновки, а какие — нет. Используем конструкцию:
ВЫБРАТЬ
Документ1.Ссылка КАК Ссылка,
Документ1.Номер КАК Номер,
Документ1.Дата КАК Дата,
{ВЫБРАТЬ Ссылка.*, Номер, Дата}
ИЗ
Документ.Документ1 КАК Документ1
{ГДЕ Документ1.Ссылка.*, Документ1.Номер, Документ1.Дата}
Применение фигурных скобок {ВЫБРАТЬ} и {ГДЕ} позволяет жестко контролировать, какие поля будут участвовать в построении итогового SQL-запроса. Это исключает использование дочерних реквизитов (через точку), которые часто и вызывают нежелательные JOIN.
Чтобы окончательно убедиться, какой именно запрос отправляется в СУБД после всех преобразований компоновщика, мы можем вывести "исполняемую схему" программно. Разберем полезный прием: добавим на форму временную кнопку и проанализируем результат выполнения следующего кода:
// Получаем реальный текст запроса, который строит СКД
Компоновщик = Новый КомпоновщикМакетаКомпоновкиДанных;
Схема = Элементы.Список.ПолучитьИсполняемуюСхемуКомпоновкиДанных();
Настройки = Элементы.Список.ПолучитьИсполняемыеНастройкиКомпоновкиДанных();
Макет = Компоновщик.Выполнить(Схема, Настройки);
ТекстЗапросаДляСУБД = Макет.НаборыДанных[0].Запрос;
Сообщить(ТекстЗапросаДляСУБД);
Этот метод позволяет увидеть запрос именно в том виде, в котором его "видит" платформа перед отправкой на SQL-сервер, включая все наложенные пользователем фильтры и сортировки. Проанализировав ТекстЗапросаДляСУБД, мы сможем точно понять, какое поле вызывает LEFT JOIN.
Бывают случаи, когда программист уже исправил запрос в конфигураторе, убрал все лишние сортировки, но профайлер SQL всё равно показывает старую "дичь". Выясним причину: настройки динамических списков сохраняются в системном хранилище настроек в разрезе пользователей и полных имен форм.
Рассмотрим порядок действий для очистки:
ХранилищеОбщихНастроек.КомпоновщикНастроек к состоянию, определенному в конфигураторе.Проанализируем еще два важных момента, которые могут влиять на появление ISNULL:
1. Ограничение доступа на уровне записей (RLS). Если у пользователя ограничены права, платформа будет принудительно добавлять условия в секцию WHERE для проверки прав. Эти условия практически всегда содержат ISNULL и дополнительные соединения с таблицами прав. Если под "Полными правами" запрос выглядит нормально, а под пользователем — "дико", значит, проблему нужно решать оптимизацией шаблонов RLS.
2. Свойство "Динамическое считывание данных". В свойствах динамического списка (в конфигураторе) есть флаг ДинамическоеСчитываниеДанных. Когда он включен, платформа использует специальные алгоритмы пагинации (Key-set pagination), которые требуют сравнения текущей записи с предыдущей/следующей. Именно здесь часто рождаются сложные условия в WHERE с использованием ISNULL по полям даты и ссылки для обеспечения стабильности курсора при прокрутке.
Резюме: Чтобы избавиться от лишних LEFT JOIN и ISNULL, в первую очередь откажитесь от сортировки по полю Ссылка. Используйте явную сортировку по Дата (и, если нужно, дополнительно по Номер или другому индексированному полю). Контролируйте состав доступных полей через фигурные скобки в запросе и не забывайте очищать пользовательские настройки после внесения изменений в метаданные.