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

Программист
← На главную

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

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

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

Решение 1: Вложенный запрос с агрегатной функцией

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

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

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


ВЫБРАТЬ
    Таб_А.Сотр КАК Сотрудник,
    Таб_А.Ном КАК Номенклатура,
    ПодготовленнаяТабВ.Цех КАК Цех
ИЗ
    Таб_А КАК Таб_А
        ЛЕВОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ
            Таб_В.Сотр КАК Сотр,
            МАКСИМУМ(Таб_В.Цех) КАК Цех
        ИЗ
            Таб_В КАК Таб_В
        СГРУППИРОВАТЬ ПО
            Таб_В.Сотр) КАК ПодготовленнаяТабВ
        ПО Таб_А.Сотр = ПодготовленнаяТабВ.Сотр

Разберем по шагам, что здесь происходит:

  1. Вложенный запрос (подзапрос): Сначала выполняется часть в скобках. Он обращается к таблице Таб_В, группирует все записи по полю Сотр и для каждой группы находит максимальное значение в поле Цех. На выходе мы получаем виртуальную таблицу с двумя колонками: Сотр и Цех, где каждый сотрудник встречается только один раз.
  2. Основной запрос: Далее выполняется основная часть запроса. Он берет таблицу Таб_А и соединяет ее левым соединением с результатами нашего вложенного запроса. Соединение происходит по полю Сотр.

В итоге для каждой записи из Таб_А будет найдена ровно одна запись в подготовленных данных, и мы получим корректный результат без дублей.

Этот метод прост и понятен, отлично подходит для несложных запросов и небольших объемов данных. Однако в 1С есть более предпочтительный с точки зрения производительности способ.

Решение 2: Использование временных таблиц

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

Логика остается той же, но мы разбиваем запрос на два последовательных пакета:

  1. Сначала создаем и заполняем временную таблицу с подготовленными данными.
  2. Затем в отдельном запросе соединяем основную таблицу с этой временной таблицей.

Такой подход делает код более читаемым и позволяет платформе лучше оптимизировать каждый шаг.

Шаг 1: Создание временной таблицы с цехами сотрудников

В первом пакете запроса мы выполняем ту же группировку, что и во вложенном запросе, но результат помещаем во временную таблицу с помощью инструкции ПОМЕСТИТЬ.


ВЫБРАТЬ
    Таб_В.Сотр КАК Сотрудник,
    МАКСИМУМ(Таб_В.Цех) КАК Цех
ПОМЕСТИТЬ ВТ_ЦехаСотрудников
ИЗ
    Таб_В КАК Таб_В
СГРУППИРОВАТЬ ПО
    Таб_В.Сотрудник
;

Шаг 2: Соединение основной таблицы с временной

Во втором пакете запроса, который выполняется после первого, мы уже работаем с готовой временной таблицей ВТ_ЦехаСотрудников. Соединение с ней будет быстрым и эффективным.


////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Таб_А.Сотр КАК Сотрудник,
    Таб_А.Ном КАК Номенклатура,
    ВТ_ЦехаСотрудников.Цех
ИЗ
    Таб_А КАК Таб_А
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ЦехаСотрудников КАК ВТ_ЦехаСотрудников
        ПО Таб_А.Сотр = ВТ_ЦехаСотрудников.Сотрудник

Преимущества этого подхода:

Важный нюанс: Сотрудник или Физическое лицо?

В ходе обсуждения на форуме был поднят важный вопрос о структуре данных. В конфигурациях 1С часто разделяют понятия "Сотрудник" и "Физическое лицо".

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

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

Правильное понимание модели данных — ключ к построению корректных и эффективных запросов.

← На главную