Как в запросе 1С узнать порядковый номер элемента массива и проверить последовательность строк?

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

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

Ограничения использования массивов в языке запросов платформы 1С

Рассмотрим природу передачи параметров типа Массив в язык запросов 1С. В платформе массив в запросе допустимо применять исключительно как список значений для оператора вхождения: ГДЕ Поле В (&Массив).

Передать массив в конструкцию ИЗ или обращаться к нему как к источнику записей со строками синтаксис языка не позволяет. Соответственно, у элементов массива внутри текста запроса принципиально отсутствуют такие атрибуты, как индекс, номер строки или порядковый номер выборки. Реляционная СУБД воспринимает переданный массив просто как неупорядоченное множество значений.

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

Способ 1. Передача типизированной таблицы значений с номерами строк

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

Посмотрим на пример подготовки таблицы и передачи её во временную таблицу запроса:


// Создаем таблицу значений с явной типизацией колонок
ТаблицаДляЗапроса = Новый ТаблицаЗначений;
ТаблицаДляЗапроса.Колонки.Добавить("Товар", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТаблицаДляЗапроса.Колонки.Добавить("ПорядковыйНомер", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(10, 0)));

// Заполняем таблицу элементами с сохранением их индекса
Для Индекс = 0 По ИсходныйМассив.Количество() - 1 Цикл
    НоваяСтрока = ТаблицаДляЗапроса.Добавить();
    НоваяСтрока.Товар = ИсходныйМассив[Индекс];
    НоваяСтрока.ПорядковыйНомер = Индекс + 1;
КонецЦикла;

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

Запрос.УстановитьПараметр("ТаблицаДляЗапроса", ТаблицаДляЗапроса);
Запрос.УстановитьПараметр("СсылкаНаДокумент", СсылкаНаДокумент);
Результат = Запрос.Выполнить().Выгрузить();

В этом случае мы получаем полноценную возможность соединять таблицы по номенклатуре и сравнивать НомерСтрокиТЧ и переданный ПорядковыйНомер.

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

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

Часто в регистре сведений порядковые номера идут с разрывами (например: 8, 22, 43, 992). Для того чтобы получить непрерывный ряд натуральных чисел (1, 2, 3...) без пропусков, используем классический прием соединения выборки самой с собой по неравенству:


ВЫБРАТЬ
    Регистр1.Товар КАК Товар,
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Регистр2.ПорядковыйНомер) КАК НепрерывныйНомер
ПОМЕСТИТЬ ВТ_РангРегистра
ИЗ
    РегистрСведений.ПорядокТоваров КАК Регистр1
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ПорядокТоваров КАК Регистр2
        ПО Регистр1.ПорядковыйНомер >= Регистр2.ПорядковыйНомер
СГРУППИРОВАТЬ ПО
    Регистр1.Товар
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ЕСТЬNULL(ТЧ.Товар, ВТ.Товар) КАК Товар,
    ТЧ.НомерСтроки КАК НомерСтрокиТЧ,
    ВТ.НепрерывныйНомер КАК РангРегистра
ИЗ
    Документ.ЗаказКлиента.Товары КАК ТЧ
        ПОЛНОЕ СОЕДИНЕНИЕ ВТ_РангРегистра КАК ВТ
        ПО ТЧ.Товар = ВТ.Товар
ГДЕ
    ТЧ.Ссылка = &Ссылка
    И (ТЧ.НомерСтроки <> ВТ.НепрерывныйНомер 
        ИЛИ ТЧ.Товар ЕСТЬ NULL 
        ИЛИ ВТ.Товар ЕСТЬ NULL)

Разберем, как работает этот запрос:

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

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

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

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


// Допустим, ТабличнаяЧасть - строки документа, МассивСтарогоПорядка - упорядоченный список ссылок
КоличествоТЧ = ТабличнаяЧасть.Количество();
КоличествоМассива = МассивСтарогоПорядка.Количество();

Если КоличествоТЧ <> КоличествоМассива Тогда
    // Количество элементов отличается, порядок уже нарушен
    ПоследовательностьНарушена = Истина;
Иначе
    ПоследовательностьНарушена = Ложь;
    Для Номер = 0 По КоличествоТЧ - 1 Цикл
        Если ТабличнаяЧасть[Номер].Товар <> МассивСтарогоПорядка[Номер] Тогда
            ПоследовательностьНарушена = Истина;
            Прервать;
        КонецЕсли;
    КонецЦикла;
КонецЕсли;

Этот способ работает за линейное время $O(N)$, минимально нагружает оперативную память и не создает лишней нагрузки на сервер базы данных.

← На главную