В процессе разработки часто возникает задача объединить данные из двух таблиц. Классический случай — когда нужно к основной таблице добавить информацию из связанной. Но что делать, если в связанной таблице для одной записи из основной таблицы находится несколько соответствий, а нам нужно выбрать только одно, причем всегда одно и то же?
Рассмотрим реальный пример: есть таблица А с перечнем материалов, числящихся за сотрудниками, и таблица Б с информацией о том, в каком цехе и на какой должности работает каждый сотрудник. Проблема в том, что один и тот же сотрудник может числиться в нескольких цехах (например, по основному месту работы и по совместительству). Наша задача — для каждой строки из таблицы А подставить какой-то один цех сотрудника, но сделать это консистентно: у одного и того же сотрудника во всех строках итоговой таблицы должен быть один и тот же цех.
Простое соединение таблиц (ЛЕВОЕ СОЕДИНЕНИЕ) в этом случае приведет к дублированию строк из таблицы А, что является некорректным результатом. Проанализируем несколько правильных подходов к решению этой задачи.
Наиболее прямолинейный способ — это подготовить данные из второй таблицы перед соединением. Идея заключается в том, чтобы сгруппировать данные по сотруднику и с помощью агрегатной функции выбрать одно-единственное значение цеха. Это гарантирует, что для каждого сотрудника у нас будет только одна строка, которую мы и присоединим к основной таблице.
В качестве агрегатной функции можно использовать МАКСИМУМ() или МИНИМУМ(). Какую именно выбрать — не так важно, если нет конкретных бизнес-требований. Главное, что результат их работы детерминирован: при каждом выполнении запроса для одного и того же набора данных они будут возвращать одно и то же значение. Например, МАКСИМУМ() для строковых полей выберет значение, которое идет последним в алфавитном порядке.
Посмотрим на пример запроса. Мы соединяем таблицу А не напрямую с таблицей Б, а с результатом вложенного запроса, который уже содержит уникальных сотрудников и один выбранный для них цех.
ВЫБРАТЬ
Таб_А.Сотр КАК Сотрудник,
Таб_А.Ном КАК Номенклатура,
ПодготовленнаяТабВ.Цех КАК Цех
ИЗ
Таб_А КАК Таб_А
ЛЕВОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ
Таб_В.Сотр КАК Сотр,
МАКСИМУМ(Таб_В.Цех) КАК Цех
ИЗ
Таб_В КАК Таб_В
СГРУППИРОВАТЬ ПО
Таб_В.Сотр) КАК ПодготовленнаяТабВ
ПО Таб_А.Сотр = ПодготовленнаяТабВ.Сотр
Разберем по шагам, что здесь происходит:
Таб_В, группирует все записи по полю Сотр и для каждой группы находит максимальное значение в поле Цех. На выходе мы получаем виртуальную таблицу с двумя колонками: Сотр и Цех, где каждый сотрудник встречается только один раз.Таб_А и соединяет ее левым соединением с результатами нашего вложенного запроса. Соединение происходит по полю Сотр.В итоге для каждой записи из Таб_А будет найдена ровно одна запись в подготовленных данных, и мы получим корректный результат без дублей.
Этот метод прост и понятен, отлично подходит для несложных запросов и небольших объемов данных. Однако в 1С есть более предпочтительный с точки зрения производительности способ.
Вложенные запросы, особенно при соединении, могут быть неоптимальными. Оптимизатор запросов платформы 1С не всегда может точно предсказать количество записей в результате подзапроса, что иногда приводит к построению неэффективного плана выполнения. Общепринятой лучшей практикой в 1С является использование временных таблиц.
Логика остается той же, но мы разбиваем запрос на два последовательных пакета:
Такой подход делает код более читаемым и позволяет платформе лучше оптимизировать каждый шаг.
Шаг 1: Создание временной таблицы с цехами сотрудников
В первом пакете запроса мы выполняем ту же группировку, что и во вложенном запросе, но результат помещаем во временную таблицу с помощью инструкции ПОМЕСТИТЬ.
ВЫБРАТЬ
Таб_В.Сотр КАК Сотрудник,
МАКСИМУМ(Таб_В.Цех) КАК Цех
ПОМЕСТИТЬ ВТ_ЦехаСотрудников
ИЗ
Таб_В КАК Таб_В
СГРУППИРОВАТЬ ПО
Таб_В.Сотрудник
;
Шаг 2: Соединение основной таблицы с временной
Во втором пакете запроса, который выполняется после первого, мы уже работаем с готовой временной таблицей ВТ_ЦехаСотрудников. Соединение с ней будет быстрым и эффективным.
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Таб_А.Сотр КАК Сотрудник,
Таб_А.Ном КАК Номенклатура,
ВТ_ЦехаСотрудников.Цех
ИЗ
Таб_А КАК Таб_А
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ЦехаСотрудников КАК ВТ_ЦехаСотрудников
ПО Таб_А.Сотр = ВТ_ЦехаСотрудников.Сотрудник
Преимущества этого подхода:
В ходе обсуждения на форуме был поднят важный вопрос о структуре данных. В конфигурациях 1С часто разделяют понятия "Сотрудник" и "Физическое лицо".
Если человек работает в одной организации и по основному месту, и по внутреннему совместительству, то в системе это будет одно физическое лицо, но два разных элемента в справочнике "Сотрудники". Каждый из этих элементов-сотрудников будет привязан к своему подразделению (цеху).
Поэтому, перед тем как писать запрос, критически важно понять, по какому полю происходит связь в ваших таблицах:
СправочникСсылка.Сотрудники, то описанная выше проблема может и не возникнуть, так как каждый "сотрудник-совместитель" — это уникальная запись со своим одним цехом. Простое соединение будет работать корректно.Сотрудник.Физлицо или напрямую по ссылке на справочник "Физические лица"), то у одного физлица действительно может быть несколько цехов, и для решения задачи необходимо использовать агрегацию, как было показано выше.Правильное понимание модели данных — ключ к построению корректных и эффективных запросов.