Как найти заказы, в которых отсутствует определенный вид упаковки или товар

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

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

Проанализируем ситуацию: если мы просто напишем условие ГДЕ Упаковка <> &Сумка, система выдаст нам все строки, где упаковка не является сумкой. Однако, если в заказе №1 есть и «Сумка», и «Пакет», строка с пакетом все равно попадет в выборку, хотя сам заказ условию «нет сумки» не соответствует. Разберем по шагам, как решить эту задачу правильно и эффективно.

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

Рассмотрим решение, которое предложил автор темы. Этот способ основан на математической логике: мы присваиваем каждой строке с искомым значением («Сумка») число 1, а всем остальным — 0. Если сумма этих чисел для всего заказа равна нулю, значит, искомого элемента в заказе нет.

Проанализируем этот алгоритм подробнее:

  1. Создаем вложенный запрос, в котором группируем данные по НомерЗаказа.
  2. Внутри группировки используем конструкцию ВЫБОР КОГДА.
  3. Суммируем результаты выбора.
  4. Во внешнем запросе фильтруем те группы, где сумма равна 0.

Пример кода реализации:


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

Метод 2: Оптимизация через секцию ИМЕЮЩИЕ (HAVING)

Разберем, как сделать запрос более изящным и производительным. В языке запросов 1С можно избежать создания вложенного запроса, применив секцию ИМЕЮЩИЕ. Это позволит платформе выполнить фильтрацию непосредственно в момент группировки данных.

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


ВЫБРАТЬ
    ТаблицаЗаказов.НомерЗаказа КАК НомерЗаказа
ИЗ
    Справочник.ТаблицаЗаказов КАК ТаблицаЗаказов
СГРУППИРОВАТЬ ПО
    ТаблицаЗаказов.НомерЗаказа
ИМЕЮЩИЕ
    МАКСИМУМ(ВЫБОР
            КОГДА ТаблицаЗаказов.Упаковка = &Упаковка
                ТОГДА 1
            ИНАЧЕ 0
        КОНЕЦ) = 0

В данном случае мы используем МАКСИМУМ вместо СУММА. Если в группе найдется хотя бы одна «Сумка», максимум станет равен 1, и заказ будет отсечен фильтром. Этот метод работает быстрее, так как не требует построения промежуточных таблиц во вложенных запросах.

Метод 3: Исключение через левое соединение (LEFT JOIN)

Выясним, какой способ считается наиболее профессиональным при работе с большими объемами данных. Метод «от противного» через ЛЕВОЕ СОЕДИНЕНИЕ часто выигрывает в производительности на SQL-серверах.

Логика работы следующая:

  1. Берем список всех уникальных заказов.
  2. Присоединяем к нему таблицу тех же заказов, но отфильтрованную только по упаковке «Сумка».
  3. Если соединение не удалось (в правой таблице образовался NULL), значит, сумки в заказе нет.

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


ВЫБРАТЬ РАЗЛИЧНЫЕ
    ВсеЗаказы.НомерЗаказа КАК НомерЗаказа
ИЗ
    Справочник.ТаблицаЗаказов КАК ВсеЗаказы
        ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ТаблицаЗаказов КАК ТолькоСумки
        ПО ВсеЗаказы.НомерЗаказа = ТолькоСумки.НомерЗаказа
            И ТолькоСумки.Упаковка = &Упаковка
ГДЕ
    ТолькоСумки.НомерЗаказа ЕСТЬ NULL

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

Метод 4: Использование конструкции НЕ В (NOT IN)

Это, пожалуй, самый интуитивно понятный способ. Мы просто просим систему: «Выбери все заказы, кроме тех, где сумка присутствует».


ВЫБРАТЬ РАЗЛИЧНЫЕ
    ТаблицаЗаказов.НомерЗаказа
ИЗ
    Справочник.ТаблицаЗаказов КАК ТаблицаЗаказов
ГДЕ
    НЕ ТаблицаЗаказов.НомерЗаказа В
            (ВЫБРАТЬ
                Т.НомерЗаказа
            ИЗ
                Справочник.ТаблицаЗаказов КАК Т
            ГДЕ
                Т.Упаковка = &Упаковка)

Проанализируем производительность этого метода: современные версии платформы 1С 8.3 эффективно транслируют НЕ В в SQL-команды. Однако на очень больших таблицах (миллионы строк) рекомендуется использовать временные таблицы для списка исключаемых заказов, чтобы оптимизатор SQL-сервера мог лучше построить план выполнения.

Советы по повышению производительности запросов

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

Проблема пустых заказов

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

Таким образом, мы разобрали четыре основных подхода к решению задачи. Выбор конкретного метода зависит от объема ваших данных и требований к читаемости кода. Для небольших баз отлично подходит метод с ИМЕЮЩИЕ, а для высоконагруженных систем — ЛЕВОЕ СОЕДИНЕНИЕ с проверкой на NULL.

← На главную