При работе с внешними базами данных, такими как MySQL, из среды 1С Предприятия часто используется технология ADODB (ActiveX Data Objects). Она позволяет устанавливать соединение, выполнять SQL-запросы и получать результаты. Однако иногда программисты сталкиваются с ситуациями, когда ожидаемое количество записей из запроса не совпадает с фактически полученным результатом. Рассмотрим подробнее одну из таких проблем, когда вместо множества записей возвращается только одна, и выясним, как ее решить.
Представим ситуацию: вы выполняете SQL-запрос к базе данных MySQL, который, по вашему убеждению, должен вернуть несколько строк, но в результате работы кода в 1С вы видите только одну запись. Такая проблема может сбить с толку, поскольку синтаксис ADODB для работы с Recordset кажется стандартным и проверенным. Как показывает практика, основная причина, как правило, кроется не в механизме выборки данных ADODB, а в самом SQL-запросе или в условиях его формирования.
Давайте посмотрим на пример кода, с которым столкнулся автор темы на форуме, чтобы понять контекст. Изначально была настроена строка соединения с MySQL-сервером:
СтрокаСоединенияСервером = "DRIVER={MySQL ODBC 8.0 Unicode Driver};"+
"SERVER=192.168.123.10;"+
"DATABASE=pacs;"+
"UID=user1c;"+
"PWD=Daniel1234;"+
"MULTI_STATEMENTS=1; "+
"STMT=SET CHARACTER SET utf8";
Эта строка соединения выглядит корректно. Мы используем MySQL ODBC 8.0 Unicode Driver, указаны сервер, база данных, логин, пароль. Параметры MULTI_STATEMENTS=1 (позволяет выполнять несколько SQL-операторов, разделенных точкой с запятой, хотя в данном случае используется один SELECT) и STMT=SET CHARACTER SET utf8 (устанавливает кодировку UTF-8 для корректной работы с символами) важны для корректной работы с данными. Далее, в коде создаются объекты ADODB.Connection и ADODB.Recordset:
Соединение = Новый COMОбъект("ADODB.Connection");
НаборЗаписей=Новый COMОбъект("ADODB.Recordset");
Если Соединение.state=0 тогда
Соединение.open(СтрокаСоединенияСервером);
Сообщить("Подключились");
КонецЕсли;
Здесь мы видим стандартный подход: создание объекта соединения, проверка его состояния и открытие. Сообщение "Подключились" подтверждает успешность этапа соединения.
Проблема получения одной записи вместо множества, как выяснилось в ходе обсуждения на форуме, возникла на этапе выполнения SQL-запроса и обхода его результатов. Изначально код автора выглядел примерно так:
ЗапросSQL = "SELECT * FROM pacs.dicom_series where ID like '"+"53016"+"'";
// ... далее попытки обработки результата
НаборЗаписей.ActiveConnection=Соединение;
НаборЗаписей.Open(ЗапросSQL); // Первый вызов
// ...
Table=Соединение.execute(ЗапросSQL); // Второй вызов
Пока Table.EOF() = 0 do
Value1= Table.Fields(0).Value;
Сообщить(Value1);
Table.MoveNext();
КонецЦикла;
Table.Close();
В этом блоке кода мы замечаем избыточное выполнение запроса. Сначала используется НаборЗаписей.Open(ЗапросSQL), а затем Соединение.execute(ЗапросSQL). Метод Соединение.execute(), если запрос является SELECT, возвращает новый объект ADODB.Recordset, который мы сохраняем в переменную Table. Именно с этим объектом Table и происходит дальнейшая итерация. Если бы использовался только НаборЗаписей.Open(ЗапросSQL), то и итерироваться нужно было бы по объекту НаборЗаписей.
Стандартный и правильный подход к итерации по объекту ADODB.Recordset включает следующие шаги:
Recordset. Это можно сделать двумя основными способами:
Connection.Execute(SQLЗапрос), который вернет новый Recordset.Recordset.Open(SQLЗапрос, Connection), который заполнит уже существующий объект Recordset.Recordset, используя свойство EOF() (End Of File).Recordset.MoveFirst()). Хотя часто это не требуется, если Open или Execute уже позиционировали на первую запись, это хорошая практика для обеспечения надежности.Пока будем проверять свойство EOF(). Пока оно не равно Истина (т.е., пока мы не достигли конца набора записей), обрабатываем текущую запись.Recordset.MoveNext().Recordset и соединение, чтобы освободить ресурсы.Пример корректной итерации, показанный в одном из ответов на форуме, демонстрирует этот подход:
НаборЗаписей.Open(ЗапросSQL);
Пока НаборЗаписей.EOF() = 0 Цикл
// Здесь обрабатываем текущую запись
Сообщить(НаборЗаписей.Fields(0).Value); // Например, выводим первое поле
НаборЗаписей.MoveNext();
КонецЦикла;
НаборЗаписей.Close();
Несмотря на кажущуюся корректность ADODB-кода и итерации, истинная причина проблемы заключалась в самом SQL-запросе. Автор темы ожидал 8 записей, но получал только одну. Разбирая проблему, участники форума обратили внимание на условие WHERE:
ЗапросSQL = "SELECT * FROM pacs.dicom_series where ID like '"+"53016"+"'";
Как выяснилось, поле, по которому производился поиск в базе данных MySQL (ID), не содержало значения '53016' в таком количестве, как ожидалось. На самом деле, 8 записей имели значение '53016' в поле STUDY_ID, а не ID. Это классическая ошибка: перепутано имя столбца в условии WHERE. Когда запрос был выполнен с правильным именем столбца, ожидаемые 8 записей были успешно возвращены.
Для решения проблемы необходимо было изменить SQL-запрос, указав правильное имя поля:
ЗапросSQL = "SELECT * FROM pacs.dicom_series where STUDY_ID like '"+"53016"+"'";
Или, если требуется точное совпадение и поле STUDY_ID является строковым:
ЗапросSQL = "SELECT * FROM pacs.dicom_series where STUDY_ID = '"+"53016"+"'";
Если поле STUDY_ID является числовым, то кавычки не нужны:
ЗапросSQL = "SELECT * FROM pacs.dicom_series where STUDY_ID = 53016";
Использование LIKE '53016' без символов подстановки (% или _) обычно эквивалентно = '53016' для точного строкового совпадения в MySQL, так как в этом случае шаблон совпадает только с точной строкой. Однако всегда предпочтительнее использовать оператор = для точного соответствия, так как это более явно, лучше читается и может быть более оптимизировано системой управления базами данных.
С учетом всех замечаний, полный пример рабочего кода в 1С для выборки данных из MySQL и их помещения в табличную часть 1С будет выглядеть так:
// Это содержимое обработчика формы или модуля объекта 1С
Процедура ЗагрузитьДанныеИзMySQL()
СтрокаСоединенияСервером = "DRIVER={MySQL ODBC 8.0 Unicode Driver};"+
"SERVER=192.168.123.10;"+
"DATABASE=pacs;"+
"UID=user1c;"+
"PWD=Daniel1234;"+
"MULTI_STATEMENTS=1; "+
"STMT=SET CHARACTER SET utf8";
Соединение = Новый COMОбъект("ADODB.Connection");
НаборЗаписей = Новый COMОбъект("ADODB.Recordset");
Попытка
// Открытие соединения
Если Соединение.state = 0 Тогда
Соединение.open(СтрокаСоединенияСервером);
Сообщить("Подключились к MySQL успешно.");
КонецЕсли;
// Определяем правильный SQL-запрос
// Важно: замените 'ID' на 'STUDY_ID' или другое корректное имя поля
// в соответствии со структурой вашей базы данных
ЗапросSQL = "SELECT ID, STUDY_ID, PATIENT_NAME, PATIENT_DOB FROM pacs.dicom_series where STUDY_ID = '53016'";
// Присваиваем соединение объекту Recordset и открываем его
НаборЗаписей.ActiveConnection = Соединение;
НаборЗаписей.Open(ЗапросSQL);
// Очищаем существующие данные в табличной части перед заполнением новыми.
// 'Объект.Серии' - это предполагаемая табличная часть формы/объекта 1С,
// куда мы будем добавлять данные.
// Если этого механизма нет, можно просто обрабатывать и выводить данные.
Если Объект.Серии.Количество() > 0 Тогда
Объект.Серии.Очистить();
КонецЕсли;
нн = 0;
// Проверяем, есть ли вообще записи в наборе результатов
Если НаборЗаписей.EOF() = 0 Тогда
НаборЗаписей.MoveFirst(); // Убедимся, что курсор позиционирован на первой записи
Пока НаборЗаписей.EOF() = 0 Цикл
нн = нн + 1;
стр = Объект.Серии.Добавить(); // Добавляем новую строку в табличную часть 1С
Стр.нн = нн;
// Заполняем поля табличной части данными из Recordset
Стр.ID = НаборЗаписей.Fields("ID").Value;
Стр.STUDY_ID = НаборЗаписей.Fields("STUDY_ID").Value;
Стр.PATIENT_NAME = НаборЗаписей.Fields("PATIENT_NAME").Value;
Стр.PATIENT_DOB = НаборЗаписей.Fields("PATIENT_DOB").Value;
// Для отладки можно выводить полученные значения
Сообщить("Запись №" + нн + ": ID = " + Стр.ID + ", STUDY_ID = " + Стр.STUDY_ID);
НаборЗаписей.MoveNext(); // Переходим к следующей записи
КонецЦикла;
Иначе
Сообщить("По запросу не найдено ни одной записи.");
КонецЕсли;
// Закрываем набор записей и соединение
НаборЗаписей.Close();
Соединение.Close();
Сообщить("Соединение с MySQL закрыто.");
Исключение
Сообщить("Произошла ошибка при работе с базой данных MySQL!");
// Детальная информация об ошибке из ADODB.Errors
Если Соединение.Errors.Количество() > 0 Тогда
Для каждого ОшибкаADODB Из Соединение.Errors Цикл
Сообщить("Описание ошибки: " + ОшибкаADODB.Description);
Сообщить("Номер ошибки: " + ОшибкаADODB.NativeError);
Сообщить("Источник ошибки: " + ОшибкаADODB.Source);
КонецЦикла;
Иначе
Сообщить(ОписаниеОшибки()); // Общая ошибка 1С, если ADODB не предоставил специфику
КонецЕсли;
Возврат;
КонецПопытки;
КонецПроцедуры
Обратите внимание на несколько важных моментов в приведенном коде:
НаборЗаписей.Open()), что является более чистым и однозначным подходом, чем выполнение запроса дважды.Recordset с помощью Если НаборЗаписей.EOF() = 0 Тогда. Это предотвращает попытку доступа к пустым данным и позволяет вывести соответствующее сообщение.Соединение.Errors, что очень полезно для отладки проблем с подключением или SQL-запросами. Каждая ошибка в коллекции Errors содержит подробную информацию: Description, NativeError, Source.Для эффективной и стабильной работы с MySQL через ADODB в 1С мы рекомендуем рассмотреть следующие дополнительные аспекты:
=, а не LIKE без символов подстановки. Например, WHERE STUDY_ID = '53016'.LIKE с символами подстановки: % (любое количество символов) и _ (один символ). Например, WHERE PATIENT_NAME LIKE 'Иванов%' найдет все имена, начинающиеся на "Иванов".WHERE ID = 123). Если поле является строковым – с кавычками (например, WHERE CODE = 'ABC'). MySQL обычно выполняет неявное преобразование типов, но это может повлиять на производительность и привести к непредсказуемым результатам, особенно при больших объемах данных.ADODB.Command с параметрами вместо конкатенации строк. Это особенно актуально, когда значения в запросе формируются на основе пользовательского ввода. Рассмотрим пример параметризованного запроса:
Команда = Новый COMОбъект("ADODB.Command");
Команда.ActiveConnection = Соединение;
Команда.CommandText = "SELECT * FROM pacs.dicom_series WHERE STUDY_ID = ?";
// Создаем параметр: имя (произвольное), тип (adVarChar), направление (adParamInput), размер, значение
// Константы ADODB, такие как adVarChar (200), adParamInput (1), определены в библиотеке ADODB
Параметр = Команда.CreateParameter("STUDY_ID_Param", 200, 1, 10, "53016");
Команда.Parameters.Append(Параметр); // Добавляем параметр в коллекцию
НаборЗаписей = Команда.Execute(); // Выполняем команду
// Далее работаем с НаборЗаписей как обычно
Здесь 200 соответствует константе adVarChar (для строкового типа), 1 — adParamInput (для входного параметра), 10 — максимальная длина значения параметра. Если вы не уверены в точных числовых значениях констант ADODB, вы можете использовать их строковые аналоги, если это поддерживается вашей средой или определить их явно.
STMT=SET CHARACTER SET utf8 в строке соединения критически важен для корректной работы с русским языком и другими многобайтовыми символами. Убедитесь, что ваша база данных MySQL и все соответствующие таблицы также настроены на использование UTF-8 (предпочтительно utf8mb4 для полной поддержки Unicode). Несоответствие кодировок может привести к некорректному отображению данных (так называемые "кракозябры").Попытка...Исключение. Особое внимание уделите анализу коллекции Соединение.Errors, которая может содержать несколько объектов ошибок с подробной информацией о проблемах, возникающих на уровне драйвера ODBC или сервера MySQL. Это существенно упрощает процесс отладки и выявления корневых причин проблем.Проблема получения одной записи вместо ожидаемых нескольких при работе с MySQL через ADODB в 1С чаще всего связана с некорректным SQL-запросом, а именно – с ошибкой в условии WHERE (например, перепутано имя столбца). Тщательная проверка имен столбцов, использование правильных операторов сравнения, понимание типов данных и системный подход к отладке помогут вам эффективно взаимодействовать с внешними базами данных. Применяя описанные подходы и лучшие практики, вы сможете построить надежные и производительные механизмы интеграции в ваших 1С-решениях.