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