Переход с файловой версии базы данных на серверную (MS SQL) — это важный этап масштабирования системы, однако иногда он преподносит неприятные сюрпризы. Одной из наиболее пугающих проблем является внезапное изменение результатов арифметических вычислений в отчетах. Представьте ситуацию: один и тот же код в файловой базе выдает «0», а на сервере — «14 000». В данной статье мы разберем, почему это происходит, проанализируем конкретный пример программного кода и выясним, как устранить подобные ошибки транслятора запросов.
Рассмотрим ситуацию, с которой столкнулись разработчики при работе в конфигурации 1С:Комплексная автоматизация 1.1 на платформе 8.3.15.1489. Суть проблемы заключается в том, что при выполнении арифметических операций в секции ВЫБРАТЬ, использующей данные из временных таблиц (созданных на основе внешних источников), сервер 1С выдает неверный результат.
Для воспроизведения ошибки воспользуемся следующим алгоритмом: создадим ТаблицаЗначений, наполним ее тестовыми данными и передадим в запрос как параметр. Проанализируем пример кода:
// Подготовка тестовых данных
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Организация", Новый ОписаниеТипов("Строка"));
ТЗ.Колонки.Добавить("Контрагент", Новый ОписаниеТипов("Строка"));
ТЗ.Колонки.Добавить("ДолгНачалоМесяца", Новый ОписаниеТипов("Число"));
ТЗ.Колонки.Добавить("ДолгКонецМесяца", Новый ОписаниеТипов("Число"));
// ... добавление остальных колонок
НовСтр = ТЗ.Добавить();
НовСтр.Контрагент = "ТЕСТ";
НовСтр.ДолгНачалоМесяца = 7000;
НовСтр.ДолгКонецМесяца = 7000;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст =
"ВЫБРАТЬ * ПОМЕСТИТЬ _ВТ1_4_врем ИЗ &_ВТ1_4_врем КАК ТЗ;
|
|ВЫБРАТЬ
| _ВТ1_4_врем.ДолгНачалоМесяца - _ВТ1_4_врем.ДолгКонецМесяца КАК Результат
|ИЗ
| _ВТ1_4_врем КАК _ВТ1_4_врем";
Запрос.УстановитьПараметр("_ВТ1_4_врем", ТЗ);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Сообщить("Результат (7000 - 7000): " + Выборка.Результат);
КонецЕсли;
В файловом варианте результат будет 0, что логично. Однако в определенных релизах платформы 8.3.15 на SQL-версии результат может составить 14 000. Вместо вычитания сервер фактически выполнил сложение или иную некорректную операцию на уровне СУБД.
Для понимания природы ошибки важно осознать, как 1С исполняет запросы в разных режимах:
ВЫБОР и арифметические операторы. Вероятность расхождений здесь минимальна, так как алгоритмы жестко вшиты в платформу.В версиях ветки 8.3.15 (включая сборку 1489) был зарегистрирован баг, связанный с оптимизатором запросов. При работе с временными таблицами, типизация которых не была жестко задана квалификаторами, транслятор мог ошибочно группировать поля или неверно расставлять приоритеты операций в итоговом SQL-запросе.
Существует несколько путей решения, от радикальных до обходных (workarounds).
Как показывает практика и сообщения пользователей, проблема была признана разработчиками 1С и исправлена в последующих сервисных выпусках. Переход на версию платформы 8.3.15.1869 или выше полностью устраняет ошибку трансляции арифметики. Это наиболее правильный путь, так как он лечит причину, а не следствие.
Если оперативное обновление платформы невозможно, проанализируем, как можно «помочь» транслятору не ошибиться. Часто проблема кроется в обработке NULL и неопределенных типов в tempdb. Рассмотрим улучшенный вариант запроса:
ВЫБРАТЬ
ВЫРАЗИТЬ(ЕСТЬNULL(_ВТ1_4_врем.ДолгНачалоМесяца, 0) КАК ЧИСЛО(15, 2))
- ВЫРАЗИТЬ(ЕСТЬNULL(_ВТ1_4_врем.ДолгКонецМесяца, 0) КАК ЧИСЛО(15, 2)) КАК Результат
ИЗ
_ВТ1_4_врем КАК _ВТ1_4_врем
Использование функции ВЫРАЗИТЬ заставляет сервер 1С принудительно указать тип данных в SQL-запросе (например, numeric(15,2)). Это предотвращает ситуацию, когда SQL Server пытается самостоятельно угадать тип данных временной таблицы, что иногда приводит к потере точности или ошибкам в расчетах.
Посмотрим на описание колонок ТаблицаЗначений в исходном коде. Автор использовал конструкцию Новый ОписаниеТипов("Число"). В серверном режиме это «плохой тон», так как не задана разрядность. При создании временной таблицы на сервере 1С может создать колонку с типом float или numeric(32,0) без дробной части.
Правильный подход:
КвалификаторыЧисла = Новый КвалификаторыЧисла(15, 2);
ОписаниеЧисла = Новый ОписаниеТипов("Число", КвалификаторыЧисла);
ТЗ.Колонки.Добавить("ДолгНачалоМесяца", ОписаниеЧисла);
Явное указание квалификаторов гарантирует, что временная таблица в tempdb на стороне SQL будет создана с корректными параметрами, и арифметика станет предсказуемой.
Если ваш запрос содержит длинную цепочку вычислений с множеством конструкций ВЫБОР КОГДА..., попробуйте разбить его на несколько этапов. Вместо одного огромного запроса используйте промежуточные временные таблицы. Это заставляет платформу вычислять промежуточные итоги и записывать их в физические таблицы на диске/в памяти SQL, что снижает вероятность ошибки оптимизатора при формировании одного сверхсложного T-SQL выражения.
Мы проанализировали ситуацию, в которой арифметика серверной 1С ведет себя нелогично. Основной причиной является ошибка транслятора запросов в конкретных релизах платформы. Для предотвращения подобных ситуаций придерживайтесь следующих правил:
ТаблицаЗначений, которые планируете передавать в запрос.ЕСТЬNULL для всех полей, участвующих в математических операциях.ВЫРАЗИТЬ(... КАК ЧИСЛО) в самом тексте запроса для критически важных вычислений.Своевременное обновление платформы и аккуратная работа с типами данных — залог стабильной работы системы в любом режиме, будь то файловая база или высоконагруженный SQL-кластер.