Приветствуем вас! Довольно часто при интеграции 1С с внешними базами данных MS SQL Server разработчики сталкиваются со следующей проблемой: запрос к SQL-базе написан абсолютно верно, таблицы успешно связываются через соединение LEFT JOIN, но при попытке перенести эти данные в табличную часть 1С добавляется всего одна строка. В худшем случае сессия 1С намертво зависает, уходя в бесконечный цикл.
В этой статье мы подробно разберем, почему так происходит, исправим частые синтаксические ошибки и напишем идеальный, отказоустойчивый шаблон для работы с ADODB.Recordset в среде 1С:Предприятие.
Рассмотрим классическую ошибку начинающих разработчиков, с которой и началась данная тема на форуме. Посмотрим на исходный фрагмент кода, в котором выполняется запрос к внешней базе данных:
НаборЗаписей = Новый COMОбъект("ADODB.Recordset");
ТекстЗапроса = "SELECT
| kadr.Kod,
| ab.KodSotr,
| ab.Day as Day
|FROM [" + База + "].[dbo].[God] AS kadr
| LEFT OUTER JOIN [dbo].[St] AS ab ON kadr.Kod = ab.KodSotr";
НоваяСтрока = ОбъектКонтрагент.Дни.Добавить();
НоваяСтрока.РазмерДней = НаборЗаписей.Fields("Day").Value;
Выясним причину, почему в данном случае добавляется всего одна запись, хотя в связанной таблице-источнике их гораздо больше. Обратите внимание на то, где именно вызывается метод Добавить(). Он выполняется ровно один раз вне какого-либо цикла.
Платформа 1С послушно создает одну-единственную строку в табличной части Дни объекта ОбъектКонтрагент и записывает туда значение текущей (первой) записи из набора данных ADO. Чтобы перенести все строки, нам необходим цикл обхода выборки, но его реализация имеет свои важные нюансы.
Пытаясь исправить эту ситуацию самостоятельно, разработчики часто совершают несколько критических ошибок в синтаксисе 1С и технологии ADO. Разберем их по шагам, чтобы больше никогда не повторять на практике.
Для каждого Стр из ОбъектКонтрагент.Дни.Добавить() Цикл приводит к закономерной ошибке выполнения "Значение не является значением объектного типа". Метод Добавить() возвращает конкретную только что добавленную строку табличной части, а не коллекцию строк. Обойти его циклом Для каждого технически невозможно.Для каждого Стр из ОбъектКонтрагент.Дни Цикл также не решает задачу. Если табличная часть изначально пуста (или содержит старые строки), этот цикл либо вообще не выполнится, либо просто перезапишет существующие данные значением одной и той же первой записи SQL-запроса. Нам нужно отталкиваться от количества записей во внешнем источнике, а не в самой 1С.MoveNext().EOF (End Of File) у объекта Recordset является именно булевым свойством, а не методом. Запись вида EOF() со скобками является синтаксически неверной и вызовет ошибку в рантайме. Правильно обращаться к нему напрямую: НаборЗаписей.EOF.Проанализируем правильный алгоритм. Для успешного переноса всех данных нам нужно организовать цикл Пока, проверять свойство EOF (выполнять цикл, пока не достигнут конец файла), внутри цикла создавать новую строку табличной части 1С, заполнять ее реквизиты данными из текущей строки ADO и в самом конце итерации обязательно перемещать указатель ADO на следующую запись с помощью метода MoveNext(). Посмотрим на пример готового решения:
// Сначала рекомендуется очистить табличную часть от старых данных
ОбъектКонтрагент.Дни.Очистить();
// Запускаем цикл обхода записей SQL
Пока Не НаборЗаписей.EOF Цикл
// Добавляем новую строку в табличную часть 1С
НоваяСтрока = ОбъектКонтрагент.Дни.Добавить();
// Получаем значение из поля "Day" и заполняем реквизит
НоваяСтрока.РазмерДней = НаборЗаписей.Fields("Day").Value;
// Переходим к следующей записи в наборе данных (КРИТИЧЕСКИ ВАЖНО!)
НаборЗаписей.MoveNext();
КонецЦикла;
// Закрываем набор записей после завершения работы для освобождения памяти
НаборЗаписей.Close();
Рассмотрим еще один важнейший нюанс, связанный со спецификой работы реляционных баз данных и языка SQL. В исходном запросе автора темы используется связь LEFT OUTER JOIN [dbo].[St] AS ab ON kadr.Kod = ab.KodSotr.
Это означает, что если в левой таблице (кадры) есть сотрудник, для которого нет соответствующих записей в правой таблице (дни), то поле ab.Day вернет значение NULL.
Когда 1С попытается прочитать это значение через НаборЗаписей.Fields("Day").Value, она получит системный тип Null. Попытка напрямую записать Null в числовой или строковый реквизит табличной части 1С приведет к ошибке платформы или некорректному поведению интерфейса. Мы можем решить эту проблему двумя путями.
Вариант 1. Обработка на стороне SQL (рекомендуется): Мы можем обернуть получаемое поле в функцию ISNULL() или COALESCE() в самом SQL-запросе. В таком случае база данных вернет 0 (или пустую строку) вместо NULL:
SELECT
kadr.Kod,
ab.KodSotr,
ISNULL(ab.Day, 0) AS Day
FROM [dbo].[God] AS kadr
LEFT OUTER JOIN [dbo].[St] AS ab ON kadr.Kod = ab.KodSotr
Вариант 2. Обработка на стороне 1С: Если у вас нет прав на изменение SQL-запроса, добавьте проверку типа непосредственно в цикл обхода выборки в 1С:
ЗначениеДня = НаборЗаписей.Fields("Day").Value;
Если ЗначениеДня = Null Тогда
НоваяСтрока.РазмерДней = 0; // Значение по умолчанию
Иначе
НоваяСтрока.РазмерДней = ЗначениеДня;
КонецЕсли;
Использование технологии COMОбъект("ADODB.Connection") и ADODB.Recordset имеет один гигантский недостаток в современных реалиях разработки на платформе 1С:Предприятие 8.3 — жесткую привязку к операционной системе Windows. Если ваша база данных 1С работает на сервере под управлением операционной системы Linux (что сейчас встречается повсеместно при использовании СУБД PostgreSQL), использование COM-объектов вызовет неустранимую ошибку «Класс не зарегистрирован».
Для создания надежных, производительных и кроссплатформенных интеграций мы настоятельно рекомендуем использовать штатный объект платформы — Внешние источники данных (ВИД). Давайте разберем его ключевые преимущества перед классическим ADO:
Новый Запрос), соединять ее со справочниками и документами 1С, использовать в СКД.NULL.Применяйте правильные шаблоны обхода внешних выборок, не забывайте про обязательный вызов метода MoveNext() для предотвращения бесконечных циклов и всегда учитывайте обработку пустых значений при соединении таблиц. Желаем вам успешной разработки и стабильно работающих интеграций!