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

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

В процессе разработки на платформе 1С:Предприятие программисты часто сталкиваются с ситуацией, когда при соединении двух таблиц в запросе возникает "размножение" строк. Это происходит, если одному ключу связи в первой таблице соответствует несколько записей во второй. Классический пример: нам нужно получить остаток товара и только одну (например, самую свежую или самую крупную) цену из регистра цен. Если просто использовать ЛЕВОЕ СОЕДИНЕНИЕ, в результат попадут все найденные цены, и количество строк исходной таблицы увеличится.

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

Способ 1: Использование группировки и агрегатных функций

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

Рассмотрим ситуацию: у нас есть Таблица1 со списком номенклатуры и Таблица2 с ценами. Для каждой позиции номенклатуры нам нужно вытащить только максимальную цену.

Выполним следующие шаги:

  1. Создадим вложенный запрос ко второй таблице.
  2. Сгруппируем данные по полю связи (например, номенклатура).
  3. Применим агрегатную функцию МАКСИМУМ к полю, которое нам нужно получить.
  4. Соединим основной запрос с этим вложенным запросом.

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


ВЫБРАТЬ
    Таб1.Поле1 КАК Поле1,
    ВложенныйЗапрос.ПолеТаб2 КАК МаксимальноеЗначение
ИЗ
    Таб1 КАК Таб1
        ЛЕВОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ
            Таб2.Поле1 КАК Поле1,
            МАКСИМУМ(Таб2.ПолеТаб2) КАК ПолеТаб2
        ИЗ
            Таб2 КАК Таб2
        СГРУППИРОВАТЬ ПО
            Таб2.Поле1) КАК ВложенныйЗапрос
        ПО Таб1.Поле1 = ВложенныйЗапрос.Поле1

Этот метод эффективен, когда нам нужно получить только одно числовое или датированное значение. Однако у него есть ограничение: если из второй таблицы нужно вытащить несколько полей (например, и цену, и ответственного, который ее установил), простая группировка не позволит это сделать без потери связи между данными.

Способ 2: Оконные функции (для платформы 8.3.20 и выше)

Современные версии платформы 1С 8.3.20+ предоставляют мощный инструмент — оконные функции. Они позволяют пронумеровать строки внутри каждой группы данных прямо в запросе. Это идеальное решение задачи "первой записи".

Разберем алгоритм решения через ПОРЯДКОВЫЙ_НОМЕР():

  1. Во второй таблице мы создаем вычисляемое поле с порядковым номером.
  2. Используем конструкцию ПЕРЕГОРОДКА ПО (PARTITION BY) для группировки по ключу связи.
  3. Указываем УПОРЯДОЧИТЬ ПО для определения того, какая запись будет считаться первой (например, по убыванию даты).
  4. Во внешнем запросе просто накладываем условие, что номер строки должен быть равен 1.

Проанализируем структуру такого запроса:


ВЫБРАТЬ
    Т1.Ссылка,
    Т2.Цена,
    Т2.Сотрудник
ИЗ
    Справочник.Номенклатура КАК Т1
        ЛЕВОЕ СОЕДИНЕНИЕ (
            ВЫБРАТЬ
                Цены.Номенклатура,
                Цены.Цена,
                Цены.Сотрудник,
                ПОРЯДКОВЫЙ_НОМЕР() OVER (PARTITION BY Цены.Номенклатура УПОРЯДОЧИТЬ ПО Цены.Период УБЫВ) КАК НомерСтроки
            ИЗ
                РегистрСведений.ЦеныНоменклатуры КАК Цены
        ) КАК Т2
        ПО Т1.Ссылка = Т2.Номенклатура И Т2.НомерСтроки = 1

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

Способ 3: Двойное соединение через временные таблицы

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

Разберем по шагам:

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

Пример реализации:


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

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

Важный нюанс: Если в одну и ту же секунду (период) были установлены две разные цены, этот запрос все равно вернет две строки. Чтобы этого избежать, в группировку и в условия связи нужно добавлять дополнительное уникальное поле, например, УникальныйИдентификатор или НомерСтроки.

Способ 4: Использование виртуальных таблиц

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

Рассмотрим пример:


ВЫБРАТЬ
    Номенклатура.Ссылка,
    ЦеныСрез.Цена
ИЗ
    Справочник.Номенклатура КАК Номенклатура
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, ) КАК ЦеныСрез
        ПО Номенклатура.Ссылка = ЦеныСрез.Номенклатура

Это самый быстрый и рекомендуемый способ при работе с регистрами сведений, так как расчет среза выполняется эффективно на уровне СУБД.

Рекомендации по оптимизации

Выбирая метод сопоставления первой записи, следует помнить о производительности:

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

← На главную