Как оптимизировать медленный запрос к документам в 1С 7.7 и избежать ошибок CDX?

Программист 1С v7.7 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

Разработка на платформе 1С:Предприятие 7.7 требует особого подхода к написанию запросов, так называемых «черных запросов». В отличие от более современных версий платформы, механизмы 7.7 крайне чувствительны к количеству группировок и глубине обращения к реквизитам через «точку». В этой статье мы подробно разберем, почему «простенький» запрос может выполняться часами, как устранить ошибки индексных файлов CDX и какими способами можно ускорить выборку данных в десятки раз.

Анализ проблемы: почему запрос работает долго

Рассмотрим ситуацию: программисту необходимо выгрузить данные по реализации за месяц (номенклатура, количество, цена, контрагент). При написании стандартного запроса часто совершается ошибка — избыточное использование группировок. В 1С 7.7 каждая группировка создает дополнительный уровень вложенности итогов, что при большом объеме данных приводит к экспоненциальному росту нагрузки на дисковую подсистему и оперативную память. Если в запросе присутствуют группировки по числовым полям (Цена, Количество, Сумма), система пытается построить дерево уникальных значений для каждого числа, что практически лишает запрос смысла и «вешает» базу.

Основные причины деградации производительности:

  1. Избыточные группировки: Группировки по ресурсам (числам) или уникальным значениям внутри строк документа заставляют систему выполнять колоссальный объем вычислительной работы.
  2. Обращение через точку: Конструкции вида Контрагент.ОсновноеКонтактноеЛицо.Телефоны внутри запроса инициируют неявные соединения таблиц. В файловом варианте (DBF) это приводит к постоянному переключению между индексами разных файлов.
  3. Ошибка CDX: Это прямое следствие переполнения временных индексных файлов, которые создает 1С при выполнении тяжелых запросов. Если временный файл индекса превышает 2 ГБ или происходит сбой кэширования, запрос «отваливается».

Решение №1: Оптимизация стандартного запроса

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

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


// Оптимизированный вариант запроса
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса = 
"//{{ЗАПРОС(СформироватьЗаказы)
|Период с ВыбНачПериода по ВыбКонПериода;
|Обрабатывать НеПомеченныеНаУдаление;
|РасходнаяНакладная = Документ.РасходнаяНакладная.ТекущийДокумент;
|Номенклатура = Документ.РасходнаяНакладная.Номенклатура;
|Количество = Документ.РасходнаяНакладная.Количество;
|Сумма = Документ.РасходнаяНакладная.Сумма;
|Цена = Документ.РасходнаяНакладная.Цена;
|Подразделение = Документ.РасходнаяНакладная.Подразделение;
|Функция ВсегоКол = Сумма(Количество);
|Функция ВсегоСумма = Сумма(Сумма);
|Группировка РасходнаяНакладная;
|Группировка Номенклатура без групп;
|"//}}ЗАПРОС
;

Если Запрос.Выполнить(ТекстЗапроса) = 0 Тогда
    Возврат;
КонецЕсли;

Проанализируем изменения: мы оставили только две группировки. Теперь запрос получит список накладных, а внутри них — список уникальной номенклатуры. Данные о цене и подразделении можно будет получить непосредственно из выборки при обходе результатов запроса. Это значительно снижает нагрузку на временные файлы CDX.

Решение №2: Использование объектной модели (Перебор)

Если цель — просто выгрузить все строки документов за период в файл CSV без сложной фильтрации по остаткам или регистрам, то использование объекта Запрос может быть вообще нецелесообразным. В 1С 7.7 прямое сканирование документов через ВыбратьДокументы() зачастую работает быстрее и стабильнее, так как оно последовательно читает данные из файлов DH* (шапки) и DT* (табличные части), не создавая громоздких временных таблиц в памяти.

Разберем алгоритм по шагам:

  1. Создаем объект типа Документ.РасходнаяНакладная.
  2. Устанавливаем фильтр по периоду с помощью ВыбратьДокументы().
  3. В цикле получаем каждый документ и открываем выборку его строк через ВыбратьСтроки().
  4. Внутри вложенного цикла получаем данные строки и записываем их в файл.

Док = СоздатьОбъект("Документ.РасходнаяНакладная");
Док.ВыбратьДокументы(ВыбНачПериода, ВыбКонПериода);
Пока Док.ПолучитьДокумент() = 1 Цикл
    Если Док.ПометкаУдаления() = 1 Тогда
        Продолжить;
    КонецЕсли;
    
    // Получаем реквизиты шапки (быстро, так как объект уже в памяти)
    ТекКонтрагент = Док.Контрагент;
    // Обращение к телефонам лучше делать через справочник напрямую, если нужно ускорить
    ТекТелефон = ТекКонтрагент.ОсновноеКонтактноеЛицо.Телефоны;
    
    Док.ВыбратьСтроки();
    Пока Док.ПолучитьСтроку() = 1 Цикл
        // Обработка данных каждой строки:
        // Док.Номенклатура, Док.Количество, Док.Цена и т.д.
        // Запись в CSV...
    КонецЦикла;
КонецЦикла;

Этот метод исключает возникновение ошибки CDX при выполнении запроса, так как данные считываются порциями. Это «золотой стандарт» для простых выгрузок в 1С 7.7.

Решение №3: Прямые SQL-запросы (для продвинутых)

Для баз с огромным количеством документов (десятки и сотни тысяч в месяц) даже перебор может занять значительное время. В среде 1С 7.7 для решения таких задач принято использовать внешние компоненты, такие как 1C++ или 1SQLite. Они позволяют выполнять прямые SQL-запросы к базе данных (даже если она в формате DBF, через драйверы или библиотеку SQLite).

Рассмотрим пример логики прямого запроса. Мы объединяем таблицу шапок документов и таблицу строк по идентификатору IDDOC. Это выполняется на уровне движка базы данных и работает в десятки раз быстрее «черного запроса».


// Пример логики прямого запроса (с использованием SQLite)
ТекстЗапроса = "
|SELECT 
|    Шапка.iddoc [Документ :Документ.РасходнаяНакладная],
|    Шапка.spXXX as Контрагент, 
|    Строки.spYYY as Номенклатура,
|    Строки.spZZZ as Сумма
|FROM 
|    [Документ.РасходнаяНакладная] as Шапка 
|LEFT JOIN 
|    [ДокументСтроки.РасходнаяНакладная] as Строки ON Шапка.iddoc = Строки.iddoc
|WHERE 
|    Шапка.date BETWEEN :НачДата AND :КонДата
|";

Важно: Прямые запросы требуют понимания внутренней структуры таблиц 1С (имена полей вида sp123), но это единственный способ мгновенного получения данных на больших объемах.

Советы по оптимизации выгрузки в CSV

Если вы используете Scripting.FileSystemObject (FSO) для записи данных, обратите внимание на следующие моменты:

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

← На главную