В языке запросов 1С существует мощная, но не всегда очевидная конструкция, позволяющая сравнивать сразу несколько полей (кортеж) с результатом вложенного запроса. Синтаксис вида (Таблица1.А, Таблица1.Б) В (ВЫБРАТЬ Таблица2.А, Таблица2.Б...) часто вызывает вопросы у разработчиков относительно того, как именно СУБД обрабатывает это условие и какие «подводные камни» могут встретиться при его использовании. В этой статье мы подробно разберем механизм работы этого оператора, проанализируем вопросы производительности и рассмотрим лучшие практики написания таких запросов.
Давайте разберем, как платформа 1С интерпретирует запись, где несколько полей заключаются в скобки и проверяются на вхождение в результат подзапроса. С точки зрения логики, это условие проверяет существование в источнике (подзапросе) такой строки, где значения всех перечисленных полей одновременно совпадают со значениями из основной таблицы.
Рассмотрим пример из обсуждения. Если у нас есть условие:
(Таблица1.ПолеА, Таблица1.ПолеБ) В
(ВЫБРАТЬ
Таблица2.ПолеХ,
Таблица2.ПолеY
ИЗ
Таблица2 КАК Таблица2)
Для каждой строки из Таблица1 система будет пытаться найти хотя бы одно соответствие в Таблица2. Важно понимать, что порядок полей в скобках должен строго соответствовать порядку полей в списке выбора подзапроса. То есть ПолеА будет сравниваться с ПолеХ, а ПолеБ — с ПолеY.
Проанализируем ситуацию с технической точки зрения: фактически это условие можно представить как скрытый EXISTS. Как верно отмечено в сообществе, это будет эквивалентно следующей логике:
ГДЕ 1 В (
ВЫБРАТЬ ПЕРВЫЕ 1
ИЗ Таблица2
ГДЕ Таблица2.ПолеХ = Таблица1.ПолеА
И Таблица2.ПолеY = Таблица1.ПолеБ
)
Выясним причину, по которой это условие может работать по-разному в зависимости от используемой базы данных. Платформа 1С является кроссплатформенной, и её генератор SQL-кода адаптирует запросы под конкретную СУБД:
WHERE (f1, f2) IN (SELECT ...), которая выполняется весьма эффективно.IN нет. Поэтому платформа 1С чаще всего трансформирует такое условие в оператор EXISTS с коррелированным подзапросом, внутри которого прописываются условия равенства по всем полям.Посмотрим на пример трансформации для SQL Server. Условие превратится в нечто подобное:
WHERE EXISTS (
SELECT 1
FROM Table2
WHERE Table2.FieldX = Table1.FieldA
AND Table2.FieldY = Table1.FieldB
)
Один из самых критичных моментов, который мы должны рассмотреть, — это обработка значений NULL. В языке SQL сравнение любого значения с NULL (даже NULL = NULL) дает результат UNKNOWN (неизвестно), что в логических условиях трактуется как ложь.
Важно: Если в вашей основной таблице (Таблица1) хотя бы в одном из полей кортежа содержится NULL, то вся проверка В для этой строки гарантированно вернет «Ложь», даже если в подзапросе (Таблица3) есть точно такая же строка с NULL. Это происходит потому, что оператор В под капотом использует сравнение через =. Если вам необходимо учитывать пустые значения, их следует предварительно обрабатывать функцией ЕСТЬNULL(Поле, ЗначениеЗамены).
Разберем по шагам, что нужно сделать для обеспечения высокой скорости выполнения таких запросов. Когда мы используем подзапрос внутри условия В, оптимизатор СУБД должен быстро находить соответствия. Без правильных индексов система будет выполнять полное сканирование таблицы (Table Scan), что на миллионах строк приведет к зависанию системы.
Для эффективной работы конструкции (А, Б) В (...) в Таблица3 (источник данных подзапроса) крайне желательно наличие составного индекса по полям А и Б. Порядок полей в индексе должен совпадать с порядком полей в условии запроса. Если индекс будет только по одному полю или порядок полей будет обратным, эффективность поиска может значительно снизиться.
Также проанализируем ситуацию с временными таблицами. В профессиональной разработке на 1С использование вложенных подзапросов внутри условий ГДЕ часто считается «антипаттерном». Рекомендуется следовать следующему алгоритму:
Таблица3 во временную таблицу.А и Б.ВНУТРЕННЕЕ СОЕДИНЕНИЕ (INNER JOIN) вместо оператора В.Как справедливо заметили участники обсуждения, использование кортежей в скобках через запятую — (А, Б) В (...) — может усложнять чтение кода другими разработчиками. Рассмотрим альтернативный и более прозрачный вариант написания того же самого запроса через соединение:
ВЫБРАТЬ
Таблица1.ПолеА,
Таблица1.ПолеБ
ИЗ
Таблица1 КАК Таблица1
ГДЕ
(Таблица1.ПолеА, Таблица1.ПолеБ) В (ВЫБРАТЬ Т3.А, Т3.Б ИЗ Таблица3 КАК Т3)
Теперь сравним это с использованием соединения, которое выполняет ту же задачу (при условии отсутствия дублей в Таблица3):
ВЫБРАТЬ
Таблица1.ПолеА,
Таблица1.ПолеБ
ИЗ
Таблица1 КАК Таблица1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Таблица3 КАК Таблица3
ПО Таблица1.ПолеА = Таблица3.А
И Таблица1.ПолеБ = Таблица3.Б
Вариант с соединением считается более «каноническим» для 1С по нескольким причинам:
ВНУТРЕННЕЕ СОЕДИНЕНИЕ на ЛЕВОЕ СОЕДИНЕНИЕ для поиска записей, не попавших в выборку.Несмотря на то, что платформа 1С позволяет включать в кортеж множество полей, не стоит этим злоупотреблять. При использовании более 3-4 полей в одном условии В результирующий SQL-запрос становится крайне тяжелым для оптимизатора СУБД. Если вам нужно сопоставлять данные по 5 и более полям, это явный признак того, что структуру данных или логику запроса стоит пересмотреть, возможно, используя предварительную подготовку уникальных идентификаторов строк.
Резюме: Конструкция сопоставления полей в скобках — это удобный инструмент для простых фильтраций «на лету», но для высоконагруженных участков кода и сложных отчетов всегда лучше отдавать предпочтение временным таблицам с индексами и явным соединениям.