В работе программиста платформы 1С:Предприятие часто возникает задача фильтрации данных по принципу исключений. Ситуации, когда необходимо получить список элементов из одного набора, которые не имеют аналогов в другом наборе, встречаются повсеместно. Это может быть поиск товаров без остатков, контрагентов без долгов или документов, которые еще не были проведены в смежной системе.
Неправильный выбор механизма отсечения данных может привести к катастрофическому падению производительности запроса, особенно на больших объемах информации. В этой статье мы детально разберем три основных подхода к решению этой задачи в языке запросов 1С. Мы рассмотрим их синтаксис, логику работы и влияние на скорость выполнения.
Логическое отрицание НЕ в языке запросов
Самый интуитивно понятный и часто используемый способ — применение логического отрицания НЕ в условии соединения или в секции ГДЕ. Синтаксически это выглядит как попытка найти строки, где поле левой таблицы не равно полю правой таблицы при определенном условии. Этот метод удобен для простых сценариев, но имеет свои подводные камни.
При использовании оператора НЕ важно понимать, что он работает строго по строкам. Если в правой таблице (таблице исключения) для одного элемента из левой таблицы существует несколько записей, логика может сработать некорректно или привести к дублированию результатов, если не использовать РАЗЛИЧНЫЕ. Кроме того, часто возникает путаница с обработкой пустых значений NULL.
Если в поле правой таблицы стоит NULL, то сравнение ПОЛЕ1 НЕ = ПОЛЕ2 вернет Неопределено, что в условии ГДЕ трактуется как ЛОЖЬ. Это означает, что строки с пустыми ссылками могут быть ошибочно отфильтрованы. Для корректной работы часто приходится добавлять явную проверку на пустоту, усложняя запрос.
⚠️ Внимание: При использовании отрицания НЕ убедитесь, что индексы в базе данных покрывают поля соединения. В противном случае СУБД может выполнить полный скан таблиц, что критично замедлит выборку на миллионах записей.
Для оптимизации стоит помнить, что сервер 1С преобразует такие запросы в специфический план выполнения. В некоторых версиях платформы использование НЕ СУЩЕСТВУЕТ (подзапрос) работает быстрее, чем прямое отрицание в условии соединения, особенно если правая таблица значительно больше левой.
Используйте конструкцию "НЕ СУЩЕСТВУЕТ (ВЫБРАТЬ 1...)" вместо простого "НЕ" в условиях соединения для сложных запросов с большими таблицами.
Механизм ЛЕВОЕ СОЕДИНЕНИЕ и отбор пустых ссылок
Альтернативный и часто более производительный подход — использование ЛЕВОЕ СОЕДИНЕНИЕ. Суть метода заключается в том, что мы берем все записи из основной (левой) таблицы и присоединяем к ним данные из таблицы исключений. Ключевой момент: мы отбираем только те строки, где поля правой таблицы остались пустыми после соединения.
Этот метод считается "классическим" для задач типа "найти отсутствующие записи". Он позволяет серверу баз данных оптимизировать выполнение запроса, используя стандартные алгоритмы хэш-соединений или вложенных циклов. Синтаксически это выглядит как соединение по ключу с последующим условием ГДЕ ПраваяТаблица.Ссылка ЕСТЬ NULL.
Преимущество подхода в том, что он явно указывает оптимизатору запросов на необходимость найти unmatched rows (несопоставленные строки). В отличие от отрицания, здесь нет двусмысленности в обработке логики: если соединения не произошло, значит, записи во второй таблице нет.
- 📊 Метод отлично масштабируется на больших объемах данных благодаря планам выполнения СУБД.
- 🔍 Позволяет легко выводить дополнительные поля из левой таблицы без риска дублирования.
- 🛡️ Более предсказуем в работе с индексами и статистикой распределения данных.
Однако стоит учитывать, что при использовании ЛЕВОЕ СОЕДИНЕНИЕ необходимо внимательно следить за условиями отбора. Если вы поместите условие по полям правой таблицы в секцию ГДЕ, оно может превратить внешнее соединение во внутреннее, и результат будет пустым. Такие условия должны быть в секции ГДЕ только для проверки на NULL.
Использование подзапросов с оператором СУЩЕСТВУЕТ
Третий мощный инструмент — использование конструкции НЕ СУЩЕСТВУЕТ с вложенным подзапросом. Этот подход наиболее гибок и позволяет формулировать сложные условия наличия или отсутствия записей. Запрос читается почти как естественный язык: "Выбрать товары, для которых не существует записей в таблице остатков".
Синтаксически подзапрос размещается внутри условия ГДЕ основной выборки. Сервер 1С выполняет подзапрос для каждой строки основного набора (или использует оптимизацию semi-join), проверяя условие существования. Это позволяет использовать сложные фильтры внутри подзапроса, которые трудно реализовать через обычное соединение.
Важной особенностью является то, что подзапрос может возвращать любые поля, но обычно используют ВЫБРАТЬ 1 или ВЫБРАТЬ NULL, так как нам важен сам факт наличия строки, а не её содержимое. Это снижает объем передаваемых данных между уровнями выполнения запроса.
ВЫБРАТЬ
Номенклатура.Ссылка
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
НЕ СУЩЕСТВУЕТ (
ВЫБРАТЬ
РегистрНакопления.ТоварыНаСкладах.Номенклатура
ИЗ
РегистрНакопления.ТоварыНаСкладах КАК ТоварыНаСкладах
ГДЕ
ТоварыНаСкладах.Номенклатура = Номенклатура.Ссылка
)
Использование СУЩЕСТВУЕТ часто дает выигрыш в производительности, если правая таблица очень большая, но подходящих записей в ней мало. Оптимизатор может прервать выполнение подзапроса сразу после нахождения первой подходящей строки, не сканируя всю таблицу до конца.
⚠️ Внимание: Избегайте использования коррелированных подзапросов внутри циклов программиста (в коде 1С). Всегда старайтесь переносить логику фильтрации на уровень запроса к базе данных.
Сравнительный анализ производительности методов
Выбор конкретного метода зависит от структуры данных, объема выборок и версии платформы 1С. Не существует универсального решения, которое работало бы быстрее всех в 100% случаев. Однако есть общие закономерности, выявленные в ходе нагрузочного тестирования на реальных базах.
Для небольших таблиц (до нескольких тысяч записей) разница во времени выполнения между методами обычно незаметна для пользователя. В таких случаях стоит руководствоваться читаемостью кода. Для больших информационных баз (миллионы записей) разница может составлять секунды и даже минуты.
| Метод | Читаемость | Производительность (Большие данные) | Сложность отладки |
|---|---|---|---|
| ЛЕВОЕ СОЕДИНЕНИЕ | Высокая | Оптимальная | Низкая |
| НЕ СУЩЕСТВУЕТ | Средняя | Высокая (зависит от индексов) | Средняя |
| Оператор НЕ | Высокая | Средняя/Низкая | Высокая (риск NULL) |
| Временные таблицы | Низкая | Зависит от реализации | Высокая |
Стоит отметить, что в современных версиях платформы 1С (начиная с 8.3.10 и выше) оптимизатор запросов научился эффективно трансформировать одни конструкции в другие. Часто запрос с НЕ СУЩЕСТВУЕТ internally преобразуется в план выполнения, аналогичный ЛЕВОМУ СОЕДИНЕНИЮ.
Влияние версии платформы на оптимизацию
В версиях 1С ниже 8.3.8 оптимизатор запросов работал менее эффективно, и ручная оптимизация через временные таблицы давала ощутимый прирост скорости. В актуальных релизах лучше полагаться на встроенную оптимизацию.
Работа с временными таблицами для сложных выборок
В случаях, когда логика выборки становится слишком запутанной или требуется многоступенчатая фильтрация, целесообразно использовать временные таблицы. Этот подход предполагает разбиение сложного запроса на несколько этапов с сохранением промежуточных результатов в памяти сервера.
Сначала мы выбираем данные из основной таблицы во временный набор. Затем выбираем данные из таблицы исключений в другой временный набор. После этого выполняем операцию РАЗЛИЧИЯ (EXCEPT) или соединяем эти таблицы с отбором. Это делает код более модульным и понятным для поддержки.
Использование временных таблиц особенно оправдано, если данные для выборки формируются динамически или требуют предварительной агрегации. Однако этот метод создает дополнительную нагрузку на оперативную память сервера 1С и может увеличивать время выполнения из-за накладных расходов на создание и уничтожение временных объектов.
- 💾 Подходит для сложных отчетов с множеством условий фильтрации.
- 🧩 Упрощает чтение и поддержку кода другими разработчиками.
- 🚀 Позволяет кэшировать промежуточные результаты при повторных вызовах.
Не забывайте очищать временные таблицы, если они создаются в цикле или в долгоживущих сеансах, чтобы избежать переполнения памяти. В большинстве случаев достаточно области видимости одного запроса.
Временные таблицы — это инструмент для структурирования сложной логики, а не всегда способ ускорения. Используйте их, когда читаемость важнее микро-оптимизации.
Обработка пустых значений и особенности NULL
Одной из самых частых ошибок при выборе записей, которых нет в другой таблице, является некорректная обработка NULL. В реляционных базах данных NULL означает "неизвестное значение", и оно не равно ничему, даже другому NULL. Это фундаментальное правило трехзначной логики.
Если вы используете соединение по полю, которое может быть пустым, убедитесь, что ваша логика учитывает это. Например, при поиске документов без привязки к договору, условие Документ.Договор = NULL никогда не выполнится. Необходимо использовать конструкцию Документ.Договор ЕСТЬ NULL.
При использовании оператора НЕ ситуация еще тоньше. Выражение НЕ (Поле1 = Поле2) вернет ИСТИНА, только если оба поля заполнены и не равны. Если одно из полей NULL, результат будет НЕОПРЕДЕЛЕНО, и строка будет отброшена. Это часто приводит к потере данных, которые программист ожидал увидеть в выборке.
⚠️ Внимание: Всегда проверяйте поведение вашего запроса на тестовой базе с данными, содержащими пустые ссылки. Логика работы с
NULLможет отличаться в разных СУБД (MSSQL, PostgreSQL, Oracle), используемых под 1С.
Для надежной работы рекомендуется явно приводить пустые значения к какому-либо фиктивному идентификатору или использовать функцию ЕСТЬNULL в тех случаях, когда семантика задачи это позволяет, хотя в задачах поиска отсутствующих записей это применяется редко.
☑️ Чек-лист оптимизации запроса
FAQ: Часто задаваемые вопросы
Почему запрос с ЛЕВЫМ СОЕДИНЕНИЕМ работает медленнее, чем с НЕ?
Это может происходить, если оптимизатор СУБД неверно оценивает селективность выборки или если отсутствуют статистические данные по таблицам. Также возможно, что в условии ГДЕ ошибочно добавлены фильтры по правой таблице, превращающие соединение во внутреннее.
Можно ли использовать оператор ПОДОБНО для поиска отсутствующих записей?
Технически можно, но это крайне не рекомендуется для больших объемов данных. Оператор ПОДОБНО (LIKE) отключает использование индексов по началу строки (в большинстве случаев) и заставляет базу перебирать каждую запись, что приведет к полному сканированию таблицы.
Как найти записи, которые есть в обеих таблицах?
Для этого используется ВНУТРЕННЕЕ СОЕДИНЕНИЕ или просто СОЕДИНЕНИЕ. Оно вернет только те строки, для которых нашлось соответствие в обеих таблицах по указанному условию связи.
Влияет ли тип соединения на блокировки в базе данных?
Да, тип соединения и условия отбора влияют на план выполнения, который определяет, какие индексы и страницы данных будут прочитаны. Чтение больших объемов данных может создавать разделяемые блокировки, которые в редких случаях могут конфликтовать с транзакциями записи, особенно при уровне изоляции, отличном от "Read Committed".