При разработке конфигураций на платформе 1С, особенно для мобильных приложений, часто возникает задача записи данных из табличной части документа в непериодический регистр сведений. Основная сложность здесь заключается в обеспечении уникальности записей: если попытаться записать в регистр набор измерений, который там уже существует, система выдаст ошибку нарушения уникальности. Рассмотрим подробно, как элегантно решить эту задачу, избежав лишних движений и ошибок производительности.
Это наиболее эффективный и правильный с точки зрения производительности способ, особенно если строк в табличной части документа много. Вместо того чтобы проверять каждую строку в цикле (что порождает избыточные обращения к базе данных), мы одним запросом определим, каких данных еще нет в регистре.
Проанализируем логику этого подхода: мы берем данные из табличной части нашего документа и "накладываем" их на существующие записи регистра. Те строки, для которых соответствие в регистре не найдено (где значения из регистра будут NULL), являются новыми и подлежат записи.
Рассмотрим пример реализации такого запроса:
// Создаем запрос для получения только новых строк
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| ДокТЧ.Упражнение КАК Упражнение,
| ДокТЧ.Вес КАК Вес
|ПОМЕСТИТЬ ВТ_ДанныеДокумента
|ИЗ
| &ТаблицаДанных КАК ДокТЧ
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ.Упражнение,
| ВТ.Вес
|ИЗ
| ВТ_ДанныеДокумента КАК ВТ
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ИсторияТренировок КАК Рег
| ПО ВТ.Упражнение = Рег.Упражнение
| И ВТ.Вес = Рег.Вес
|ГДЕ
| Рег.Упражнение ЕСТЬ NULL";
// Передаем таблицу части документа в запрос
Запрос.УстановитьПараметр("ТаблицаДанных", Объект.ВыполненныеУпражнения.Выгрузить());
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
// Здесь выполняем запись в регистр
КонецЦикла;
Важный момент: использование конструкции ВЫБРАТЬ РАЗЛИЧНЫЕ внутри запроса или предварительное использование метода Свернуть() для таблицы значений документа позволяет избежать дублей внутри самой транзакции записи, если пользователь случайно ввел одинаковые строки в одном документе.
Если нам нужно проверить наличие записи "здесь и сейчас" для одной конкретной строки, удобнее использовать объект РегистрСведенийМенеджерЗаписи. Этот метод более "объектный" и наглядный, но его стоит использовать с осторожностью внутри циклов при большом количестве данных.
Разберем алгоритм работы с менеджером записи по шагам:
Прочитать().Выбран(). Если оно возвращает Ложь, значит, такой комбинации измерений в базе нет.Пример кода для этого метода:
Для Каждого СтрокаТЧ Из Объект.ВыполненныеУпражнения Цикл
МенеджерЗаписи = РегистрыСведений.ИсторияТренировок.СоздатьМенеджерЗаписи();
// Заполняем измерения для поиска
МенеджерЗаписи.Упражнение = СтрокаТЧ.Упражнение;
МенеджерЗаписи.Вес = СтрокаТЧ.Вес;
// Пытаемся прочитать запись из базы
МенеджерЗаписи.Прочитать();
Если Не МенеджерЗаписи.Выбран() Тогда
// Записи нет, можем заполнять ресурсы и записывать
МенеджерЗаписи.ДатаПервогоПодхода = ТекущаяДата();
МенеджерЗаписи.Записать();
КонецЕсли;
КонецЦикла;
Этот способ хорош тем, что он автоматически учитывает текущие блокировки базы данных, но проигрывает запросу в скорости при обработке сотен строк.
В некоторых случаях, например, при работе на мобильной платформе с ограниченными ресурсами или специфической логикой синхронизации, бывает удобно выгрузить данные регистра в таблицу значений и сравнить их в оперативной памяти. Рассмотрим, как ускорить этот процесс с помощью индексации.
Если мы просто будем искать строку в таблице перебором, это займет много времени. Поэтому мы добавим индекс по ключевым полям:
// Выгружаем существующие записи регистра в ТЗ
ТаблицаРегистра = РегистрыСведений.ИсторияТренировок.СрезПоследних(); // Или просто Выбрать()
ТаблицаРегистра.Индексы.Добавить("Упражнение, Вес");
ТаблицаДокумента = Объект.ВыполненныеУпражнения.Выгрузить();
Для Каждого СтрокаДок Из ТаблицаДокумента Цикл
ПараметрыОтбора = Новый Структура("Упражнение, Вес", СтрокаДок.Упражнение, СтрокаДок.Вес);
НайденныеСтроки = ТаблицаРегистра.НайтиСтроки(ПараметрыОтбора);
Если НайденныеСтроки.Количество() = 0 Тогда
// Таких данных в регистре нет, добавляем в список на запись
КонецЕсли;
КонецЦикла;
Проанализируем ситуацию, описанную в обсуждении: автору важно знать, когда он впервые выполнил упражнение с определенным весом. Использование непериодического регистра с проверкой на "уникальность" может быть избыточным усложнением.
Рекомендуемое решение: сделать регистр сведений периодическим (с периодичностью "В пределах секунды") и подчиненным регистратору. В этом случае:
СрезПервых.Это избавляет от необходимости писать сложный код проверки перед записью и делает систему более гибкой: вы всегда сможете посмотреть историю прогресса, а не только факт "было или не было".
Выясним причину, почему на мобильных устройствах стоит быть внимательнее к проверкам:
Упражнение и Вес являются измерениями регистра. В 1С измерения индексируются автоматически, что критически важно для быстродействия запросов типа ЛЕВОЕ СОЕДИНЕНИЕ.Используя предложенные методы, вы сможете не только избежать раздражающих сообщений об ошибках, но и повысить общую стабильность и скорость работы вашего приложения.