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

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

При разработке на платформе 1С часто возникает необходимость получить данные из регистра сведений таким образом, чтобы одно из измерений (например, ИЗМ_1) встречалось в результате строго один раз, а значения других полей брались из первой попавшейся (или любой другой) записи. Рассмотрим подробнее, как правильно составить такой запрос, выясним причину частых ошибок и разберем по шагам несколько вариантов решения этой задачи — от классических до самых современных.

Почему не работает оператор РАЗЛИЧНЫЕ

Многие начинающие разработчики пытаются решить эту проблему, используя операцию ВЫБРАТЬ РАЗЛИЧНЫЕ. Проанализируем ситуацию: почему этот метод не дает нужного результата? Архитектурная особенность языка запросов 1С (как и классического SQL) такова, что оператор РАЗЛИЧНЫЕ оценивает уникальность не по одному какому-то выбранному полю, а по комбинации всех полей, указанных в секции выборки.

Если значение ИЗМ_1 в двух строках одинаковое, а ИЗМ_2 разное, то для системы эти строки в целом считаются разными, и дубли не отсекутся. Нам нужен принципиально иной подход.

Классическое решение: Группировка и агрегатные функции

Самый простой и распространенный метод для выборки всего двух полей — использование группировки. Посмотрим на пример, где мы группируем записи по целевому измерению и применяем функцию МИНИМУМ (или МАКСИМУМ) к остальным полям.


ВЫБРАТЬ
    ВашРегистр.ИЗМ_1 КАК ИЗМ_1,
    МИНИМУМ(ВашРегистр.ИЗМ_2) КАК ИЗМ_2
ИЗ
    РегистрСведений.ВашРегистр КАК ВашРегистр

СГРУППИРОВАТЬ ПО
    ВашРегистр.ИЗМ_1

В этом случае платформа соберет все строки с одинаковым ИЗМ_1 в одну группу, а для поля ИЗМ_2 выберет минимальное значение из этой группы. Проблема решена одним компактным запросом!

Проблема «строки-Франкенштейна»

Разберем ситуацию, когда нам необходимо вытащить не только ИЗМ_1 и ИЗМ_2, но и другие поля (например, ИЗМ_3 или ресурсы), причем эти поля должны относиться строго к одной и той же физической строке регистра.

Если мы просто продолжим добавлять агрегатные функции (например, МИНИМУМ(ИЗМ_2) и МАКСИМУМ(ИЗМ_3)), то запрос может взять значение второго измерения из первой строки регистра, а значение третьего — из второй. В итоге мы получим несуществующую, "слепленную" строку-Франкенштейна. Чтобы этого избежать, применяют другие методы.

Надежное решение: Самосоединение таблиц

Чтобы достать целую физическую строку со всеми реквизитами для каждого уникального ИЗМ_1, традиционно используют внутреннее соединение таблиц. Проанализируем этот алгоритм:

  1. Первым шагом (через вложенный запрос или временную таблицу) мы делаем группировку по ИЗМ_1 с получением МАКСИМУМ(ИЗМ_2).
  2. Вторым шагом мы внутренне соединяем исходную таблицу регистра с результатом первого запроса, используя совпадение сразу по обоим полям.

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


ВЫБРАТЬ
    Исходный.ИЗМ_1 КАК ИЗМ_1,
    Исходный.ИЗМ_2 КАК ИЗМ_2,
    Исходный.ИЗМ_3 КАК ИЗМ_3
ИЗ
    РегистрСведений.ВашРегистр КАК Исходный
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ (ВЫБРАТЬ
            Т.ИЗМ_1 КАК ИЗМ_1,
            МАКСИМУМ(Т.ИЗМ_2) КАК ИЗМ_2
        ИЗ
            РегистрСведений.ВашРегистр КАК Т
        СГРУППИРОВАТЬ ПО
            Т.ИЗМ_1) КАК ВложенныйЗапрос
        ПО Исходный.ИЗМ_1 = ВложенныйЗапрос.ИЗМ_1
            И Исходный.ИЗМ_2 = ВложенныйЗапрос.ИЗМ_2

Этот способ надежно отсекает дубли и позволяет «вытянуть» все остальные поля строго из реально существующей уникальной записи.

Современное решение: Оконные функции (Платформа 8.3.20+)

Если ваша конфигурация работает на платформе 1С версии 8.3.20 и выше, задачу можно решить максимально элегантно, вообще избегая сложных соединений и группировок. Воспользуемся оконными функциями. Функция АВТОНОМЕРЗАПИСИ() с секционированием присвоит порядковые номера строкам внутри каждого отдельного значения.

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


ВЫБРАТЬ
    Т.ИЗМ_1 КАК ИЗМ_1,
    Т.ИЗМ_2 КАК ИЗМ_2,
    Т.ИЗМ_3 КАК ИЗМ_3,
    АВТОНОМЕРЗАПИСИ() ОКОННАЯФУНКЦИЯ (РАЗБИЕНИЕ ПО Т.ИЗМ_1 УПОРЯДОЧИТЬ ПО Т.ИЗМ_2) КАК НомерСтроки
ПОМЕСТИТЬ ВТ_Пронумерованные
ИЗ
    РегистрСведений.ВашРегистр КАК Т
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ.ИЗМ_1 КАК ИЗМ_1,
    ВТ.ИЗМ_2 КАК ИЗМ_2,
    ВТ.ИЗМ_3 КАК ИЗМ_3
ИЗ
    ВТ_Пронумерованные КАК ВТ
ГДЕ
    ВТ.НомерСтроки = 1

Важное преимущество: Оконные функции разделяют данные по полю ИЗМ_1 (конструкция РАЗБИЕНИЕ ПО) и нумеруют строки. Оставив условие ГДЕ НомерСтроки = 1, мы мгновенно получаем первую попавшуюся запись со всеми нужными полями.

Альтернатива: Коррелированные запросы

Существует и еще один вариант — использование коррелированных запросов, где выборка идет из таблицы, а в условии проверяется вхождение комбинации полей в результат подзапроса ВЫБРАТЬ ПЕРВЫЕ 1. Однако из-за высоких нагрузок на СУБД (так как подзапрос выполняется для каждой строки основной выборки), разработчики 1С настоятельно рекомендуют избегать такого подхода.

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

← На главную