Как найти повторяющиеся строки и проверить уникальность цен в таблице значений 1С?

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

При интерактивном формировании документов (например, установки цен или прайс-листов) часто возникает задача контроля дублирования данных. Типичный сценарий: пользователь добавляет в обработке строки с составом полей Номенклатура, Цвет и Цена. Бизнес-логика требует, чтобы для одной комбинации номенклатуры и цвета не могло быть задано две разные цены.

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

Способ 1. Проверка в момент добавления или изменения строки в форме

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

В управляемых формах строки редактируются в объекте типа ДанныеФормыКоллекция. Для контроля лучше всего подходит событие ПередОкончаниемРедактирования, так как в нем доступен параметр Отказ, позволяющий заблокировать фиксацию строки.

Посмотрим на пример кода в модуле формы:


&НаКлиенте
Процедура ТаблицаЦенПередОкончаниемРедактирования(Элемент, НоваяСтрока, ОтменаРедактирования, Отказ)
    
    Если ОтменаРедактирования Тогда
        Возврат;
    КонецЕсли;
    
    ТекущиеДанные = Элемент.ТекущиеДанные;
    Если ТекущиеДанные = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    ПараметрыОтбора = Новый Структура("Номенклатура, Цвет", ТекущиеДанные.Номенклатура, ТекущиеДанные.Цвет);
    НайденныеСтроки = Объект.ТаблицаЦен.НайтиСтроки(ПараметрыОтбора);
    
    Для Каждого Стр Из НайденныеСтроки Цикл
        // Исключаем сравнение текущей строки с самой собой
        Если Стр.ПолучитьИдентификатор() <> ТекущиеДанные.ПолучитьИдентификатор() Тогда
            Если Стр.Цена <> ТекущиеДанные.Цена Тогда
                ПоказатьОшибку(НСтр("ru = 'Для выбранной номенклатуры и цвета уже задана другая цена!'"));
                Отказ = Истина;
                Прервать;
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;

КонецПроцедуры

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

Способ 2. Проверка через группировку запросом к таблице значений

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

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


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

Разберем логику работы запроса:

  1. Конструкция КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ТЗ.Цена) подсчитывает, сколько уникальных цен привязано к одной комбинации номенклатуры и цвета.
  2. В итоговой выборке через предложение ГДЕ ВтДанные.КоличествоЦен > 1 мы отсекаем все корректные строки.
  3. Если выгруженная таблица не пуста, значит, в исходных данных есть конфликты, которые необходимо исправить перед созданием документа.

Способ 3. Использование метода Свернуть()

Рассмотрим популярный алгоритмический прием, не требующий вызова механизма запросов. Этот способ отлично работает на сервере с объектом ТаблицаЗначений.

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


&НаСервере
Функция НайтиПовторяющиесяСтрокиСверткой(ИсходнаяТЗ)
    
    // Создаем копию, чтобы не нарушать исходные данные пользователя
    ТЗКопия = ИсходнаяТЗ.Скопировать();
    
    // Сначала убираем полные дубли (где совпадают и Номенклатура, и Цвет, и Цена)
    ТЗКопия.Свернуть("Номенклатура, Цвет, Цена");
    
    // Добавляем счетчик вхождений
    ТЗКопия.Колонки.Добавить("КоличествоПовторов", Новый ОписаниеТипов("Число"));
    ТЗКопия.ЗаполнитьЗначения(1, "КоличествоПовторов");
    
    // Сворачиваем по ключевым колонкам, суммируя счетчик
    ТЗКопия.Свернуть("Номенклатура, Цвет", "КоличествоПовторов");
    
    // Отбираем строки, где зафиксировано более одной цены
    ОшибочныеСтроки = Новый Массив;
    Для Каждого Стр Из ТЗКопия Цикл
        Если Стр.КоличествоПовторов > 1 Тогда
            ОшибочныеСтроки.Добавить(Стр);
        КонецЕсли;
    КонецЦикла;
    
    Возврат ОшибочныеСтроки;
    
КонецФункции

Обратим внимание на важную деталь: первый вызов метода Свернуть("Номенклатура, Цвет, Цена") обязателен. Если пользователь случайно дважды добавил одну и ту же позицию с одинаковой ценой, это не является ошибкой разной стоимости. Первая свертка устранит идентичные дубли, а вторая выявит именно расхождения по цене.

Способ 4. Линейный поиск через сортировку

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


&НаСервере
Функция ПроверитьТаблицуСортировкой(ТЗ)
    
    ТЗ.Сортировать("Номенклатура Возр, Цвет Возр, Цена Возр");
    
    ЕстьОшибки = Ложь;
    ПредыдущаяСтрока = Неопределено;
    
    Для Каждого ТекущаяСтрока Из ТЗ Цикл
        
        Если ПредыдущаяСтрока <> Неопределено Тогда
            Если ПредыдущаяСтрока.Номенклатура = ТекущаяСтрока.Номенклатура
                И ПредыдущаяСтрока.Цвет = ТекущаяСтрока.Цвет
                И ПредыдущаяСтрока.Цена <> ТекущаяСтрока.Цена Тогда
                
                ОбщегоНазначенияКлиентСервер.СообщитьПользователю(
                    СтрШаблон("Конфликт цен для позиции: %1, цвет: %2 (%3 и %4)", 
                        ТекущаяСтрока.Номенклатура, 
                        ТекущаяСтрока.Цвет, 
                        ПредыдущаяСтрока.Цена, 
                        ТекущаяСтрока.Цена)
                );
                ЕстьОшибки = Истина;
            КонецЕсли;
        КонецЕсли;
        
        ПредыдущаяСтрока = ТекущаяСтрока;
        
    КонецЦикла;
    
    Возврат НЕ ЕстьОшибки;
    
КонецФункции

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

← На главную