Как в запросе 1С получить все существующие документы за период без лишней нагрузки?

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

При разработке универсальных механизмов — таких как выгрузки данных, свертки баз, глобальные отчеты или системы аудита — программисты часто сталкиваются с задачей получить данные по всем видам документов в системе за определенный период. Простая задача на первый взгляд превращается в серьезную проблему производительности, если в конфигурации создано несколько сотен видов документов (например, в крупных ERP-системах их может быть более 500), а реально используется не более 30–50.

Если написать динамический запрос, который объединяет через оператор ОБЪЕДИНИТЬ ВСЕ таблицы всех 500 документов, СУБД (будь то MS SQL Server или PostgreSQL) потратит огромное количество времени и ресурсов процессора только на компиляцию плана запроса. В худшем случае система может зависнуть или выдать ошибку переполнения памяти, даже если 90% таблиц документов в базе абсолютно пусты.

В этой статье мы подробно рассмотрим несколько эффективных способов решения этой задачи: от классического динамического построения запроса с предварительным опросом таблиц до современных методов платформы «1С:Предприятие 8.3».

---

Проблема "тяжелых" запросов и почему нельзя опрашивать всё подряд

Когда мы выполняем запрос к таблице документа, СУБД обращается к физической таблице на диске. Если мы объединяем сотни таблиц в один запрос, планировщик СУБД пытается построить оптимальный план выполнения для каждого соединения. С ростом количества таблиц сложность построения этого плана растет экспоненциально.

Даже если в таблице нет записей, СУБД все равно выполняет операции чтения метаданных и проверку индексов. Поэтому наша главная задача — отсечь неиспользуемые виды документов до того, как будет скомпилирован и выполнен итоговый тяжелый запрос за период.

---

Решение 1. Динамический отбор через быструю проверку наличия данных (ВЫБРАТЬ ПЕРВЫЕ 1)

Это классический и самый надежный способ. Мы делим задачу на два этапа:

  1. Быстрым точечным запросом проверяем каждый документ на наличие хотя бы одной записи за указанный период.
  2. Строим итоговый тяжелый запрос (с получением всех нужных полей) только по тем видам документов, которые прошли первый фильтр.

Рассмотрим подробнее программную реализацию этого подхода:


// Шаг 1. Собираем список документов, в которых физически есть данные за период
СписокАктивныхДокументов = Новый Массив;

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

// Шаг 2. Строим итоговый запрос только по непустым документам
Если СписокАктивныхДокументов.Количество() = 0 Тогда
    ВызватьИсключение "За выбранный период документов в базе не обнаружено!";
КонецЕсли;

ТекстЗапросаИтоговый = "";
Для Каждого ИмяАктивногоДока Из СписокАктивныхДокументов Цикл
    Если НЕ ПустаяСтрока(ТекстЗапросаИтоговый) Тогда
        ТекстЗапросаИтоговый = ТекстЗапросаИтоговый + "
        |ОБЪЕДИНИТЬ ВСЕ
        |";
    КонецЕсли;
    
    ТекстЗапросаИтоговый = ТекстЗапросаИтоговый + 
        "ВЫБРАТЬ
        |	""" + ИмяАктивногоДока + """ КАК ВидДокумента,
        |	Док.Ссылка КАК Ссылка,
        |	Док.Дата КАК Дата,
        |	Док.Проведен КАК Проведен,
        |	Док.ПометкаУдаления КАК ПометкаУдаления
        |ИЗ
        |	Документ." + ИмяАктивногоДока + " КАК Док
        |ГДЕ
        |	Док.Дата МЕЖДУ &НачалоПериода И &КонецПериода";
КонецЦикла;

ЗапросИтоговый = Новый Запрос(ТекстЗапросаИтоговый);
ЗапросИтоговый.УстановитьПараметр("НачалоПериода", НачалоПериода);
ЗапросИтоговый.УстановитьПараметр("КонецПериода", КонецПериода);

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

Преимущества метода: Мы гарантированно отсекаем пустые таблицы. Итоговый запрос содержит ровно столько таблиц, сколько реально используется в данном периоде.

Важный нюанс оптимизации: Чтобы не выполнять шаг проверки при каждом формировании отчета, полученный массив СписокАктивныхДокументов можно кэшировать в параметры сеанса или в повторно используемый модуль с ограничением по времени.

---

Решение 2. Быстрая фильтрация по размеру таблиц на уровне платформы

Если в вашей базе установлено программное окружение платформы **8.3.15** и выше, мы можем задействовать мощный встроенный инструмент платформы — метод ПолучитьРазмерДанныхБазыДанных(). Этот метод позволяет моментально узнать, есть ли вообще физические данные в таблицах конкретных документов, минуя СУБД.

Проанализируем пример реализации:


// Собираем полные имена метаданных для анализа
МассивИменМетаданных = Новый Массив;
СоответствиеИмен = Новый Соответствие;

Для Каждого МетаданноеДокумента Из Метаданные.Документы Цикл
    ПолноеИмя = "Документ." + МетаданноеДокумента.Имя;
    МассивИменМетаданных.Добавить(ПолноеИмя);
    СоответствиеИмен.Вставить(ПолноеИмя, МетаданноеДокумента.Имя);
КонецЦикла;

// Получаем размеры таблиц в байтах напрямую из системных данных платформы
ТаблицаРазмеров = ПолучитьРазмерДанныхБазыДанных(МассивИменМетаданных);

СписокИспользуемыхТипов = Новый Массив;
Для Каждого СтрокаРазмера Из ТаблицаРазмеров Цикл
    РазмерБайт = СтрокаРазмера.Значение;
    Если РазмерБайт > 0 Тогда
        // Документ физически содержит записи в базе данных
        ИмяДока = СоответствиеИмен.Получить(СтрокаРазмера.Ключ);
        СписокИспользуемыхТипов.Добавить(ИмяДока);
    КонецЕсли;
КонецЦикла;

Как это использовать? После выполнения этого кода в массиве СписокИспользуемыхТипов останутся только те документы, которые были созданы хотя бы раз за всю историю ведения учета в базе. Это сразу отсечет все неиспользуемые механизмы конфигурации, сократив количество таблиц с 500 до условных 30–50. Далее по ним можно запускать основной запрос с фильтром по периоду.

---

Решение 3. Поиск используемых типов через регистраторы в регистрах

В реальных учетных базах (ERP, Комплексная автоматизация, Управление торговлей) документы практически никогда не существуют сами по себе — они делают движения по регистрам накопления, регистрам сведений или бухгалтерским регистрам.

Мы можем использовать этот факт для определения списка видов документов, создававших движения в заданном периоде. Сделать это можно одним быстрым запросом к виртуальным таблицам оборотов или движений ключевых регистров.

Давайте выясним, как построить такой запрос по регистру накопления:


ЗапросПоРегистраторам = Новый Запрос;
ЗапросПоРегистраторам.Текст = 
    "ВЫБРАТЬ РАЗЛИЧНЫЕ
    |	ТИПЗНАЧЕНИЯ(РегистрДвижения.Регистратор) КАК ТипДокумента
    |ИЗ
    |	РегистрНакопления.ВыручкаИПродажиНашихТоваров КАК РегистрДвижения
    |ГДЕ
    |	РегистрДвижения.Период МЕЖДУ &НачалоПериода И &КонецПериода";

// Вместо одного регистра можно объединить выборки по нескольким ключевым регистрам базы.
ЗапросПоРегистраторам.УстановитьПараметр("НачалоПериода", НачалоПериода);
ЗапросПоРегистраторам.УстановитьПараметр("КонецПериода", КонецПериода);

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

СписокДокументов = Новый Массив;
Для Каждого СтрокаТипа Из ТаблицаТипов Цикл
    Если СтрокаТипа.ТипДокумента = Неопределено Тогда
        Продолжить;
    КонецЕсли;
    
    // Получаем строковое имя метаданных по типу значения
    МетаданныеДока = Метаданные.НайтиПоТипу(СтрокаТипа.ТипДокумента);
    Если МетаданныеДока <> Неопределено Тогда
        СписокДокументов.Добавить(МетаданныеДока.Имя);
    КонецЕсли;
КонецЦикла;

Когда применять данный подход? Метод идеален для задач финансового или логистического аудита, когда нас интересуют исключительно проведенные документы, повлиявшие на учет. Он работает значительно быстрее циклического обхода метаданных, так как индексы по полю Период в регистрах оптимизированы СУБД.

---

Решение 4. Архитектурное решение через создание Журнала документов

Если задача получения всех документов за период в вашей конфигурации возникает регулярно (например, ежедневно для обмена данными), правильным решением будет доработка структуры самой конфигурации.

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

После этого запрос сводится к элементарному коду:


ЗапросЖурнала = Новый Запрос;
ЗапросЖурнала.Текст = 
    "ВЫБРАТЬ
    |	Журнал.Ссылка КАК Ссылка,
    |	Журнал.Дата КАК Дата,
    |	Журнал.ТипПредопределенногоРасчета КАК ТипДокумента
    |ИЗ
    |	ЖурналДокументов.ОбщийЖурналДокументов КАК Журнал
    |ГДЕ
    |	Журнал.Дата МЕЖДУ &НачалоПериода И &КонецПериода";

ЗапросЖурнала.УстановитьПараметр("НачалоПериода", НачалоПериода);
ЗапросЖурнала.УстановитьПараметр("КонецПериода", КонецПериода);

ТаблицаДокументов = ЗапросЖурнала.Выполнить().Выгрузить();

Преимущества метода: Максимальная скорость работы. СУБД обращается к одной-единственной индексированной таблице журнала. Нет необходимости динамически собирать запросы.

Недостатки: Требуется изменять конфигурацию (снимать с полной поддержки), а также незначительно увеличивается объем базы данных из-за создания дополнительной таблицы журнала.

---

Подведем итоги

Какое из решений выбрать для вашего проекта?

← На главную