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

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

При работе с запросами в 1С часто возникает задача: вывести список основных объектов (например, физических лиц) и дополнить их данными из табличных частей (например, сведениями об образовании или составе семьи). Однако сложность заключается в том, что в табличной части может быть много строк, а в отчет нужно вывести только одну — самую первую или актуальную. Попытка использовать конструкцию ПЕРВЫЕ 1 внутри пакета запросов часто приводит к ошибкам, когда вместо одной строки на каждого человека система выдает одну строку на весь запрос. В этой статье мы разберем, как правильно решить эту задачу, используя примеры из реальной практики разработчиков.

Почему конструкция ПЕРВЫЕ 1 не работает во временных таблицах

Рассмотрим типичную ошибку начинающего разработчика. Проанализируем ситуацию: когда мы пишем ВЫБРАТЬ ПЕРВЫЕ 1 во временной таблице, которая соединяется с основной таблицей, язык запросов 1С ограничивает весь результат этой временной таблицы одной записью. Если у нас в списке 100 сотрудников, то после такого запроса во временной таблице останется информация только по одному (случайному) сотруднику. Соединение в финальном запросе просто не найдет данных для остальных 99 человек.

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

Решение 1: Фильтрация по номеру строки

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

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


ВЫБРАТЬ
    ФизическиеЛица.Ссылка КАК ФизЛицо,
    ФизическиеЛица.ФИО
ПОМЕСТИТЬ ВТ_Сотрудники
ИЗ
    Справочник.ФизическиеЛица КАК ФизическиеЛица
ГДЕ
    ФизическиеЛица.ЭтоГруппа = ЛОЖЬ;

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

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_Сотрудники.ФИО,
    ВТ_ПервоеОбразование.ВидОбразования,
    ВТ_ПервоеОбразование.УчебноеЗаведение
ИЗ
    ВТ_Сотрудники КАК ВТ_Сотрудники
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ПервоеОбразование КАК ВТ_ПервоеОбразование
        ПО ВТ_Сотрудники.ФизЛицо = ВТ_ПервоеОбразование.Ссылка

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

Решение 2: Использование агрегатных функций и группировки

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

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

Проанализируем пример получения данных о семье с использованием этого подхода:


// Шаг 1: Получаем минимальный номер строки для каждого физлица
ВЫБРАТЬ
    СоставСемьи.Ссылка КАК ФизЛицо,
    МИНИМУМ(СоставСемьи.НомерСтроки) КАК НомерПервойСтроки
ПОМЕСТИТЬ ВТ_ИндексыСемьи
ИЗ
    Справочник.ФизическиеЛица.СоставСемьи КАК СоставСемьи
СГРУППИРОВАТЬ ПО
    СоставСемьи.Ссылка;

////////////////////////////////////////////////////////////////////////////////
// Шаг 2: Получаем данные конкретно по этим строкам
ВЫБРАТЬ
    СоставСемьи.Ссылка КАК Ссылка,
    СоставСемьи.СтепеньРодства,
    СоставСемьи.Имя
ПОМЕСТИТЬ ВТ_ДанныеСемьи
ИЗ
    ВТ_ИндексыСемьи КАК Индексы
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ФизическиеЛица.СоставСемьи КАК СоставСемьи
        ПО Индексы.ФизЛицо = СоставСемьи.Ссылка
            И Индексы.НомерПервойСтроки = СоставСемьи.НомерСтроки;

Этот метод считается "золотым стандартом" в разработке на 1С, так как он гарантирует получение ровно одной записи для каждого объекта, даже если в ТЧ сотни строк.

Типичные ошибки при объединении данных

Выясним причину, почему иногда запрос начинает выдавать огромное количество повторяющихся строк ("кашу"). Это происходит из-за декартова произведения. Если вы во временной таблице ВТ_Образование не ограничили количество строк до одной на сотрудника, а потом просто соединили таблицы, система выведет все возможные комбинации.

Никогда не делайте так:


ИЗ
    ВТ_Сотрудники КАК ВТ_Сотрудники,
    ВТ_Семья КАК ВТ_Семья,
    ВТ_Образ КАК ВТ_Образ

Такой синтаксис (через запятую) в 1С означает соединение "каждый с каждым". Всегда используйте ЛЕВОЕ СОЕДИНЕНИЕ с четким условием связи по Ссылке.

Оптимизация: Индексирование временных таблиц

Посмотрим на вопрос производительности. Если в справочнике "Физические лица" десятки тысяч записей, соединения временных таблиц будут работать медленно. Чтобы ускорить процесс, необходимо индексировать поля связи.

Добавим инструкцию индексации в наши временные таблицы:


ПОМЕСТИТЬ ВТ_ОбщееФизЛица
ИЗ
    Справочник.ФизическиеЛица КАК ФизическиеЛица
ГДЕ
    ...
ИНДЕКСИРОВАТЬ ПО
    ФИО // ФИО здесь - это поле Ссылка
;

Индексирование по полю связи позволяет СУБД мгновенно находить соответствия, что сокращает время выполнения запроса с нескольких минут до секунд.

Резюме

Подведем итоги. Чтобы вывести по одной записи из табличной части в запросе 1С, мы рекомендуем использовать следующий алгоритм:

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

Следуя этим правилам, вы сможете строить сложные и эффективные отчеты в таких конфигурациях, как ЗУП 2.5, УПП или ERP, где данные распределены по множеству вложенных таблиц.

← На главную