Почему 1С 7.7 ПУБ завершается с ошибкой в RGProc.dll при формировании отчета по покупателям на SQL и как это исправить?

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

Перевод старых баз данных 1С:Предприятие 7.7 с файлового формата (DBF) на SQL-версию часто сопровождается труднообъяснимыми падениями платформы. Одна из самых распространенных и неприятных проблем — аварийное завершение работы (APPCRASH) исполняемого файла 1cv7s.exe в модуле RGProc.dll с кодом исключения c00000fd. Эта ошибка обычно возникает при формировании тяжелых отчетов (например, "Отгрузка покупателям и комиссионерам" или "Поступление от покупателей" в комплексных конфигурациях ПУБ — "Производство + Услуги + Бухгалтерия").

В этой статье мы подробно разберем, почему файловая версия успешно "прощает" ошибки в коде, в то время как SQL-версия стабильно вылетает, проанализируем структуру проблемного запроса и рассмотрим четыре практических способа устранения этой неисправности.

---

Выясняем техническую причину падения

Код исключения c00000fd в операционной системе Windows означает STATUS_STACK_OVERFLOW (переполнение стека). Падение происходит в библиотеке RGProc.dll, которая отвечает за выборку данных, работу с регистрами и построение стандартных (так называемых "черных") запросов платформы.

Давайте разберемся, почему это происходит именно в SQL-версии:

  1. Различие движков СУБД. В файловой версии (DBF) стандартный запрос обрабатывается локальным внутренним движком 1С 7.7. Он последовательно перебирает записи таблиц и мягко обрабатывает пустые или отсутствующие значения. В SQL-версии платформа пытается транслировать метакод встроенного запроса 1С в один гигантский T-SQL-запрос (используя многочисленные конструкции UNION и LEFT OUTER JOIN), после чего отправляет его на выполнение СУБД (MS SQL Server).
  2. Проблема отсутствующих реквизитов (NULL). Если в стандартном запросе 1С 7.7 объединяются данные из нескольких видов документов (многодокументный запрос), и вы описываете переменную, которая есть только в одном документе, но отсутствует в остальных, SQL-транслятор подставляет вместо нее значение NULL для тех документов, где этого реквизита физически нет.
  3. Рекурсивный тупик транслятора. Когда на переменную, содержащую NULL, накладывается жесткое условие (например, Условие(ТипХозОпер <> 3)) или она участвует в агрегатных функциях внутри блока когда (...), парсер 1С в RGProc.dll зацикливается при попытке построить дерево SQL-запроса. Возникает бесконечная рекурсия, стек переполняется, и платформа аварийно завершает работу без вывода диагностического сообщения.

---

Анализируем проблемный код встроенного запроса

Посмотрим на типовой запрос из конфигурации ПУБ 7.7, который вызывает падение системы на SQL:


Текст = "//{{ЗАПРОС(ОтгрузкаТоваров)
|Период с ДатаНач" + ПериодПо + ";
|
|ОбрабатыватьДокументы Проведенные;
|Обрабатывать НеПомеченныеНаУдаление;
|
|ТипХозОпер   = Документ.РеализацияПродукции.ТипХозОперации;
|
|Покупатель   = Документ.РеализацияМатериалов.Контрагент, 
|               Документ.РеализацияПродукции.Контрагент, 
|               Документ.РеализацияРаботПоНормам.Контрагент, 
|               Документ.РеализацияРабот.Контрагент;
|
|Договор      = Документ.РеализацияМатериалов.Договор, 
|               Документ.РеализацияПродукции.Договор, 
|               Документ.РеализацияРаботПоНормам.Договор, 
|               Документ.РеализацияРабот.Договор;
|
|ВалютаЦен    = Документ.РеализацияМатериалов.ВалютаЦен,
|               Документ.РеализацияПродукции.ВалютаЦен, 
|               Документ.РеализацияРаботПоНормам.ВалютаЦен;
|
|Валюта       = Документ.РеализацияМатериалов.Договор.Валюта,
|               Документ.РеализацияПродукции.Договор.Валюта, 
|               Документ.РеализацияРаботПоНормам.Договор.Валюта, 
|               Документ.РеализацияРабот.Договор.Валюта;
|
|Номенклатура = Документ.РеализацияМатериалов.Товар, 
|               Документ.РеализацияПродукции.Товар, 
|               Документ.РеализацияРаботПоНормам.Продукция, 
|               Документ.РеализацияРабот.Продукция;
|
|Количество   = Документ.РеализацияМатериалов.Количество, 
|               Документ.РеализацияПродукции.Количество, 
|               Документ.РеализацияРаботПоНормам.Количество, 
|               Документ.РеализацияРабот.КоличествоПродукции;
|
|Всего        = Документ.РеализацияМатериалов.Всего, 
|               Документ.РеализацияПродукции.Всего, 
|               Документ.РеализацияРаботПоНормам.Всего, 
|               Документ.РеализацияРабот.Всего;
|
|НДС          = Документ.РеализацияМатериалов.НДС, 
|               Документ.РеализацияПродукции.НДС, 
|               Документ.РеализацияРаботПоНормам.НДС, 
|               Документ.РеализацияРабот.НДС;
|
|НП           = Документ.РеализацияМатериалов.НП, 
|               Документ.РеализацияПродукции.НП, 
|               Документ.РеализацияРаботПоНормам.НП, 
|               Документ.РеализацияРабот.НП;
|
|Коэффициент  = Документ.РеализацияМатериалов.ЕдиницаИзмерения.Коэффициент, 
|               Документ.РеализацияПродукции.ЕдиницаИзмерения.Коэффициент, 
|               Документ.РеализацияРаботПоНормам.ЕдиницаИзмерения.Коэффициент, 
|               Документ.РеализацияРабот.ЕдиницаИзмерения.Коэффициент;
|
|Курс            = Документ.РеализацияМатериалов.Курс,
|               Документ.РеализацияПродукции.Курс,
|               Документ.РеализацияРаботПоНормам.Курс;
|
|Функция Кол      = Сумма(Количество*Коэффициент) когда (Коэффициент <> 0);
|
|Функция КолБезЕИ = Сумма(Количество) когда (Коэффициент = 0);
|
|Функция Сум           = Сумма(Всего" + ?(ВключатьНДС = 0, "-НДС", "") + ?(ВключатьНП = 0, "-НП", "") + ") когда (ВалютаЦен <> 2);
|Функция СумПоКурсу    = Сумма((Всего" + ?(ВключатьНДС = 0, "-НДС", "") + ?(ВключатьНП = 0, "-НП", "") + ") * Курс) когда (ВалютаЦен = 2);
|Функция СумВал        = Сумма(Всего" + ?(ВключатьНДС = 0, "-НДС", "") + ?(ВключатьНП = 0, "-НП", "") + ") когда (ВалютаЦен = 2);
|Функция СумВалПоКурсу = Сумма((Всего" + ?(ВключатьНДС = 0, "-НДС", "") + ?(ВключатьНП = 0, "-НП", "") + ") / Курс) когда ((ВалютаЦен <> 2) и (Курс <> 0));
|
|Условие (ТипХозОпер <> 3);
|";

Проанализируем слабые места этого кода:

---

Варианты решения проблемы

Рассмотрим четыре способа исправления данной ошибки — от самого быстрого (временного) до наиболее правильного и производительного.

Способ 1. Быстрый костыль через отключение трансляции в SQL

Самое простое решение, которое моментально устраняет падение платформы — принудительное отключение генерации SQL-запроса. Для этого перед выполнением запроса в коде отчета необходимо вызвать специальный метод ВключитьSQL() с параметром 0.

Разберем, как применить это на практике. Найдем в модуле отчета место, где инициализируется и выполняется запрос, и добавим управляющую строку:


Запрос = СоздатьОбъект("Запрос");
// Отключаем трансляцию запроса в SQL-формат
Запрос.ВключитьSQL(0); 

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

Как это работает: Платформа 1С прекратит попытки построить сложный T-SQL запрос. Вместо этого она скачает необходимые таблицы документов во временные dbf-файлы на локальный компьютер (или на сервер терминалов, где запущена сессия пользователя) и обработает запрос локально, как в старой доброй файловой версии.

Важное предупреждение: Данный способ сильно снижает скорость построения отчетов. Если объем документов за выбранный период велик (десятки или сотни тысяч документов), формирование отчета может затянуться на часы, так как сеть будет перегружена пересылкой сырых таблиц базы данных. Используйте этот метод только как временную меру!

Способ 2. Вынос фильтрации из тела запроса (Рекомендуемый способ)

Чтобы сохранить высокую скорость выполнения запроса на SQL-сервере, нам нужно убрать из тела запроса условия, которые вызывают сбой транслятора. Разберем этот процесс по шагам.

Шаг 1. Полностью удалим проблемное условие из текста запроса:


// Удаляем эту строчку из переменной Текст:
// |Условие (ТипХозОпер <> 3);

Шаг 2. Перенесем фильтрацию данных непосредственно в цикл обхода результатов запроса. Найдем в коде отчета процедуру вывода строк и добавим ручную проверку типа хозяйственной операции:


Пока Запрос.Группировка(1) = 1 Цикл
    // Получаем текущий документ из выборки
    Док = Запрос.ТекущийДокумент;
    
    // Если это Реализация Продукции, проверяем тип операции вручную
    Если Док.Вид() = "РеализацияПродукции" Тогда
        Если Док.ТипХозОперации = 3 Тогда
            // Пропускаем документы с типом операции 3
            Продолжить; 
        КонецЕсли;
    КонецЕсли;
    
    // Далее идет стандартный код вывода строки в таблицу...
КонецЦикла;

Этот подход позволяет запросу успешно выполниться на стороне SQL-сервера на максимальной скорости, а "проблемная" фильтрация безопасно отработает на клиенте стандартными средствами встроенного языка.

Способ 3. Разделение запроса на несколько простых

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

Рассмотрим схему реализации этого алгоритма:

  1. Создаем временную таблицу значений (ТЗ) с колонками: Покупатель, Договор, Номенклатура, Количество, Сумма.
  2. Пишем первый простой запрос только по документу РеализацияМатериалов, выгружаем его результаты в созданную ТЗ.
  3. Пишем второй запрос по РеализацияПродукции, накладывая условие ТипХозОпер <> 3 прямо в нем. Выгружаем результаты и добавляем их в общую ТЗ.
  4. Повторяем операцию для оставшихся видов документов (РеализацияРаботПоНормам и РеализацияРабот).
  5. Сворачиваем полученную Таблицу Значений по группировочным полям методом ТЗ.Свернуть("Покупатель, Договор, Номенклатура", "Количество, Сумма").
  6. Выводим данные из свернутой таблицы в печатную форму отчета.

Этот метод гарантирует 100% стабильность работы на SQL СУБД любого поколения (от MS SQL 2000 до современных версий), так как простые однодокументные запросы транслируются платформой идеально.

Способ 4. Использование прямых SQL-запросов (Для профи)

Если ваша база данных имеет внушительный объем, а отчеты должны формироваться мгновенно, стандартный механизм запросов 1С 7.7 лучше полностью заменить на механизм "Прямых запросов" с использованием внешней компоненты 1С++ (1cpp.dll).

Прямой запрос пишется на чистом языке T-SQL и передается SQL-серверу в обход стандартного транслятора платформы. В коде это выглядит примерно так:


// Пример инициализации прямого запроса через 1С++
БазаДанных = СоздатьОбъект("ODBCDatabase");
ЗапросSQL = СоздатьОбъект("ODBCRecordset");

ТекстЗапроса = "
|SELECT 
|    $Реал.Контрагент AS [Покупатель $Справочник.Контрагенты],
|    SUM($РеалСтроки.Всего) AS Сумма
|FROM 
|    $ДокументСтроки.РеализацияПродукции AS РеалСтроки
|INNER JOIN 
|    $Документ.РеализацияПродукции AS Реал ON Реал.IDDoc = РеалСтроки.IDDoc
|WHERE 
|    $Реал.ТипХозОперации <> 3
|GROUP BY 
|    $Реал.Контрагент
|";

ТЗ = ЗапросSQL.ВыполнитьИнструкцию(ТекстЗапроса);

Преимущества: Прямые запросы работают в десятки и сотни раз быстрее стандартных за счет отсутствия лишних уровней абстракции 1С и явного контроля типов данных разработчиком (включая корректную обработку NULL-значений с помощью T-SQL функции ISNULL()).

---

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

Аварийное завершение работы 1С 7.7 при формировании отчетов на SQL — это не приговор базе данных, а следствие несовершенства старого транслятора запросов платформы. Наиболее эффективным, быстрым и безопасным решением для большинства системных администраторов и программистов станет Способ №2 (вынос условий из текста запроса в цикл обхода результатов). Он сохраняет скорость работы базы данных и навсегда избавляет пользователей от вылетов программы.

← На главную