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

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

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

Суть проблемы и исходный код с ошибками

Рассмотрим ситуацию: есть табличная часть документа ЗаказПокупателя.Запасы. Для каждой строки нам нужно получить два свойства из дополнительных реквизитов характеристики номенклатуры: НашНомер и НомерКлиента. Первоначальная попытка решения часто выглядит как объединение двух подзапросов.

Проанализируем пример не совсем корректного запроса, который приводит к проблемам:


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

ОБЪЕДИНИТЬ

ВЫБРАТЬ
    ВТ.Номенклатура,
    ВТ.Характеристика,
    ВТ.Количество,
    ВТ.Примечание,
    "",
    ХарактеристикиНоменклатурыДополнительныеРеквизиты.Значение
ИЗ
    (ВЫБРАТЬ
        ЗаказПокупателяЗапасы.Номенклатура КАК Номенклатура,
        // ... (те же поля)
    ИЗ
        Документ.ЗаказПокупателя.Запасы КАК ЗаказПокупателяЗапасы
    ГДЕ
        ЗаказПокупателяЗапасы.Ссылка = &Ссылка) КАК ВТ
        ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК ХарактеристикиНоменклатурыДополнительныеРеквизиты
        ПО ВТ.Характеристика = ХарактеристикиНоменклатурыДополнительныеРеквизиты.Ссылка
ГДЕ
    ХарактеристикиНоменклатурыДополнительныеРеквизиты.Свойство = &Свойство1

Выясним причину, почему такой код плох. На выходе мы получаем задвоение таблицы: для одной и той же номенклатуры выводится две строки. В первой заполнена колонка НашНомер и пустая НомерКлиента, во второй — наоборот. Более того, есть грубая архитектурная ошибка: условие отбора ГДЕ ХарактеристикиНоменклатурыДополнительныеРеквизиты.Свойство = &Свойство написано в секции ГДЕ. Это автоматически превращает ЛЕВОЕ СОЕДИНЕНИЕ во ВНУТРЕННЕЕ. Если у характеристики свойство не заполнено, строка просто исчезнет из результата.

Решение через ПОЛНОЕ СОЕДИНЕНИЕ и временные таблицы (нерекомендуемое)

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


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

// Аналогично создается ВТНомерКлиента

ВЫБРАТЬ
    ВТНашНомер.Номенклатура КАК Номенклатура,
    ВТНашНомер.НашНомер КАК НашНомер,
    ВТНомерКлиента.НомерКлиента КАК НомерКлиента
ИЗ
    ВТНашНомер КАК ВТНашНомер
    ПОЛНОЕ СОЕДИНЕНИЕ ВТНомерКлиента КАК ВТНомерКлиента
    ПО ВТНашНомер.Номенклатура = ВТНомерКлиента.Номенклатура 
       И ВТНашНомер.Характеристика = ВТНомерКлиента.Характеристика
       // ... и другие поля связи

Такой подход будет работать, но мы настоятельно не рекомендуем его использовать. Полное соединение по множеству полей (особенно текстовых, таких как Примечание) — крайне ресурсоемкая операция. При наличии полностью одинаковых строк в документе, СУБД может вернуть непредсказуемое перемноженное количество записей.

Оптимальное решение 1: Использование нескольких левых соединений

Это самый лаконичный и быстрый способ. Вместо создания кучи временных таблиц, давайте сделаем несколько левых соединений прямо в основном запросе. Критически важный момент: условие отбора конкретного свойства нужно перенести из ГДЕ внутрь условия связи ПО.

Посмотрим на пример правильного запроса:


ВЫБРАТЬ
    ЗаказЗапасы.Номенклатура КАК Номенклатура,
    ЗаказЗапасы.Характеристика КАК Характеристика,
    ЗаказЗапасы.Количество КАК Количество,
    ЗаказЗапасы.Примечание КАК Примечание,
    ЕСТЬNULL(ДопРеквизитыНашНомер.Значение, "") КАК НашНомер,
    ЕСТЬNULL(ДопРеквизитыНомерКлиента.Значение, "") КАК НомерКлиента
ИЗ
    Документ.ЗаказПокупателя.Запасы КАК ЗаказЗапасы
    
    // Первое левое соединение для свойства "НашНомер"
    ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК ДопРеквизитыНашНомер
        ПО ЗаказЗапасы.Характеристика = ДопРеквизитыНашНомер.Ссылка
        И ДопРеквизитыНашНомер.Свойство = &Свойство
        
    // Второе левое соединение для свойства "НомерКлиента"
    ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК ДопРеквизитыНомерКлиента
        ПО ЗаказЗапасы.Характеристика = ДопРеквизитыНомерКлиента.Ссылка
        И ДопРеквизитыНомерКлиента.Свойство = &Свойство1
ГДЕ
    ЗаказЗапасы.Ссылка = &Ссылка

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

  1. Отсутствуют тяжелые временные таблицы и полные соединения.
  2. Если свойства нет, вернется пустая строка благодаря функции ЕСТЬNULL, а сама запись документа не пропадет.
  3. Код читается легко и отрабатывает максимально быстро на уровне СУБД.

Оптимальное решение 2: Объединение и свертка (МАКСИМУМ)

Иногда архитектура (например, программное формирование запроса) требует использования конструкции ОБЪЕДИНИТЬ ВСЕ. Чтобы при этом избежать дублей с пустыми ячейками, применим функцию МАКСИМУМ.

Логика проста: объединяем результаты во вложенном запросе, а затем группируем по основным полям. В агрегатных функциях берем максимум. Так как любая заполненная строка больше пустой (""), функция «схлопнет» данные в одну правильную строку.

Разберем подробнее код этого подхода:


ВЫБРАТЬ
    ВТ_Объединенная.Номенклатура КАК Номенклатура,
    ВТ_Объединенная.Характеристика КАК Характеристика,
    ВТ_Объединенная.Количество КАК Количество,
    ВТ_Объединенная.Примечание КАК Примечание,
    МАКСИМУМ(ВТ_Объединенная.НашНомер) КАК НашНомер,
    МАКСИМУМ(ВТ_Объединенная.НомерКлиента) КАК НомерКлиента
ИЗ
    (ВЫБРАТЬ
        ЗаказЗапасы.Номенклатура КАК Номенклатура,
        ЗаказЗапасы.Характеристика КАК Характеристика,
        ЗаказЗапасы.Количество КАК Количество,
        ЗаказЗапасы.Примечание КАК Примечание,
        ЕСТЬNULL(ДопРеквизиты.Значение, "") КАК НашНомер,
        "" КАК НомерКлиента
    ИЗ
        Документ.ЗаказПокупателя.Запасы КАК ЗаказЗапасы
        ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК ДопРеквизиты
            ПО ЗаказЗапасы.Характеристика = ДопРеквизиты.Ссылка
            И ДопРеквизиты.Свойство = &Свойство
    ГДЕ
        ЗаказЗапасы.Ссылка = &Ссылка

    ОБЪЕДИНИТЬ ВСЕ

    ВЫБРАТЬ
        ЗаказЗапасы.Номенклатура,
        ЗаказЗапасы.Характеристика,
        ЗаказЗапасы.Количество,
        ЗаказЗапасы.Примечание,
        "",
        ЕСТЬNULL(ДопРеквизиты.Значение, "")
    ИЗ
        Документ.ЗаказПокупателя.Запасы КАК ЗаказЗапасы
        ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК ДопРеквизиты
            ПО ЗаказЗапасы.Характеристика = ДопРеквизиты.Ссылка
            И ДопРеквизиты.Свойство = &Свойство1
    ГДЕ
        ЗаказЗапасы.Ссылка = &Ссылка) КАК ВТ_Объединенная

СГРУППИРОВАТЬ ПО
    ВТ_Объединенная.Номенклатура,
    ВТ_Объединенная.Характеристика,
    ВТ_Объединенная.Количество,
    ВТ_Объединенная.Примечание

Такой вариант избавляет нас от задвоения и при этом работает быстрее и безопаснее, чем полное соединение множества временных таблиц.

← На главную