Как реализовать автоматическое упорядочивание и проверку позиции элементов в иерархическом справочнике?

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

В процессе разработки часто возникает задача управления порядком элементов внутри групп иерархического справочника. Типичное требование: элементы в пределах одного родителя должны быть пронумерованы строго по порядку (1, 2, 3... N). Это позволяет пользователям менять элементы местами, например, устанавливая элементу позицию 2.5, чтобы он встал между вторым и третьим, после чего система должна автоматически нормализовать все индексы обратно в целочисленные. Рассмотрим, как реализовать алгоритм проверки и исправления такого порядка максимально эффективно.

Постановка задачи и анализ ситуации

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

Решение 1: Математический метод (Сумма арифметической прогрессии)

Это наиболее изящный и производительный способ поиска "проблемных" групп без использования ресурсоемких соединений (JOIN) в запросах. Идея основана на формуле суммы членов арифметической прогрессии. Если в группе N элементов, то сумма их позиций должна быть равна N * (N + 1) / 2. Если сумма отличается — в группе точно нарушен порядок.

Разберем реализацию этого алгоритма на языке запросов 1С:


ВЫБРАТЬ
    Т.Родитель КАК Родитель,
    КОЛИЧЕСТВО(*) КАК Количество,
    СУММА(Т.Позиция) КАК СуммаПозиции
ПОМЕСТИТЬ ТГруппы
ИЗ
    Справочник.ВашСправочник КАК Т
СГРУППИРОВАТЬ ПО
    Т.Родитель
ИМЕЮЩИЕ
    (1 + КОЛИЧЕСТВО(*)) * КОЛИЧЕСТВО(*) / 2 <> СУММА(Т.Позиция)

Однако, как справедливо заметили участники обсуждения, одной проверки суммы недостаточно, так как возможны коллизии (например, набор [1, 1.5, 3.5] даст ту же сумму, что и [1, 2, 3]). Рассмотрим подробнее, как усилить эту проверку без потери производительности. Для полной гарантии правильности ряда 1..N необходимо добавить дополнительные условия в ИМЕЮЩИЕ:

  1. Минимум должен быть равен 1: МИНИМУМ(Позиция) = 1
  2. Максимум должен быть равен количеству элементов: МАКСИМУМ(Позиция) = КОЛИЧЕСТВО(*)
  3. Количество различных значений должно совпадать с общим количеством: КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Позиция) = КОЛИЧЕСТВО(*)

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

Решение 2: Классический метод с использованием соединений

Если математический метод кажется слишком абстрактным, рассмотрим более традиционный способ — сравнение текущей позиции с "идеальным" номером строки, полученным через соединение таблицы самой с собой. Мы можем посчитать, сколько элементов в этой же группе имеют позицию меньше или равную текущей.

Пример запроса для выявления конкретных элементов, требующих изменения:


ВЫБРАТЬ
    Номенклатура.Ссылка КАК Ссылка,
    КОЛИЧЕСТВО(Номенклатура1.Ссылка) КАК НоваяПозиция
ИЗ
    Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Номенклатура1
    ПО Номенклатура.Родитель = Номенклатура1.Родитель
    И (Номенклатура.Позиция > Номенклатура1.Позиция
        ИЛИ (Номенклатура.Позиция = Номенклатура1.Позиция 
             И Номенклатура.Ссылка >= Номенклатура1.Ссылка))
СГРУППИРОВАТЬ ПО
    Номенклатура.Родитель,
    Номенклатура.Ссылка,
    Номенклатура.Позиция
ИМЕЮЩИЕ
    КОЛИЧЕСТВО(Номенклатура1.Ссылка) <> Номенклатура.Позиция

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

Решение 3: Использование оконных функций (ROW_NUMBER)

В современных версиях платформы 1С 8.3 появилась поддержка оконных функций. Это позволяет решить задачу максимально "чисто" с точки зрения SQL. Мы можем рассчитать порядковый номер элемента в контексте его родителя, упорядочив данные по текущему полю позиции.

Хотя в языке запросов 1С синтаксис оконных функций ограничен, логика выглядит так: мы выбираем данные, применяем ПОРЯДКОВЫЙ_НОМЕР() с группировкой по Родителю и сортировкой по Позиции. Если полученное значение не совпадает с записанным в базе — элемент нуждается в обновлении.

Алгоритм исправления порядка (Программная часть)

Выясним, как эффективно обновить данные после того, как "проблемные" группы найдены. Рассмотрим программный код, который обходит выборку по группировкам и перезаписывает только изменившиеся элементы:


// Выборка уже содержит только те группы, где сумма прогрессии не сошлась
ВыборкаГрупп = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

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

Индустриальные стандарты: Метод LexoRank

Проанализируем ситуацию, когда элементов в группе очень много и частая перезапись всех позиций (как в случае с 1, 2, 3...) создает очереди блокировок. В таких системах применяется алгоритм LexoRank. Вместо чисел используются строки (например, "a", "b", "c").

В среде 1С такой подход оправдан в высоконагруженных системах (например, в списках задач крупных проектов), где интерактивность важнее идеальной красоты индексов в базе данных.

Резюме и рекомендации

Для большинства типовых задач в 1С оптимальным будет сочетание математического поиска в запросе и точечного обновления объектов. Рассмотрим финальный чек-лист внедрения:

  1. Используйте сумму прогрессии для быстрого отсева корректных групп.
  2. Обязательно добавляйте проверку на целочисленность (например, через ВЫРАЗИТЬ(Позиция КАК Число(15,0)) = Позиция), если в вашей системе разрешен ввод дробных чисел для сортировки.
  3. При обновлении используйте блокировку данных, чтобы избежать ситуации, когда один процесс пересчитывает порядок, а другой в этот момент переносит элемент в другую группу.
  4. Для удобства пользователя можно реализовать подписку на событие ПередЗаписью, которая будет автоматически предлагать "красивый" номер позиции.

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

← На главную