Как сравнить таблицу значений с регистром сведений в 1С 8.3 и записать новые данные без ошибок?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

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

Анализ типичных ошибок из практики разработчиков

Давайте рассмотрим классический сценарий, с которым часто сталкиваются программисты. На форме присутствует реквизит с типом ТаблицаЗначений (в исходном коде он обозначен как Реквизит3). Задача состоит в том, чтобы сравнить строки этой таблицы с данными регистра сведений и при отсутствии совпадений добавить новые записи в регистр.

Посмотрим на пример кода, содержащего типичные ошибки проектирования:


Процедура ИзменитьДОКНаСервере()
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Аб.ЛицевойСчет КАК ЛицевойСчет,
        |   ДанныеФизЛицСрезПоследних.Вид КАК Вид,
        |   ДанныеФизЛицСрезПоследних.Период КАК Период,
        |   ДанныеФизЛицСрезПоследних.Номер КАК Номер,
        |   ДанныеФизЛицСрезПоследних.ФизЛицо КАК ФизЛицо
        |ИЗ
        |   Справочник.Аб КАК Аб
        |   ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ДанныеФизЛиц.СрезПоследних КАК ДанныеФизЛицСрезПоследних
        |   ПО Аб.ВладелецЛС = ДанныеФизЛицСрезПоследних.ФизЛицо";
    
    Результат = Запрос.Выполнить().Выбрать();
    
    Для каждого ЛицевойСчет из Реквизит3 Цикл
        Если ЛицевойСчет = Результат.ЛицевойСчет Тогда
            // Какое-то действие...
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

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

  1. Неверное обращение к строке таблицы значений. В конструкции цикла Для каждого ЛицевойСчет из Реквизит3 переменная ЛицевойСчет представляет собой не конкретное значение (строку или ссылку на лицевой счет), а полноценный объект СтрокаТаблицыЗначений. Пытаясь сравнить всю строку таблицы с конкретным полем из результата запроса (Результат.ЛицевойСчет), мы всегда будем получать результат Ложь. Чтобы сравнение заработало, необходимо обращаться к конкретной колонке этой строки, например: ЛицевойСчет.ИмяКолонкиЛицевогоСчета.
  2. Отсутствие обхода выборки запроса. Переменная Результат после вызова метода Выбрать() возвращает объект ВыборкаИзРезультатаЗапроса. Этот объект является указателем на набор строк, но сам по себе не позиционирован ни на одной из них. Чтобы получить доступ к данным первой строки, необходимо вызвать метод Результат.Следующий(). Без этого вызова обращение к свойству Результат.ЛицевойСчет вернет пустое значение.
  3. Неоптимальный алгоритм. Сравнивать каждую строку таблицы значений на сервере вручную через вложенные циклы — это крайне непроизводительный подход, который приведет к резкому падению скорости работы программы при увеличении объема данных в таблице.

Решение проблемы: Сравнение без циклов через Временные Таблицы

Самым правильным и производительным способом сравнения больших объемов данных в платформе 1С является перенос логики сопоставления на сторону СУБД. Для этого мы можем передать нашу таблицу значений прямо в запрос в качестве параметра, поместить её во временную таблицу и выполнить левое соединение с регистром сведений.

Разберем этот процесс по шагам.

Шаг 1. Преобразование реквизита формы в полноценную Таблицу Значений. Поскольку на управляемой форме данные хранятся в специальном формате ДанныеФормыКоллекция, их нельзя напрямую передать в параметр запроса. Нам потребуется преобразовать реквизит формы в классический объект типа ТаблицаЗначений:

ТаблицаНаСервере = РеквизитФормыВЗначение("Реквизит3");

Важно помнить: Колонки таблицы значений должны быть жестко типизированы. Если таблица на форме создавалась динамически, обязательно убедитесь, что её колонкам назначены соответствующие типы данных (например, СправочникСсылка.Аб, СправочникСсылка.ФизическиеЛица и т.д.).

Шаг 2. Разработка запроса с использованием Временной Таблицы. Мы создаем запрос, передаем туда нашу таблицу значений в качестве параметра и соединяем её с регистром сведений. При этом используем условие ЕСТЬ NULL, чтобы отобрать только те записи, которых еще нет в нашей базе данных.


Запрос = Новый Запрос;
// Обязательно инициализируем менеджер временных таблиц
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;

// Помещаем исходную ТЗ во временную таблицу
Запрос.Текст = 
    "ВЫБРАТЬ
    |   ТЗ.ЛицевойСчет КАК ЛицевойСчет,
    |   ТЗ.ФизЛицо КАК ФизЛицо
    |ПОМЕСТИТЬ ВТ_ИсходныеДанные
    |ИЗ
    |   &ВнешнийИсточник КАК ТЗ";
    
Запрос.УстановитьПараметр("ВнешнийИсточник", ТаблицаНаСервере);
Запрос.Выполнить();

// Выполняем сопоставление с регистром сведений
Запрос.Текст = 
    "ВЫБРАТЬ
    |   ВТ.ЛицевойСчет КАК ЛицевойСчет,
    |   ВТ.ФизЛицо КАК ФизЛицо
    |ИЗ
    |   ВТ_ИсходныеДанные КАК ВТ
    |   ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ДанныеФизЛиц.СрезПоследних КАК ДанныеФизЛиц
    |       ПО ВТ.ФизЛицо = ДанныеФизЛиц.ФизЛицо
    |ГДЕ
    |   ДанныеФизЛиц.ФизЛицо ЕСТЬ NULL"; // Фильтр: выбираем только то, чего нет в БД
    
РезультатЗапроса = Запрос.Выполнить();

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

Как правильно записать новые данные в регистр сведений

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

Способ А. Использование Набора Записей с установкой отбора (Рекомендуемый)

Чтобы платформа не затерла весь регистр, мы должны четко указать ей, по каким измерениям производится запись. Для этого используется объект РегистрСведенийНаборЗаписей с обязательной установкой отборов.


Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
    
    Набор = РегистрыСведений.ДанныеФизЛиц.СоздатьНаборЗаписей();
    
    // Устанавливаем отбор по ключевому измерению
    Набор.Отбор.ФизЛицо.Установить(Выборка.ФизЛицо);
    
    // Считываем текущие записи из базы данных по этому отбору
    Набор.Прочитать();
    
    // Добавляем новую запись к прочитанным
    НоваяЗапись = Набор.Добавить();
    НоваяЗапись.Период = ТекущаяДата();
    НоваяЗапись.ФизЛицо = Выборка.ФизЛицо;
    НоваяЗапись.Номер = Выборка.ЛицевойСчет;
    
    // Записываем набор. Старые данные по другим физлицам не пострадают!
    Набор.Записать();
    
КонецЦикла;

Способ Б. Массовая запись без замещения

Если вам нужно быстро "докинуть" массив уникальных строк в регистр сведений без лишних обращений к СУБД для каждой строки, можно заполнить один общий набор записей и вызвать метод Записать(Ложь). Параметр Ложь указывает платформе, что старые записи заменять не нужно.


Набор = РегистрыСведений.ДанныеФизЛиц.СоздатьНаборЗаписей();
// Отборы не устанавливаем, так как пишем данные массово в конец таблицы

Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
    НоваяЗапись = Набор.Добавить();
    НоваяЗапись.Период = ТекущаяДата();
    НоваяЗапись.ФизЛицо = Выборка.ФизЛицо;
    НоваяЗапись.Номер = Выборка.ЛицевойСчет;
КонецЦикла;

// Важнейший параметр: Записать(Ложь) - режим добавления
Набор.Записать(Ложь);

Внимание: Данный метод сработает успешно только в том случае, если среди добавляемых строк нет записей с ключевыми измерениями, которые уже физически существуют в базе данных. Если возникнет конфликт уникальности ключа, система выдаст ошибку транзакции.

Способ В. Использование Менеджера Записи (Самый простой и безопасный)

Для небольших объемов данных или точечного обновления записей удобнее всего использовать РегистрСведенийМенеджерЗаписи. Этот объект работает по принципу одиночной строки таблицы: он сам находит нужную запись по ключевым измерениям и либо обновляет ресурсы, либо создает новую строку.


Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
    
    МенеджерЗаписи = РегистрыСведений.ДанныеФизЛиц.СоздатьМенеджерЗаписи();
    
    // Указываем ключевые поля для поиска/создания записи
    МенеджерЗаписи.Период = ТекущаяДата();
    МенеджерЗаписи.ФизЛицо = Выборка.ФизЛицо;
    
    // Заполняем ресурсы
    МенеджерЗаписи.Номер = Выборка.ЛицевойСчет;
    
    // Записываем. Если запись существовала - она обновится, если нет - создастся новая.
    МенеджерЗаписи.Записать();
    
КонецЦикла;

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

Заключение

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

  1. Никогда не сравнивайте переменную цикла строки ТЗ напрямую со значениями реквизитов. Всегда обращайтесь к конкретному имени колонки строки.
  2. Используйте временные таблицы для сопоставления коллекций на стороне СУБД вместо медленных вложенных циклов. Это сбережет ресурсы сервера и ускорит выполнение операции.
  3. При работе с объектом НаборЗаписей всегда устанавливайте отборы по измерениям либо используйте метод записи с параметром Ложь, чтобы избежать непреднамеренной очистки данных регистра.
← На главную