Почему не находятся документы по сумме через COM-соединение и как это исправить?

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

При разработке механизмов интеграции между двумя базами 1С через COM-соединение программисты часто сталкиваются с загадочным поведением: запросы, которые отлично работают внутри одной базы, начинают "терять" документы при выполнении через внешнее соединение. Особенно ярко это проявляется при попытке найти документ по точному соответствию суммы, если в ней есть копейки (дробная часть). Мы можем видеть документ в базе-источнике, знать его номер и точную сумму, но запрос по этому значению через COMConnector возвращает пустой результат.

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

Причина проблемы: Особенности маршалинга дробных чисел

Прежде чем переходить к коду, давайте выясним причину. Когда мы создаем V83.COMConnector и передаем параметры в запрос, данные проходят через интерфейс IDispatch. Это стандартный механизм Windows для взаимодействия между приложениями. В этот момент происходит процесс, называемый маршалингом (преобразованием типов).

Тип Число платформы 1С, который внутри базы хранится с фиксированной точностью, при передаче через COM часто преобразуется в тип Double (число с плавающей точкой двойной точности). Проблема заключается в том, что стандарт IEEE 754, по которому работают числа с плавающей точкой, не может абсолютно точно представить многие десятичные дроби в двоичном виде. Например, число 9.05 в памяти компьютера может превратиться в 9.049999999999997.

Когда этот параметр попадает в текст запроса на стороне COM-сервера, условие ГДЕ Сумма = &Сумма сравнивает точное значение из базы (ровно 9.05) с "приблизительным" значением из COM. Естественно, система сообщает, что данные не найдены, так как равенство не соблюдается даже при микроскопическом расхождении.

Способ №1: Использование целочисленного множителя

Проанализируем самый популярный и простой "хак", который часто применяется разработчиками. Чтобы избежать проблем с плавающей точкой, мы можем временно перевести дробное число в целое на время передачи, а внутри запроса произвести обратное преобразование. Рассмотрим пример реализации:


// В вызывающей базе
ИскомаяСумма = 9.05;
Множитель = 100; // Для двух знаков после запятой
СуммаДляПередачи = Цел(ИскомаяСумма * Множитель); 

ЗапросCOM = COMБаза.NewObject("Запрос");
ЗапросCOM.Текст = 
    "ВЫБРАТЬ
    |	Ссылка
    |ИЗ
    |	Документ.РеализацияТоваровУслуг
    |ГДЕ
    |	СуммаДокумента * &Множитель = &СуммаЦелая";

ЗапросCOM.УстановитьПараметр("Множитель", Множитель);
ЗапросCOM.УстановитьПараметр("СуммаЦелая", СуммаДляПередачи);

В этом случае мы сравниваем два целых числа, что гарантирует 100% точность попадания. Однако стоит помнить, что умножение поля в условии ГДЕ может привести к тому, что СУБД не сможет использовать индекс по полю СуммаДокумента, что замедлит выполнение запроса на больших таблицах.

Способ №2: Поиск в диапазоне (Эпсилон-сравнение)

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


// Определяем малую погрешность (эпсилон)
Погрешность = 0.0001;
СуммаМинус = ИскомаяСумма - Погрешность;
СуммаПлюс  = ИскомаяСумма + Погрешность;

ЗапросCOM = COMБаза.NewObject("Запрос");
ЗапросCOM.Текст = 
    "ВЫБРАТЬ
    |	Ссылка
    |ИЗ
    |	Документ.РеализацияТоваровУслуг
    |ГДЕ
    |	СуммаДокумента МЕЖДУ &СуммаМинус И &СуммаПлюс";

ЗапросCOM.УстановитьПараметр("СуммаМинус", СуммаМинус);
ЗапросCOM.УстановитьПараметр("СуммаПлюс", СуммаПлюс);

Этот метод хорош тем, что он корректно отрабатывает все особенности типа Double и при этом позволяет SQL-серверу эффективно использовать индексы, так как само поле СуммаДокумента не подвергается математическим преобразованиям в условии запроса.

Способ №3: Явное приведение типов в запросе

Если мы хотим остаться в рамках классического сравнения, можно попробовать заставить платформу на стороне COM-сервера привести переданный параметр к нужному типу Число с фиксированной разрядностью. Для этого используем конструкцию ВЫРАЗИТЬ.

Рассмотрим по шагам, что происходит в этом случае:

  1. Параметр &Сумма приходит как "неточное" число.
  2. Функция ВЫРАЗИТЬ округляет его до указанной точности (например, до 2 знаков).
  3. Сравнение происходит уже между двумя нормализованными величинами.

ЗапросCOM.Текст = 
    "ВЫБРАТЬ
    |	Ссылка
    |ИЗ
    |	Документ.РеализацияТоваровУслуг
    |ГДЕ
    |	СуммаДокумента = ВЫРАЗИТЬ(&Сумма КАК ЧИСЛО(15, 2))";

Важный момент: Иногда платформа 1С может "сопротивляться" такому выражению в параметре, поэтому в некоторых версиях надежнее работает предварительное округление на стороне вызывающего кода функцией Округлить(), хотя, как показывает практика темы, это помогает не всегда.

Способ №4: Отказ от фильтрации по сумме в COM-запросе

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

Посмотрим на пример логики:

  1. Выполняем запрос через COM только по дате и контрагенту (эти типы передаются без искажений).
  2. Выгружаем результат в ТаблицуЗначений.
  3. Используем метод НайтиСтроки() или еще один локальный запрос к этой таблице для поиска точной суммы.

// Получаем данные без фильтра по сумме
ТЗ_Результат = ЗапросCOM.Выполнить().Выгрузить();

// Теперь ТЗ_Результат находится в нашей базе, типы нормализованы
ПараметрыОтбора = Новый Структура("СуммаДокумента", 9.05);
НайденныеСтроки = ТЗ_Результат.НайтиСтроки(ПараметрыОтбора);

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

Резюме

Проблема с поиском дробных чисел через COM — это не баг 1С, а фундаментальная особенность компьютерного представления чисел. Для стабильной работы обменов рекомендуем использовать метод диапазона (МЕЖДУ) или целочисленное умножение. Это избавит вас от ситуаций, когда часть документов мистическим образом "пропадает" при переносе данных.

← На главную