Перевод старых баз данных 1С:Предприятие 7.7 с файлового формата (DBF) на SQL-версию часто сопровождается труднообъяснимыми падениями платформы. Одна из самых распространенных и неприятных проблем — аварийное завершение работы (APPCRASH) исполняемого файла 1cv7s.exe в модуле RGProc.dll с кодом исключения c00000fd. Эта ошибка обычно возникает при формировании тяжелых отчетов (например, "Отгрузка покупателям и комиссионерам" или "Поступление от покупателей" в комплексных конфигурациях ПУБ — "Производство + Услуги + Бухгалтерия").
В этой статье мы подробно разберем, почему файловая версия успешно "прощает" ошибки в коде, в то время как SQL-версия стабильно вылетает, проанализируем структуру проблемного запроса и рассмотрим четыре практических способа устранения этой неисправности.
---
Код исключения c00000fd в операционной системе Windows означает STATUS_STACK_OVERFLOW (переполнение стека). Падение происходит в библиотеке RGProc.dll, которая отвечает за выборку данных, работу с регистрами и построение стандартных (так называемых "черных") запросов платформы.
Давайте разберемся, почему это происходит именно в SQL-версии:
UNION и LEFT OUTER JOIN), после чего отправляет его на выполнение СУБД (MS SQL Server).NULL для тех документов, где этого реквизита физически нет.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);
|";
Проанализируем слабые места этого кода:
ТипХозОпер определена исключительно для одного документа РеализацияПродукции. Для остальных трех документов в выборке (РеализацияМатериалов, РеализацияРаботПоНормам, РеализацияРабот) этого поля не существует.Условие (ТипХозОпер <> 3); заставляет СУБД сравнивать несуществующие значения (NULL) с тройкой. Именно на этой строке парсер SQL валит платформу в 100% случаев.ВалютаЦен и Курс отсутствуют в документе РеализацияРабот. Функции СумВалПоКурсу и СумПоКурсу пытаются использовать эти переменные в условиях ветвления когда (...), что также приводит к непредсказуемому поведению SQL-движка 1С 7.7.---
Рассмотрим четыре способа исправления данной ошибки — от самого быстрого (временного) до наиболее правильного и производительного.
Самое простое решение, которое моментально устраняет падение платформы — принудительное отключение генерации SQL-запроса. Для этого перед выполнением запроса в коде отчета необходимо вызвать специальный метод ВключитьSQL() с параметром 0.
Разберем, как применить это на практике. Найдем в модуле отчета место, где инициализируется и выполняется запрос, и добавим управляющую строку:
Запрос = СоздатьОбъект("Запрос");
// Отключаем трансляцию запроса в SQL-формат
Запрос.ВключитьSQL(0);
Если Запрос.Выполнить(Текст) = 0 Тогда
Возврат;
КонецЕсли;
Как это работает: Платформа 1С прекратит попытки построить сложный T-SQL запрос. Вместо этого она скачает необходимые таблицы документов во временные dbf-файлы на локальный компьютер (или на сервер терминалов, где запущена сессия пользователя) и обработает запрос локально, как в старой доброй файловой версии.
Важное предупреждение: Данный способ сильно снижает скорость построения отчетов. Если объем документов за выбранный период велик (десятки или сотни тысяч документов), формирование отчета может затянуться на часы, так как сеть будет перегружена пересылкой сырых таблиц базы данных. Используйте этот метод только как временную меру!
Чтобы сохранить высокую скорость выполнения запроса на SQL-сервере, нам нужно убрать из тела запроса условия, которые вызывают сбой транслятора. Разберем этот процесс по шагам.
Шаг 1. Полностью удалим проблемное условие из текста запроса:
// Удаляем эту строчку из переменной Текст:
// |Условие (ТипХозОпер <> 3);
Шаг 2. Перенесем фильтрацию данных непосредственно в цикл обхода результатов запроса. Найдем в коде отчета процедуру вывода строк и добавим ручную проверку типа хозяйственной операции:
Пока Запрос.Группировка(1) = 1 Цикл
// Получаем текущий документ из выборки
Док = Запрос.ТекущийДокумент;
// Если это Реализация Продукции, проверяем тип операции вручную
Если Док.Вид() = "РеализацияПродукции" Тогда
Если Док.ТипХозОперации = 3 Тогда
// Пропускаем документы с типом операции 3
Продолжить;
КонецЕсли;
КонецЕсли;
// Далее идет стандартный код вывода строки в таблицу...
КонецЦикла;
Этот подход позволяет запросу успешно выполниться на стороне SQL-сервера на максимальной скорости, а "проблемная" фильтрация безопасно отработает на клиенте стандартными средствами встроенного языка.
Если отчет по-прежнему работает нестабильно или медленно из-за NULL-значений в функциях расчета валютных сумм, оптимальным решением станет разделение одного гигантского запроса на четыре независимых простых запроса.
Рассмотрим схему реализации этого алгоритма:
Покупатель, Договор, Номенклатура, Количество, Сумма.РеализацияМатериалов, выгружаем его результаты в созданную ТЗ.РеализацияПродукции, накладывая условие ТипХозОпер <> 3 прямо в нем. Выгружаем результаты и добавляем их в общую ТЗ.РеализацияРаботПоНормам и РеализацияРабот).ТЗ.Свернуть("Покупатель, Договор, Номенклатура", "Количество, Сумма").Этот метод гарантирует 100% стабильность работы на SQL СУБД любого поколения (от MS SQL 2000 до современных версий), так как простые однодокументные запросы транслируются платформой идеально.
Если ваша база данных имеет внушительный объем, а отчеты должны формироваться мгновенно, стандартный механизм запросов 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 (вынос условий из текста запроса в цикл обхода результатов). Он сохраняет скорость работы базы данных и навсегда избавляет пользователей от вылетов программы.