Как исправить ошибку "column is of type boolean but expression is of type char" при выгрузке из 1С в PostgreSQL через ADO?

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

При организации обмена данными между 1С и СУБД PostgreSQL через механизм COM-соединения ADO (ADODB.Command и ADODB.Connection) разработчики нередко сталкиваются со сбоем при передаче логических значений. Запрос на добавление данных завершается исключением: ERROR: column "grp" is of type boolean but expression is of type "char"; Error while preparing parameters.

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

Выясним причину возникновения ошибки

Посмотрим на типовой исходный код, вызывающий исключение при выполнении метода Execute():


cmd = Новый COMОбъект("ADODB.Command");
cmd.ActiveConnection = ADOConnection;
cmd.CommandText = "INSERT INTO product (id, name, grp) VALUES (?, ?, ?);";
cmd.Prepared = True;

cmd.Parameters.Append(cmd.CreateParameter("@id", 3, 1)); // adInteger
cmd.Parameters.Append(cmd.CreateParameter("@name", 130, 1, 128)); // adVarWChar
cmd.Parameters.Append(cmd.CreateParameter("@grp", 11, 1)); // adBoolean

cmd.Parameters.Item(0).Value = 1;
cmd.Parameters.Item(1).Value = "Товар 1";
cmd.Parameters.Item(2).Value = Истина;

cmd.Execute();

Рассмотрим, почему при абсолютно корректном указании типа adBoolean (константа 11) база данных отвергает параметр:

  1. Поведение ODBC-драйвера (psqlODBC): По умолчанию в конфигурации драйвера PostgreSQL ODBC включена опция BoolsAsChar (значение 1). При активной настройке драйвер сопоставляет логические типы не с SQL_BIT или boolean, а с внутренним типом SQL_CHAR (символьный тип "char").
  2. Подготовка выражений на стороне сервера: Если свойство cmd.Prepared установлено в True, провайдер Microsoft OLE DB Provider for ODBC Drivers (MSDASQL) запрашивает типизированную подготовку запроса на сервере. Сервер PostgreSQL получает жесткое указание, что в поле передается тип char.
  3. Строгая типизация PostgreSQL: Начиная с версии PostgreSQL 8.3, неявное автоматическое приведение символьных типов к логическим отключено. Сервер видит попытку вставки символа в колонку типа boolean и генерирует ошибку несовместимости типов.

Способ 1. Изменение параметров подключения ODBC (рекомендуемый вариант)

Самый правильный и системный способ — отключить приведение булевых значений к символам на уровне самого драйвера ODBC. Разберем, как это сделать:

  1. Через строку подключения ADO: Добавим в параметры строки подключения директиву BoolsAsChar=0; (или короткий алиас B9=0;):
    
    СтрокаПодключения = "Driver={PostgreSQL Unicode};Server=127.0.0.1;Port=5432;Database=mydb;Uid=postgres;Pwd=secret;BoolsAsChar=0;";
    ADOConnection.Open(СтрокаПодключения);
    
  2. Через настройку DSN: Если подключение выполняется через системный DSN, откроем системную утилиту администрирования источников данных ODBC (для 32-разрядной платформы 1С обязательно запускаем C:\Windows\SysWOW64\odbcad32.exe). Перейдем в свойства подключения (кнопка Datasource / вкладка Page 1) и снимем флажок Bools as Char.

После отключения этого флага передаем в значение параметра стандартные булевы типы 1С без преобразования их в строки:

cmd.Parameters.Item(2).Value = (Сп.Получить(5).Значение <> "0");

Способ 2. Явное приведение типов в тексте SQL-запроса

Если изменить параметры ODBC-драйвера или строку подключения на сервере нет возможности, используем явное приведение типов (CAST) прямо в тексте запроса. Это позволит сохранить параметризацию и защитит запрос от некорректного толкования типов СУБД:


// Используем синтаксис CAST(? AS boolean) или ?::boolean
cmd.CommandText = "INSERT INTO product (id, name, grp) VALUES (?, ?, CAST(? AS boolean));";

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

Способ 3. Отключение серверной подготовки запроса

Рассмотрим вариант, при котором драйвер сам подставляет литералы в тело команды перед отправкой запроса на сервер, избегая строгой типизации серверного плана:

  1. В коде 1С устанавливаем свойство подготовки в значение Ложь: cmd.Prepared = False;
  2. Либо в строке подключения указываем отключение серверной подготовки: UseServerSidePrepare=0; (или C8=0;).

Способ 4. Переход на динамическое формирование текста SQL

Если параметризованный запрос через ADO продолжает выдавать ошибки из-за особенностей провайдера MSDASQL, можно сформировать текст запроса динамически, подставляя логические константы true или false напрямую в тело команды SQL:


Для Каждого СтрокаТЗ Из АДО_Запрос Цикл
    Сп = ИзСтрокиСРазделителями(СтрокаТЗ.Значения);
    
    Ид = Число(Сп.Получить(0).Значение);
    Наименование = СтрЗаменить(Сп.Получить(1).Значение, "'", "''");
    ПризнакГруппы = ?(Сп.Получить(5).Значение = "0", "false", "true");
    
    ТекстSQL = "INSERT INTO product (id, name, grp) VALUES (" 
        + Формат(Ид, "ЧГ=0") + ", "
        + "'" + Наименование + "', "
        + ПризнакГруппы + ");";
        
    Попытка
        ADOConnection.Execute(ТекстSQL);
    Исключение
        Сообщить("Ошибка вставки записи: " + ОписаниеОшибки());
    КонецПопытки;
КонецЦикла;

Рекомендации по ускорению пакетной выгрузки

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

  1. Явное управление транзакциями: Откроем транзакцию до начала цикла выгрузки с помощью ADOConnection.BeginTrans() и зафиксируем ее после окончания цикла вызовом ADOConnection.CommitTrans(). В случае ошибки откатим изменения методом ADOConnection.RollbackTrans().
  2. Пакетная вставка (Multi-row INSERT): Вместо отправки каждой строки по отдельности формируем один запрос INSERT INTO product (...) VALUES (...), (...), (...) порциями по 500–1000 строк.
  3. Использование команды COPY: Для выгрузки десятков и сотен тысяч строк наиболее производительным решением является выгрузка данных из 1С в промежуточный CSV-файл с последующим выполнением команды COPY product FROM 'путь_к_файлу' WITH (FORMAT csv, DELIMITER ';');.
← На главную