В практике разработчика 1С часто возникает задача, которая кажется тривиальной на первый взгляд, но требует особого подхода при реализации в языке запросов. Типичный пример: нужно выбрать из базы данных только те заказы, в которых среди множества строк полностью отсутствует определенный признак (например, нет упаковки типа «Сумка»).
Проанализируем ситуацию: если мы просто напишем условие ГДЕ Упаковка <> &Сумка, система выдаст нам все строки, где упаковка не является сумкой. Однако, если в заказе №1 есть и «Сумка», и «Пакет», строка с пакетом все равно попадет в выборку, хотя сам заказ условию «нет сумки» не соответствует. Разберем по шагам, как решить эту задачу правильно и эффективно.
Рассмотрим решение, которое предложил автор темы. Этот способ основан на математической логике: мы присваиваем каждой строке с искомым значением («Сумка») число 1, а всем остальным — 0. Если сумма этих чисел для всего заказа равна нулю, значит, искомого элемента в заказе нет.
Проанализируем этот алгоритм подробнее:
НомерЗаказа.ВЫБОР КОГДА.Пример кода реализации:
ВЫБРАТЬ
Влож.Заказ КАК НомерЗаказа
ИЗ
(ВЫБРАТЬ
ТаблицаЗаказов.НомерЗаказа КАК Заказ,
СУММА(ВЫБОР
КОГДА ТаблицаЗаказов.Упаковка = &Упаковка
ТОГДА 1
ИНАЧЕ 0
КОНЕЦ) КАК Количество
ИЗ
Справочник.ТаблицаЗаказов КАК ТаблицаЗаказов
СГРУППИРОВАТЬ ПО
ТаблицаЗаказов.НомерЗаказа) КАК Влож
ГДЕ
Влож.Количество = 0
Разберем, как сделать запрос более изящным и производительным. В языке запросов 1С можно избежать создания вложенного запроса, применив секцию ИМЕЮЩИЕ. Это позволит платформе выполнить фильтрацию непосредственно в момент группировки данных.
Посмотрим на пример оптимизированного кода:
ВЫБРАТЬ
ТаблицаЗаказов.НомерЗаказа КАК НомерЗаказа
ИЗ
Справочник.ТаблицаЗаказов КАК ТаблицаЗаказов
СГРУППИРОВАТЬ ПО
ТаблицаЗаказов.НомерЗаказа
ИМЕЮЩИЕ
МАКСИМУМ(ВЫБОР
КОГДА ТаблицаЗаказов.Упаковка = &Упаковка
ТОГДА 1
ИНАЧЕ 0
КОНЕЦ) = 0
В данном случае мы используем МАКСИМУМ вместо СУММА. Если в группе найдется хотя бы одна «Сумка», максимум станет равен 1, и заказ будет отсечен фильтром. Этот метод работает быстрее, так как не требует построения промежуточных таблиц во вложенных запросах.
Выясним, какой способ считается наиболее профессиональным при работе с большими объемами данных. Метод «от противного» через ЛЕВОЕ СОЕДИНЕНИЕ часто выигрывает в производительности на SQL-серверах.
Логика работы следующая:
NULL), значит, сумки в заказе нет.Рассмотрим пример кода:
ВЫБРАТЬ РАЗЛИЧНЫЕ
ВсеЗаказы.НомерЗаказа КАК НомерЗаказа
ИЗ
Справочник.ТаблицаЗаказов КАК ВсеЗаказы
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ТаблицаЗаказов КАК ТолькоСумки
ПО ВсеЗаказы.НомерЗаказа = ТолькоСумки.НомерЗаказа
И ТолькоСумки.Упаковка = &Упаковка
ГДЕ
ТолькоСумки.НомерЗаказа ЕСТЬ NULL
Важный нюанс: условие по конкретной упаковке (&Упаковка) необходимо ставить именно в условии связи ПО, а не в секции ГДЕ. Это критически важно для корректной работы левого соединения.
Это, пожалуй, самый интуитивно понятный способ. Мы просто просим систему: «Выбери все заказы, кроме тех, где сумка присутствует».
ВЫБРАТЬ РАЗЛИЧНЫЕ
ТаблицаЗаказов.НомерЗаказа
ИЗ
Справочник.ТаблицаЗаказов КАК ТаблицаЗаказов
ГДЕ
НЕ ТаблицаЗаказов.НомерЗаказа В
(ВЫБРАТЬ
Т.НомерЗаказа
ИЗ
Справочник.ТаблицаЗаказов КАК Т
ГДЕ
Т.Упаковка = &Упаковка)
Проанализируем производительность этого метода: современные версии платформы 1С 8.3 эффективно транслируют НЕ В в SQL-команды. Однако на очень больших таблицах (миллионы строк) рекомендуется использовать временные таблицы для списка исключаемых заказов, чтобы оптимизатор SQL-сервера мог лучше построить план выполнения.
Чтобы ваш запрос работал мгновенно даже при большом количестве заказов, следует придерживаться нескольких правил:
НомерЗаказа и Упаковка должны быть проиндексированы. Если это реквизиты табличной части, проверьте свойство «Индексировать» в метаданных.Упаковка = &Упаковка, где параметром передается ссылка на элемент справочника, работает в разы быстрее, чем поиск по текстовому наименованию "Сумка".МенеджерВременныхТаблиц), а затем выполняйте исключение. Это значительно снижает нагрузку на дисковую подсистему сервера.Рассмотрим важную деталь, которую легко упустить. Если в вашей системе существуют заказы, в которых вообще нет ни одной строки в табличной части (пустые заказы), должны ли они попасть в результат?
Если вы используете таблицу строк заказов (как в примерах выше), пустые заказы не попадут в выборку, так как информации о них просто нет в этой таблице. Если же ваша цель — найти абсолютно все заказы без сумок, включая пустые, то в качестве основной таблицы для левого соединения нужно брать документ (Документ.ЗаказПокупателя), а не его табличную часть.
Таким образом, мы разобрали четыре основных подхода к решению задачи. Выбор конкретного метода зависит от объема ваших данных и требований к читаемости кода. Для небольших баз отлично подходит метод с ИМЕЮЩИЕ, а для высоконагруженных систем — ЛЕВОЕ СОЕДИНЕНИЕ с проверкой на NULL.