Как вставить запрос в конструкцию ВЫБОР в 1С? Разбираем ограничения синтаксиса и оптимальные решения

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

При написании сложных запросов в системе «1С:Предприятие 8» разработчики часто сталкиваются с необходимостью динамического вычисления данных на основе условий. Первое интуитивное решение, которое приходит в голову программистам с опытом работы в классических СУБД (например, MS SQL Server или PostgreSQL) — это вставить вложенный подзапрос напрямую внутрь конструкции ВЫБОР. Однако при попытке сделать это в 1С платформа выдает ошибку синтаксиса.

В рамках данной статьи мы подробно разберем, почему стандартный подход со скалярными подзапросами не работает в языке запросов 1С, в каких исключительных случаях вложенные запросы все-таки можно использовать внутри ВЫБОР, и как правильно решать подобные прикладные задачи с использованием временных таблиц и объединений.

1. Почему нельзя напрямую вставить подзапрос в секции ТОГДА или ИНАЧЕ

В классическом SQL существует понятие скалярного подзапроса (scalar subquery) — это запрос в списке выбора (секции SELECT), который возвращает ровно одну строку и одну колонку. СУБД умеет обрабатывать такие конструкции «на лету» для каждой строки выборки.

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


// ВНИМАНИЕ: Данный код вызовет синтаксическую ошибку!
ВЫБРАТЬ
    Статьи.Ссылка КАК Статья,
    ВЫБОР
        КОГДА Статьи.Ссылка = ЗНАЧЕНИЕ(Справочник.СтатьиДвиженияДенежныхСредств.ВыдачаДенежныхСредствКассеККМ)
            ТОГДА (ВЫБРАТЬ СУММА(Обороты.СуммаОборот) ИЗ РегистрНакопления.ДвиженияДенежныхСредств.Обороты КАК Обороты)
        ИНАЧЕ 0
    КОНЕЦ КАК Сумма
ИЗ
    Справочник.СтатьиДвиженияДенежныхСредств КАК Статьи

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

2. Где в конструкции ВЫБОР подзапросы всё-таки разрешены?

Несмотря на жесткие ограничения, в языке запросов 1С существует единственное легитимное место для использования вложенных запросов внутри оператора ВЫБОР — это секция условия КОГДА. Но использовать их там можно только в связке с оператором проверки вхождения В (ВЫБРАТЬ...).

Рассмотрим два наиболее частых сценария использования этого механизма.

Сценарий А. Проверка вхождения элемента во временную таблицу или выборку

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


ВЫБРАТЬ
    СправочникНоменклатура.Ссылка КАК Товар,
    ВЫБОР
        КОГДА СправочникНоменклатура.Ссылка В (
            ВЫБРАТЬ
                ВТ_АкционныеТовары.Номенклатура
            ИЗ
                ВТ_АкционныеТовары КАК ВТ_АкционныеТовары
        ) ТОГДА ИСТИНА
        ИНАЧЕ ЛОЖЬ
    КОНЕЦ КАК ЭтоАкционныйТовар
ИЗ
    Справочник.Номенклатура КАК СправочникНоменклатура

Сценарий Б. Эмуляция отсутствующего оператора EXISTS

В языке запросов 1С нет прямого аналога SQL-оператора EXISTS, который проверяет наличие связанных записей в подчиненных таблицах. Однако мы можем элегантно сэмулировать его поведение с помощью связки 1 В (ВЫБРАТЬ ПЕРВЫЕ 1 1...) в условии КОГДА:


ВЫБРАТЬ
    ДокументРеализация.Ссылка КАК ДокументСсылка,
    ДокументРеализация.Контрагент КАК Контрагент,
    ВЫБОР
        КОГДА 1 В (
            ВЫБРАТЬ ПЕРВЫЕ 1
                1
            ИЗ
                Документ.СчетФактураВыданный КАК СФ
            ГДЕ
                СФ.ДокументОснование = ДокументРеализация.Ссылка
        ) ТОГДА ИСТИНА
        ИНАЧЕ ЛОЖЬ
    КОНЕЦ КАК ЕстьСчетФактура
ИЗ
    Документ.РеализацияТоваровУслуг КАК ДокументРеализация

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

3. Как правильно решить задачу автора темы?

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

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

Вариант А. Использование временной таблицы и левого соединения (LEFT JOIN)

Это наиболее структурированный и понятный способ. Вместо попыток рассчитать сумму «на лету» внутри условия, мы выносим расчет суммы по «дополнительным» статьям в отдельный предварительный шаг — во временную таблицу.

Разберем это решение по шагам:

  1. Создаем временную таблицу (ВТ): В первом запросе пакета мы выбираем и суммируем обороты только по тем статьям ДДС, которые должны наполнять нашу целевую статью. Группируем данные в разрезе необходимых аналитик (например, торговых точек или касс).
  2. Индексируем временную таблицу: Для обеспечения максимальной скорости соединения обязательно индексируем ВТ по ключевым полям связи.
  3. Соединяем таблицы в основном запросе: Во втором запросе пакета выполняем левое соединение основной таблицы оборотов с нашей временной таблицей.
  4. Применяем конструкцию ВЫБОР: Теперь в секции ВЫБРАТЬ мы можем использовать простую условную конструкцию, подставляя значение из соединенной таблицы при совпадении целевой статьи.

Посмотрим на пример реализации данного алгоритма:


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

ИНДЕКСИРОВАТЬ ПО
    ТорговаяТочка
;

// ШАГ 2: Основной запрос пакета
ВЫБРАТЬ
    СтатьиДДС.Ссылка КАК СтатьяДвиженияДенежныхСредств,
    ДвиженияОборотыОсновной.БанковскийСчетКасса.ТорговаяТочка КАК БанковскийСчетКассаТорговаяТочка,
    ВЫБОР
        КОГДА СтатьиДДС.Ссылка = ЗНАЧЕНИЕ(Справочник.СтатьиДвиженияДенежныхСредств.ВыдачаДенежныхСредствКассеККМ)
            ТОГДА ЕСТЬNULL(ВТ_Доп.СуммаОборотДоп, 0)
        ИНАЧЕ ЕСТЬNULL(ДвиженияОборотыОсновной.СуммаОборот, 0)
    КОНЕЦ КАК СуммаОборот
ИЗ
    Справочник.СтатьиДвиженияДенежныхСредств КАК СтатьиДДС
    
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ДвиженияДенежныхСредств.Обороты(&НачалоПериода, &КонецПериода, Авто, ) КАК ДвиженияОборотыОсновной
        ПО СтатьиДДС.Ссылка = ДвиженияОборотыОсновной.СтатьяДвиженияДенежныхСредств
        
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_СуммыДополнительныхСтатей КАК ВТ_Доп
        ПО ДвиженияОборотыОсновной.БанковскийСчетКасса.ТорговаяТочка = ВТ_Доп.ТорговаяТочка

Вариант Б. Схема с ОБЪЕДИНИТЬ ВСЕ и последующей группировкой

Этот прием часто оказывается более производительным, чем левое соединение, так как он избавляет СУБД от необходимости строить сложные планы соединений для больших объемов данных. Мы разбиваем выборку на две независимые ветки, объединяем их результаты и «схлопываем» при помощи итоговой группировки.

Рассмотрим данный подход подробнее:

  1. Первая ветка объединения: Выбираем обычные движения по всем статьям. При этом для нашей целевой статьи ВыдачаДенежныхСредствКассеККМ мы принудительно возвращаем оборот, равный 0, чтобы ее собственные движения не задвоились.
  2. Вторая ветка объединения: Выбираем данные по списку тех самых «дополнительных» статей, чьи суммы мы хотим приплюсовать. Но в поле статьи ДДС мы искусственно подставляем значение нашей целевой статьи.
  3. Финальная группировка: Помещаем объединенный результат во вложенный запрос или во временную таблицу и группируем по полям статьи и аналитики. Функция СУММА() автоматически сложит нулевое значение целевой статьи и подтянутые из второй ветки обороты дополнительных статей.

Посмотрим на практическую реализацию этого метода:


ВЫБРАТЬ
    ВыборкаОборотов.СтатьяДДС КАК СтатьяДвиженияДенежныхСредств,
    ВыборкаОборотов.ТорговаяТочка КАК БанковскийСчетКассаТорговаяТочка,
    СУММА(ВыборкаОборотов.Сумма) КАК СуммаОборот
ИЗ
    (
        // Часть 1: Выбираем все стандартные обороты (для целевой статьи принудительно ставим 0)
        ВЫБРАТЬ
            ОборотыОсновной.СтатьяДвиженияДенежныхСредств КАК СтатьяДДС,
            ОборотыОсновной.БанковскийСчетКасса.ТорговаяТочка КАК ТорговаяТочка,
            ВЫБОР
                КОГДА ОборотыОсновной.СтатьяДвиженияДенежныхСредств = ЗНАЧЕНИЕ(Справочник.СтатьиДвиженияДенежныхСредств.ВыдачаДенежныхСредствКассеККМ)
                    ТОГДА 0
                ИНАЧЕ ОборотыОсновной.СуммаОборот
            КОНЕЦ КАК Сумма
        ИЗ
            РегистрНакопления.ДвиженияДенежныхСредств.Обороты(&НачалоПериода, &КонецПериода, Авто, ) КАК ОборотыОсновной

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

        // Часть 2: Выбираем обороты по доп. статьям и принудительно сопоставляем их целевой статье
        ВЫБРАТЬ
            ЗНАЧЕНИЕ(Справочник.СтатьиДвиженияДенежныхСредств.ВыдачаДенежныхСредствКассеККМ) КАК СтатьяДДС,
            ОборотыДополнительные.БанковскийСчетКасса.ТорговаяТочка КАК ТорговаяТочка,
            ОборотыДополнительные.СуммаОборот КАК Сумма
        ИЗ
            РегистрНакопления.ДвиженияДенежныхСредств.Обороты(
                &НачалоПериода, 
                &КонецПериода, 
                Авто, 
                СтатьяДвиженияДенежныхСредств В (&МассивДопСтатей)
            ) КАК ОборотыДополнительные
    ) КАК ВыборкаОборотов

СГРУППИРОВАТЬ ПО
    ВыборкаОборотов.СтатьяДДС,
    ВыборкаОборотов.ТорговаяТочка

4. Рекомендации по оптимизации производительности

При работе со сложными выборками и агрегацией данных крайне важно соблюдать стандарты разработки «1С», чтобы ваши отчеты не вызывали перегрузки сервера СУБД и работали быстро даже на больших базах данных:

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

← На главную