Как избежать записи дублирующихся цен в регистр сведений 1С, если цена не изменилась?

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

В процессе разработки в среде 1С:Предприятие часто возникает задача оптимизации хранения данных. Одной из таких задач является предотвращение создания «избыточных» записей в периодических регистрах сведений. Например, если цена на товар на 25-е число составляла 100 рублей, и на 30-е число пользователь снова вводит 100 рублей, то физическая запись в базе данных за 30-е число не несет новой смысловой нагрузки. Система и так «знает» по срезу последних, что цена актуальна с 25-го числа.

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

Зачем нужна проверка на идентичность данных?

Прежде чем переходить к коду, давайте разберем, почему важно избегать дублей. Основная причина — раздувание базы данных. В крупных торговых сетях номенклатура может исчисляться сотнями тысяч позиций. Если ежедневно записывать цены, которые не менялись, размер таблиц регистра сведений будет расти в геометрической прогрессии, что со временем замедлит работу отчетов и выполнение запросов к виртуальным таблицам СрезПоследних и СрезПервых.

Вторая причина — чистота истории. Аналитикам гораздо удобнее видеть в истории только те моменты, когда цена действительно претерпела изменения.

Способ 1: Использование запроса к виртуальной таблице «СрезПоследних»

Это самый распространенный и понятный способ. Логика проста: перед тем как записать новую цену, мы спрашиваем у системы, какая цена является актуальной на текущий момент (или на дату документа). Если новая цена совпадает со старой, мы просто пропускаем итерацию записи.

Разберем пример реализации в коде. Предположим, у нас есть документ, в табличной части которого указана номенклатура и новая цена. Нам нужно сравнить эти данные с регистром ЦеныНоменклатуры.

Сначала подготовим запрос для получения актуальных цен:


// Подготовим список номенклатуры для отбора в запросе
СписокНоменклатуры = Объект.Товары.ВыгрузитьКолонку("Номенклатура");

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ЦеныНоменклатурыСрезПоследних.Номенклатура КАК Номенклатура,
    |	ЦеныНоменклатурыСрезПоследних.Цена КАК Цена
    |ИЗ
    |	РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&ДатаСреза, Номенклатура В (&СписокНоменклатуры) И ВидЦены = &ВидЦены) КАК ЦеныНоменклатурыСрезПоследних";

Запрос.УстановитьПараметр("ДатаСреза", Объект.Дата);
Запрос.УстановитьПараметр("СписокНоменклатуры", СписокНоменклатуры);
Запрос.УстановитьПараметр("ВидЦены", Справочники.ВидыЦен.Розничная);

ТаблицаАктуальныхЦен = Запрос.Выполнить().Выгрузить();
// Для быстрого поиска проиндексируем таблицу по номенклатуре
ТаблицаАктуальныхЦен.Индексы.Добавить("Номенклатура");

Теперь проанализируем процесс записи в цикле, сопоставляя данные табличной части с результатом запроса:


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

Для Каждого СтрокаТЧ Из Объект.Товары Цикл
    
    // Пытаемся найти текущую цену в нашей таблице среза последних
    ПараметрыОтбора = Новый Структура("Номенклатура", СтрокаТЧ.Номенклатура);
    НайденныеСтроки = ТаблицаАктуальныхЦен.НайтиСтроки(ПараметрыОтбора);
    
    ЦенаВРегистре = 0;
    Если НайденныеСтроки.Количество() > 0 Тогда
        ЦенаВРегистре = НайденныеСтроки[0].Цена;
    КонецЕсли;
    
    // Главное условие: записываем только если цена изменилась
    Если СтрокаТЧ.Цена <> ЦенаВРегистре Тогда
        НоваяЗапись = НаборЗаписей.Добавить();
        НоваяЗапись.Период = Объект.Дата;
        НоваяЗапись.Номенклатура = СтрокаТЧ.Номенклатура;
        НоваяЗапись.ВидЦены = Справочники.ВидыЦен.Розничная;
        НоваяЗапись.Цена = СтрокаТЧ.Цена;
    КонецЕсли;
    
КонецЦикла;

// Записываем набор, если в нем появились записи
Если НаборЗаписей.Количество() > 0 Тогда
    НаборЗаписей.Записать(Ложь); // Используем Ложь, чтобы не затереть другие записи, если это необходимо
КонецЕсли;

Способ 2: Оптимизация через временные таблицы (для больших документов)

Если в вашем документе тысячи строк, поиск в цикле методом НайтиСтроки может работать медленно. В этом случае правильнее перенести всю логику сравнения на сторону SQL-сервера с помощью временных таблиц.

Рассмотрим этот алгоритм:

  1. Выгружаем табличную часть документа во временную таблицу в запросе.
  2. Соединяем её с виртуальной таблицей СрезПоследних.
  3. В условии соединения или в секции ГДЕ указываем, что нас интересуют только те строки, где ТЧ.Цена <> СрезПоследних.Цена.
  4. Получаем готовый список строк, которые действительно нужно записать.

Такой подход минимизирует объем данных, передаваемых между сервером базы данных и сервером приложений 1С.

Способ 3: Использование Менеджера записи

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

Посмотрим на пример:


МенеджерЗаписи = РегистрыСведений.ЦеныНоменклатуры.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Номенклатура = ТекущаяНоменклатура;
МенеджерЗаписи.ВидЦены = ТекущийВидЦены;
МенеджерЗаписи.Период = ТекущаяДата;

// Пытаемся прочитать существующую запись
МенеджерЗаписи.Прочитать();

Если МенеджерЗаписи.Выбран() Тогда
    // Запись на эту дату уже есть, проверяем цену
    Если МенеджерЗаписи.Цена <> НоваяЦена Тогда
        МенеджерЗаписи.Цена = НоваяЦена;
        МенеджерЗаписи.Записать();
    КонецЕсли;
Иначе
    // Записи на эту дату нет, нужно проверить срез последних на предыдущую дату
    // (Здесь можно вставить логику из Способа 1)
КонецЕсли;

Важные нюансы при записи (НаборЗаписей)

Нужно обратить внимание на использование метода Записать(Ложь). Параметр Замещение = Ложь означает, что новые записи будут добавлены к существующим в регистре, а не заменят весь диапазон по отбору набора.

Внимание: Если в регистре уже существует запись с такими же измерениями (Номенклатура, ВидЦены) и на ту же самую секунду (Период), метод Записать(Ложь) вызовет исключение «Запись с такими ключевыми полями существует». Если вы хотите обновить цену в рамках той же даты, следует использовать Записать(Истина), предварительно установив отбор на набор записей.

Учет блокировок данных

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

Для предотвращения конфликтов и «гонок данных» при проверке цен необходимо использовать управляемые блокировки. Перед выполнением запроса следует заблокировать записи регистра по конкретной номенклатуре:


Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ЦеныНоменклатуры");
ЭлементБлокировки.УстановитьПараметр("Номенклатура", СписокНоменклатуры);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();

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

Режим «Загрузка»

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


Процедура ПередЗаписью(Отказ, Замещение)
    Если ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;
    
    // Здесь ваша логика проверки идентичности цен
КонецПроцедуры

Без этой проверки правила обмена могут работать некорректно, если в источнике данных намеренно были созданы записи с одинаковыми ценами для каких-либо целей.

Заключение

Мы выяснили, что контроль дублирования цен — это не только вопрос экономии места, но и вопрос производительности системы в целом. Наиболее гибким и производительным методом является предварительный запрос к СрезПоследних с использованием временных таблиц и блокировок. Реализация такой проверки сделает вашу конфигурацию более профессиональной и устойчивой к большим объемам данных.

← На главную