Как в запросе 1С выбрать записи с минимальным уровнем иерархии

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

В практике разработчика 1С часто возникает задача: из набора данных, где для одного объекта (например, номенклатуры) существует несколько строк, выбрать только одну, обладающую определенным признаком. В данном случае мы разберем ситуацию, когда необходимо получить записи с наименьшим числовым значением в поле УровеньИерархии. Такая задача актуальна при работе с иерархическими справочниками, расчете цен или анализе структуры каталога.

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

Метод 1. Использование временной таблицы и внутреннего соединения

Это классический и самый надежный метод, который работает на всех версиях платформы 1С:Предприятие 8. Суть метода заключается в разделении задачи на два этапа: получение минимальных ключей и получение полных данных по этим ключам.

Шаг 1. Создание временной таблицы с минимальными значениями

Сначала нам нужно выяснить для каждой номенклатуры, какой уровень иерархии является для нее минимальным в текущей выборке. Мы создадим временную таблицу ВТ_МинимальныеУровни. Проанализируем пример кода:


ВЫБРАТЬ
    МояТаблица.Номенклатура КАК Номенклатура,
    МИНИМУМ(МояТаблица.УровеньИерархии) КАК МинимальныйУровень
ПОМЕСТИТЬ ВТ_МинимальныеУровни
ИЗ
    &ИсходныеДанные КАК МояТаблица
СГРУППИРОВАТЬ ПО
    МояТаблица.Номенклатура
;

В этом запросе мы используем агрегатную функцию МИНИМУМ и обязательную группировку по полю Номенклатура. Это позволяет «схлопнуть» все записи по одному товару и оставить только информацию о том, на каком самом верхнем уровне он встречается.

Шаг 2. Получение итоговых данных

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


ВЫБРАТЬ
    МояТаблица.Номенклатура,
    МояТаблица.Лимит,
    МояТаблица.УровеньИерархии
ИЗ
    &ИсходныеДанные КАК МояТаблица
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_МинимальныеУровни КАК МинимальныеУровни
    ПО МояТаблица.Номенклатура = МинимальныеУровни.Номенклатура
        И МояТаблица.УровеньИерархии = МинимальныеУровни.МинимальныйУровень

Важный момент: Мы используем именно ВНУТРЕННЕЕ СОЕДИНЕНИЕ по двум полям. Это гарантирует, что в результат попадут только те строки из исходной таблицы, которые соответствуют найденным минимальным значениям.

Метод 2. Использование оконных функций (для платформы 8.3.20 и выше)

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

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


ВЫБРАТЬ
    Подзапрос.Номенклатура,
    Подзапрос.Лимит,
    Подзапрос.УровеньИерархии
ИЗ (
    ВЫБРАТЬ
        Т.Номенклатура,
        Т.Лимит,
        Т.УровеньИерархии,
        НОМЕР_СТРОКИ() НАД(ПАРТИЦИЯ ПО Т.Номенклатура УПОРЯДОЧИТЬ ПО Т.УровеньИерархии ВОЗР) КАК НомерЗаписи
    ИЗ
        &ИсходныеДанные КАК Т
) КАК Подзапрос
ГДЕ
    Подзапрос.НомерЗаписи = 1

В данном примере ПАРТИЦИЯ ПО определяет границы группы (для каждой номенклатуры своя нумерация), а УПОРЯДОЧИТЬ ПО задает правило, по которому мы определяем, кто будет первым. Этот метод работает быстрее и код выглядит значительно компактнее.

Решение проблемы дублей на одном уровне

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

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

Модифицируем первый этап (создание временной таблицы):


ВЫБРАТЬ
    МояТаблица.Номенклатура КАК Номенклатура,
    МИНИМУМ(МояТаблица.УровеньИерархии) КАК МинимальныйУровень,
    МИНИМУМ(МояТаблица.Лимит) КАК МинимальныйЛимит
ПОМЕСТИТЬ ВТ_УникальныеКлючи
ИЗ
    &ИсходныеДанные КАК МояТаблица
СГРУППИРОВАТЬ ПО
    МояТаблица.Номенклатура

Если же мы работаем со справочником, лучше всего группировать по Ссылка или МАКСИМУМ(Ссылка) внутри группы, чтобы гарантированно получить одно уникальное значение.

Оптимизация производительности

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

Рекомендация:

В конструкторе запроса на закладке «Индексы» укажите поля Номенклатура и УровеньИерархии для временной таблицы ВТ_МинимальныеУровни. Это позволит СУБД выполнить соединение мгновенно, используя эффективный алгоритм поиска, вместо полного сканирования таблиц.

Итоги

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

  1. Временные таблицы — универсальный способ, требующий аккуратности при наличии дублей на одном уровне.
  2. Оконные функции — современный и наиболее производительный вариант для новых систем.
  3. Группировка с вложенными запросами — возможна, но часто менее эффективна с точки зрения планировщика SQL.

Всегда проверяйте результат запроса на наличие «размножившихся» строк и используйте индексацию временных таблиц для обеспечения стабильной работы системы.

← На главную