При разработке механизмов выгрузки данных из 1С в формат DBF программисты часто сталкиваются с ситуацией, когда основной запрос возвращает множество строк, но в итоговом файле оказывается лишь одна запись. Рассмотрим, почему это происходит, разберем типичную ошибку в логике циклов и изучим правильные методы работы с объектом XBase.
Разберем ситуацию, представленную в вопросе. Программист использует цикл Пока ВыборкаДетальныеЗаписи.Следующий() Цикл для обхода результатов основного запроса. Внутри этого цикла выполняется еще один запрос (для получения ФИО физического лица), и результат этого вложенного запроса присваивается той же самой переменной — ВыборкаДетальныеЗаписи.
Проанализируем, что происходит в памяти системы:
ВыборкаДетальныеЗаписи указывает на первую строку основного результата.Запрос и выполняется.ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать() уничтожает связь переменной с основным набором данных и заменяет её новой выборкой (из вложенного запроса).ВыборкаДетальныеЗаписи.Следующий() проверяется уже для выборки ФИО, которая к этому моменту закончена.Решение "в лоб": Использование уникального имени переменной для каждой выборки. Например, для внутреннего запроса следует использовать ВыборкаФИО или ВыборкаДетальныеЗаписи2. Однако это лишь исправление симптома, а не самой архитектурной проблемы кода.
Выполнение запроса внутри цикла — это серьезная ошибка производительности (антипаттерн "N+1"). Если в вашей основной выборке 1000 записей, система обратится к базе данных 1001 раз. Вместо этого мы должны использовать возможности языка SQL (в терминах 1С — языка запросов) и объединить данные одним махом.
Рассмотрим, как должен выглядеть правильный запрос с использованием ЛЕВОЕ СОЕДИНЕНИЕ:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ОсновныеДанные.Код КАК Код,
| ОсновныеДанные.Ссылка.КодПоДРФО КАК КодПоДРФО,
| ОсновныеДанные.Ссылка.Наименование КАК Наименование,
| ОсновныеДанные.ДокументВид КАК ДокументВид,
| ОсновныеДанные.ДокументСерия КАК ДокументСерия,
| ОсновныеДанные.ДокументНомер КАК ДокументНомер,
| ФИОФизЛицСрезПоследних.Фамилия КАК Фамилия,
| ФИОФизЛицСрезПоследних.Имя КАК Имя,
| ФИОФизЛицСрезПоследних.Отчество КАК Отчество
|ИЗ
| Справочник.Сотрудники КАК ОсновныеДанные
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ФИОФизЛиц.СрезПоследних(&Период, ) КАК ФИОФизЛицСрезПоследних
| ПО ОсновныеДанные.Физлицо = ФИОФизЛицСрезПоследних.ФизЛицо
|ГДЕ
| ОсновныеДанные.Ссылка В(&МассивСотрудников)";
Запрос.УстановитьПараметр("Период", ТекущаяДата());
Запрос.УстановитьПараметр("МассивСотрудников", СписокСотрудников);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
ДБФ.Добавить();
ДБФ.CODE = Выборка.Код;
ДБФ.LASTNAME = Выборка.Фамилия;
ДБФ.FIRSTNAME = Выборка.Имя;
// ... заполнение остальных полей ...
ДБФ.Записать();
КонецЦикла;
Такой подход позволяет получить все необходимые данные за один вызов сервера БД, что ускоряет выполнение задачи в десятки раз и полностью устраняет проблему с "потерянными" строками.
Создание DBF-файла через встроенный объект 1С XBase требует соблюдения определенной последовательности действий и понимания того, как устроены эти файлы.
1. Инициализация структуры. Перед записью данных необходимо программно создать поля. Если вы открываете существующий файл, используйте ОткрытьФайл(), если создаете новый — СоздатьФайл(). Важно помнить, что длина поля в DBF жестко ограничена.
2. Кодировка. Это "камень преткновения" при передаче файлов в госструктуры. Большинство старых систем (например, банковские клиенты или налоговые программы) используют кодировку DOS (OEM). В 1С это задается свойством:
ДБФ.Кодировка = КодировкаXBase.OEM; (для кодировки DOS)
ДБФ.Кодировка = КодировкаXBase.ANSI; (для кодировки Windows-1251)
3. Метод Записать() и Добавить(). Разберем разницу. Метод Добавить() фактически выделяет место под новую строку в буфере. После этого вы заполняете поля. Метод Записать() переносит данные из буфера непосредственно в файл на диске. Если вы забудете вызвать Записать() внутри цикла, ваши данные не сохранятся.
DBF крайне чувствителен к типам. Проанализируем типичные проблемы:
СокрЛП(). В DBF строковые поля имеют фиксированную длину. Если поле определено как 50 символов, а вы записываете "Иван", в файл запишется "Иван" и 46 пробелов. 1С обрезает лишнее автоматически, если строка длиннее поля, но лучше контролировать это самостоятельно.NULL или неопределено, в числовое поле DBF нужно записывать 0. Попытка записать пустую строку в числовое поле вызовет ошибку.ДБФ.DATE_FIELD = ?(ЗначениеЗаполнено(Выборка.Дата), Выборка.Дата, Дата(1900, 1, 1)); (или оставлять пустой, если принимающая сторона это поддерживает).В некоторых случаях стандартный объект XBase может работать недостаточно быстро или иметь проблемы с чтением специфических диалектов DBF. Профессиональные разработчики иногда используют технологию ADO (ActiveX Data Objects). Это позволяет работать с DBF-файлом как с таблицей базы данных, используя привычные SQL-команды INSERT INTO.
Пример логики через ADO (схематично):
Connection = Новый COMОбъект("ADODB.Connection");
ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + ПутьККаталогу + ";Extended Properties=DBASE IV;";
Connection.Open(ConnectionString);
Command = Новый COMОбъект("ADODB.Command");
Command.ActiveConnection = Connection;
Command.CommandText = "INSERT INTO file.dbf (CODE, NAME) VALUES ('001', 'Тест')";
Command.Execute();
Connection.Close();
Этот метод считается более "тяжелым", но он незаменим, когда требуется высокая точность управления типами данных или работа с файлами через сетевые драйверы.
Всегда закрывайте файл методом ЗакрытьФайл(). Если ваша обработка аварийно завершится до вызова этого метода, файл может остаться заблокированным операционной системой, и вы не сможете его открыть, удалить или перезаписать до перезагрузки сеанса 1С или даже всего компьютера. Также хорошим тоном считается проверка на существование файла перед созданием (использование функции НайтиФайлы()) и его удаление, если вы хотите создать чистую выгрузку.
Подведем итог: чтобы ваша выгрузка работала корректно, никогда не переиспользуйте переменные итераторов, старайтесь минимизировать количество обращений к БД за счет соединений в запросах и всегда явно указывайте кодировку создаваемого DBF-файла.