При интеграции с внешними источниками данных и чтении объемных справочников (например, классификаторов ФИАС или КЛАДР) разработчики нередко используют прямое подключение к файлам *.dbf через интерфейс ADODB.Connection и ADODB.Command. Однако при выполнении запроса методом Execute() можно столкнуться с ошибкой драйвера ODBC: «Too few parameters. Expected N», даже если все параметры были явно объявлены и добавлены в коллекцию Parameters.
Разберем подробно, почему возникает эта ошибка, как устроен механизм разбора параметров в драйверах dBase и какие существуют способы решения проблемы.
Рассмотрим ситуацию: в коде 1С формируется объект ADODB.Command с именованными параметрами (например, @p1, @p2), после чего вызывается метод Execute(). Драйвер возвращает ошибку о нехватке параметров, а количество параметров в коллекции Command.Parameters.Count оказывается больше, чем было добавлено вручную. Выясним основные причины такого поведения:
AOGUID указать AOGOUID, драйвер решит, что это имя параметра, и автоматически добавит его в список ожидаемых.Microsoft dBase Driver (*.dbf) не поддерживает именованную параметризацию (синтаксис вида @ParamName). Он ожидает позиционные маркеры в виде знака вопроса (?). При передаче @p1 парсер ODBC может расценить это имя как отдельную колонку или выражение, требующее значения.Для корректной работы с драйвером ODBC dBase необходимо отказаться от имен параметров с префиксом @ в тексте SQL-запроса и использовать стандартные маркеры параметров — знаки вопроса ?. Параметры добавляются в коллекцию строго в порядке их следования в запросе.
Посмотрим на пример правильного формирования запроса:
&НаСервере
Процедура ВыполнитьЗапросПозиционныеПараметры()
СтрокаПодключения = "DRIVER={Microsoft dBase Driver (*.dbf)};DefaultDir=C:\ADDROB;";
АДО = Новый COMОбъект("ADODB.Connection");
Попытка
АДО.Open(СтрокаПодключения);
Исключение
ВызватьИсключение "Не удалось подключиться к базе данных DBF: " + ОписаниеОшибки();
КонецПопытки;
Command = Новый COMОбъект("ADODB.Command");
Command.ActiveConnection = АДО;
Command.NamedParameters = Ложь;
// В тексте запроса вместо @p1, @p2 используем знаки вопроса '?'
// Также проверяем правильность имен колонок (AOGUID вместо AOGOUID)
Command.CommandText =
"SELECT DISTINCT
| t1.FORMALNAME AS nameTown,
| t2.FORMALNAME AS nameParent,
| t3.FORMALNAME AS nameStreet
|FROM
| ADDROB77 t1, ADDROB77 t2, ADDROB77 t3
|WHERE
| t1.FORMALNAME LIKE ?
| AND t3.FORMALNAME LIKE ?
| AND t1.ENDDATE > ?
| AND t2.ENDDATE > ?
| AND t3.ENDDATE > ?
| AND t1.PARENTGUID = t2.AOGUID
| AND t1.AOGUID = t3.PARENTGUID";
мТекущаяДата = Формат(ТекущаяДатаСеанса(), "ДФ=гггг-ММ-дд");
// Добавляем параметры строго по порядку их появления в тексте SQL
// Тип 129 - adChar/adVarChar, 133 - adDBDate
Command.Parameters.Append(Command.CreateParameter("p1", 129, 1, 100, "%" + СокрЛП(НаименованиеНасПункта) + "%"));
Command.Parameters.Append(Command.CreateParameter("p2", 129, 1, 100, "%" + СокрЛП(НаименованиеУлицы) + "%"));
Command.Parameters.Append(Command.CreateParameter("p3", 133, 1, , мТекущаяДата));
Command.Parameters.Append(Command.CreateParameter("p4", 133, 1, , мТекущаяДата));
Command.Parameters.Append(Command.CreateParameter("p5", 133, 1, , мТекущаяДата));
RecordSet = Command.Execute();
Пока RecordSet.EOF = 0 Цикл
Сообщить(Строка(RecordSet.Fields("nameParent").Value) + " " +
Строка(RecordSet.Fields("nameTown").Value) + " " +
Строка(RecordSet.Fields("nameStreet").Value));
RecordSet.MoveNext();
КонецЦикла;
RecordSet.Close();
АДО.Close();
КонецПроцедуры
Если параметризованный вызов через ADODB.Command продолжает вызывать конфликты типов или сложности с драйвером, можно отказаться от объекта команды и выполнить прямой запрос через АДО.Execute() с подстановкой значений непосредственно в строку SQL.
Рассмотрим, как реализовать этот подход:
&НаСервере
Процедура ВыполнитьЗапросКонкатенацией()
СтрокаПодключения = "DRIVER={Microsoft dBase Driver (*.dbf)};DefaultDir=C:\ADDROB;";
АДО = Новый COMОбъект("ADODB.Connection");
АДО.Open(СтрокаПодключения);
// Экранируем одинарные кавычки в строковых переменных для защиты от синтаксических ошибок
СтрНасПункт = СтрЗаменить(СокрЛП(НаименованиеНасПункта), "'", "''");
СтрУлица = СтрЗаменить(СокрЛП(НаименованиеУлицы), "'", "''");
СтрДата = Формат(ТекущаяДатаСеанса(), "ДФ=гггг-ММ-дд");
ТекстЗапроса =
"SELECT DISTINCT
| t1.FORMALNAME AS nameTown,
| t2.FORMALNAME AS nameParent,
| t3.FORMALNAME AS nameStreet
|FROM
| ADDROB77 t1, ADDROB77 t2, ADDROB77 t3
|WHERE
| t1.FORMALNAME LIKE '%" + СтрНасПункт + "%'
| AND t3.FORMALNAME LIKE '%" + СтрУлица + "%'
| AND t1.ENDDATE > {d '" + СтрДата + "'}
| AND t2.ENDDATE > {d '" + СтрДата + "'}
| AND t3.ENDDATE > {d '" + СтрДата + "'}
| AND t1.PARENTGUID = t2.AOGUID
| AND t1.AOGUID = t3.PARENTGUID";
RecordSet = АДО.Execute(ТекстЗапроса);
Пока RecordSet.EOF = 0 Цикл
// Обработка полученных записей
RecordSet.MoveNext();
КонецЦикла;
RecordSet.Close();
АДО.Close();
КонецПроцедуры
При организации работы с файлами DBF большого объема через COM-объекты следует учитывать важные архитектурные особенности:
Microsoft dBase Driver (*.dbf) является 32-разрядным. При выполнении кода на 64-битном сервере 1С вызов Новый COMОбъект("ADODB.Connection") приведет к ошибке отсутствия провайдера. Для 64-битных систем требуется использование провайдера Microsoft.ACE.OLEDB.12.0 (или 16.0) с параметром Extended Properties="dBASE IV;".XBase: в платформе 1С реализован встроенный объект XBase, работающий нативно на любой операционной системе и разрядности без использования внешних драйверов. При наличии индексных файлов поиск по ключу через XBase выполняется значительно быстрее, чем тяжелые SQL-запросы к неуправляемым файловым таблицам.VFPOLEDB.1 (Visual FoxPro OLE DB) обеспечивает более высокую скорость выполнения сложных выборок и корректную работу с индексами CDX.