Как исправить ошибку «Построенный запрос к СУБД использует слишком много таблиц» в 1С?

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

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

Разберем природу этого поведения, выясним причину конфликта СУБД и RLS, а также рассмотрим пошаговые способы решения проблемы.

Почему возникает ошибка: ограничения СУБД и механизм RLS

Проанализируем архитектурные факторы, сочетание которых приводит к падению запроса:

  1. Лимит MS SQL Server на количество таблиц. В СУБД Microsoft SQL Server установлено строгое внутреннее ограничение: в одном SQL-запросе (внутри секции FROM, с учетом всех объединений JOIN, подзапросов и представлений) не может участвовать более 256 таблиц.
  2. Механизм трансляции ограничений доступа (RLS). Когда пользователь с ограниченными правами выполняет запрос со словом РАЗРЕШЕННЫЕ, платформа 1С автоматически подменяет обращения к реальным таблицам метаданных подзапросами с проверками прав. Если в системе настроен RLS по нескольким разрезам (организации, подразделения, группы доступа физических лиц), к каждому обращению к регистру или справочнику добавляются соединения с таблицами профилей, групп пользователей и прав. В результате большой пакет запросов разрастается в сотни физических соединений.
  3. Разыменование полей составного типа (обращение через точку). Поле Регистратор в регистрах накопления и сведений является составным ссылочным типом и может включать в себя десятки видов документов. При обращении в запросе к полям Данные.Регистратор.Номер или Данные.Регистратор.Дата платформа 1С вынуждена сгенерировать LEFT JOIN ко всем существующим таблицам документов, входящих в составной тип. Если составной тип включает 30 документов, обращение к двум реквизитам сразу добавит 60 дополнительных таблиц в итоговый SQL-запрос.

Способ 1. Устранение неявных разыменований составных типов и использование функции ВЫРАЗИТЬ

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


ВЫБРАТЬ
    Данные.Сотрудник КАК Сотрудник,
    Данные.Регистратор КАК Регистратор,
    Данные.Регистратор.Дата КАК РегистраторДата,
    Данные.Регистратор.Номер КАК РегистраторНомер
ИЗ
    ОбщиеЗапросыНачисленияУдержания КАК Данные

Для устранения лишних LEFT JOIN применим следующие приемы:

  1. Исключение некритичных реквизитов. Если номер документа и дата не обязательны в печатной форме или могут быть заменены стандартными реквизитами самого регистра (например, поле Период регистра накопления содержит дату регистратора), убираем обращения через точку.
  2. Сужение типа с помощью конструкции ВЫРАЗИТЬ. Если реквизиты документа необходимы, явно ограничим тип конкретным документом. В этом случае СУБД соединится только с одной конкретной таблицей, а не со всем списком:

ВЫБРАТЬ
    Данные.Сотрудник КАК Сотрудник,
    Данные.Регистратор КАК Регистратор,
    ВЫРАЗИТЬ(Данные.Регистратор КАК Документ.НачислениеЗарплаты).Номер КАК НомерНачисления,
    ВЫРАЗИТЬ(Данные.Регистратор КАК Документ.Премия).Номер КАК НомерПремии
ИЗ
    ОбщиеЗапросыНачисленияУдержания КАК Данные

Способ 2. Разделение запроса через МенеджерВременныхТаблиц

Если запрос сам по себе содержит большое количество пакетов и объединений (ОБЪЕДИНИТЬ ВСЕ), разделим единый пакет на несколько независимых вызовов объекта Запрос, связав их через один общий МенеджерВременныхТаблиц. При этом ограничение на 256 таблиц будет действовать на каждый исполняемый запрос отдельно.

Рассмотрим пример программной реализации:


МенеджерВТ = Новый МенеджерВременныхТаблиц;

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

Запрос1.УстановитьПараметр("НачалоПериода", НачалоПериода);
Запрос1.УстановитьПараметр("КонецПериода", КонецПериода);
Запрос1.УстановитьПараметр("Организация", Организация);
Запрос1.Выполнить();

// Второй запрос: обращается к уже готовой временной таблице в памяти СУБД
Запрос2 = Новый Запрос;
Запрос2.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос2.Текст = 
    "ВЫБРАТЬ
    |	Движения.Организация КАК Организация,
    |	Движения.Сотрудник КАК Сотрудник,
    |	Движения.Сумма КАК Сумма
    |ИЗ
    |	ВТ_Движения КАК Движения";

РезультатТаблица = Запрос2.Выполнить().Выгрузить();

// Обязательно закрываем менеджер после завершения работы
МенеджерВТ.Закрыть();

Способ 3. Передача таблицы значений во внешний набор данных СКД

Если отчет построен на схеме компоновки данных, и оптимизация текста запроса не дает результата из-за жестких правил RLS, вынесем тяжелые расчеты в процедуру модуля объекта ПриКомпоновкеРезультата. Заполним данные в коде и передадим результат в СКД через ВнешнийНаборДанных:


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

Способ 4. Нюансы использования привилегированного режима во внешних отчетах

Попытка отключить RLS с помощью метода УстановитьПривилегированныйРежим(Истина) внутри внешнего отчета или обработки часто не дает эффекта, если отчет открыт в безопасном режиме.

Разберем, как правильно обеспечить выполнение кода с отключением проверок RLS:

  1. Снятие безопасного режима при подключении. В справочнике «Дополнительные отчеты и обработки» для нужного отчета администратор должен снять флаг «Безопасный режим».
  2. Настройки расширения. Если отчет внедряется через расширение конфигурации, в свойствах расширения необходимо отключить флаги «Безопасный режим» и «Защита от опасных действий».
  3. Отключение автозаполнения в СКД. В наборе данных типа «Запрос» схемы компоновки рекомендуется снять флаг «Автозаполнение». Это предотвратит автоматическое включение платформой неиспользуемых полей и связей в финальный текст SQL-запроса.
← На главную