Почему в серверной версии 1С арифметика в запросах может работать некорректно?

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

Переход с файловой версии базы данных на серверную (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С исполняет запросы в разных режимах:

  1. Файловый режим: Используется собственный движок базы данных 1С. Он напрямую обрабатывает логику ВЫБОР и арифметические операторы. Вероятность расхождений здесь минимальна, так как алгоритмы жестко вшиты в платформу.
  2. Серверный режим (SQL): Сервер 1С сам не производит вычисления. Он выступает в роли транслятора: переводит ваш запрос с языка «1С:Предприятие» на язык T-SQL, понятный MS SQL Server. Ошибка возникает именно на этапе трансляции (генерации SQL-кода). Платформа формирует некорректную конструкцию для SQL-сервера, что приводит к неверному математическому итогу.

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

Разберем способы решения проблемы

Существует несколько путей решения, от радикальных до обходных (workarounds).

1. Обновление платформы (Рекомендуемое решение)

Как показывает практика и сообщения пользователей, проблема была признана разработчиками 1С и исправлена в последующих сервисных выпусках. Переход на версию платформы 8.3.15.1869 или выше полностью устраняет ошибку трансляции арифметики. Это наиболее правильный путь, так как он лечит причину, а не следствие.

2. Использование функции ЕСТЬNULL и явное приведение типов

Если оперативное обновление платформы невозможно, проанализируем, как можно «помочь» транслятору не ошибиться. Часто проблема кроется в обработке 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 пытается самостоятельно угадать тип данных временной таблицы, что иногда приводит к потере точности или ошибкам в расчетах.

3. Определение квалификаторов в Таблице Значений

Посмотрим на описание колонок ТаблицаЗначений в исходном коде. Автор использовал конструкцию Новый ОписаниеТипов("Число"). В серверном режиме это «плохой тон», так как не задана разрядность. При создании временной таблицы на сервере 1С может создать колонку с типом float или numeric(32,0) без дробной части.

Правильный подход:


КвалификаторыЧисла = Новый КвалификаторыЧисла(15, 2);
ОписаниеЧисла = Новый ОписаниеТипов("Число", КвалификаторыЧисла);
ТЗ.Колонки.Добавить("ДолгНачалоМесяца", ОписаниеЧисла);

Явное указание квалификаторов гарантирует, что временная таблица в tempdb на стороне SQL будет создана с корректными параметрами, и арифметика станет предсказуемой.

4. Разбиение сложных запросов

Если ваш запрос содержит длинную цепочку вычислений с множеством конструкций ВЫБОР КОГДА..., попробуйте разбить его на несколько этапов. Вместо одного огромного запроса используйте промежуточные временные таблицы. Это заставляет платформу вычислять промежуточные итоги и записывать их в физические таблицы на диске/в памяти SQL, что снижает вероятность ошибки оптимизатора при формировании одного сверхсложного T-SQL выражения.

Резюме

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

Своевременное обновление платформы и аккуратная работа с типами данных — залог стабильной работы системы в любом режиме, будь то файловая база или высоконагруженный SQL-кластер.

← На главную