При разработке сложных запросов в системе «1С:Предприятие» программисты часто сталкиваются с дилеммой: каким образом ограничить выборку из одной временной таблицы на основании данных другой. Классический сценарий подразумевает наличие двух таблиц — ВТ1 (фильтр) и ВТ2 (данные). Нам необходимо выбрать все поля из ВТ2, но только для тех записей, ссылки которых присутствуют в ВТ1.
Существует два основных подхода к решению этой задачи: использование ВНУТРЕННЕГО СОЕДИНЕНИЯ или использование оператора ГДЕ Ссылка В (ВЫБРАТЬ Ссылка ИЗ ВТ1). Разберем детально, какой из этих методов эффективнее, как на них реагирует оптимизатор СУБД и какие нюансы следует учитывать при работе с большими объемами данных.
Этот метод считается стандартным и наиболее прозрачным для разработчика. Мы явно указываем СУБД, как связать две таблицы. Рассмотрим пример кода:
ВЫБРАТЬ
ВТ2.Ссылка,
ВТ2.Наименование,
ВТ2.Сумма
ИЗ
ВТ2 КАК ВТ2
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ1 КАК ВТ1
ПО ВТ2.Ссылка = ВТ1.Ссылка
Проанализируем особенности этого подхода. Во-первых, при использовании соединения крайне важно, чтобы поле Ссылка в обеих таблицах было проиндексировано. Если временные таблицы содержат более нескольких сотен записей, отсутствие индексов заставит СУБД выполнять полное сканирование таблиц или строить хеш-таблицы в памяти, что существенно замедлит выполнение запроса.
Важный нюанс: семантика дублей. Проанализируем ситуацию, когда в таблице ВТ1 одна и та же ссылка встречается несколько раз. В случае ВНУТРЕННЕГО СОЕДИНЕНИЯ строки из ВТ2 будут размножены в итоговом результате ровно столько раз, сколько повторений этой ссылки найдено в ВТ1. Если ваша задача — просто отфильтровать данные, это может привести к логической ошибке (удвоению сумм, повтору строк). Чтобы избежать этого, перед соединением таблицу ВТ1 необходимо сгруппировать или выбрать только различные значения.
Этот метод часто называют полусоединением (Semi-join). С точки зрения языка запросов 1С это выглядит следующим образом:
ВЫБРАТЬ
ВТ2.Ссылка,
ВТ2.Наименование,
ВТ2.Сумма
ИЗ
ВТ2 КАК ВТ2
ГДЕ
ВТ2.Ссылка В (ВЫБРАТЬ ВТ1.Ссылка ИЗ ВТ1)
Рассмотрим, чем этот вариант отличается от предыдущего. Главное преимущество оператора В заключается в том, что он гарантирует уникальность строк результата. Даже если в ВТ1 миллион дублирующихся ссылок, каждая строка из ВТ2 попадет в выборку только один раз. Это избавляет от необходимости предварительной группировки таблицы-фильтра.
Существует распространенное заблуждение, что СУБД сначала выбирает все записи из основной таблицы, а потом фильтрует их. На самом деле современные оптимизаторы (MS SQL Server, PostgreSQL) используют технологию Predicate Pushdown (проталкивание предиката). Они способны преобразовать условие В во внутренний план выполнения, который будет работать так же эффективно, как и соединение.
Выясним причину, по которой один запрос может работать быстрее другого. В современных СУБД выбор физического метода выполнения (плана запроса) зависит не столько от того, написали вы JOIN или WHERE IN, сколько от накопленной статистики и наличия индексов.
Рассмотрим основные алгоритмы, которые применяет СУБД:
ВТ1 (фильтр) очень маленькая, а ВТ2 большая и проиндексирована. СУБД берет каждую запись из ВТ1 и мгновенно находит соответствие в ВТ2.Посмотрим на роль индексов во временных таблицах. В 1С временные таблицы физически создаются в базе данных (например, в tempdb для MS SQL). Использование конструкции ИНДЕКСИРОВАТЬ ПО для полей связи критически важно. Без индекса при объемах свыше 10-20 тысяч записей производительность упадет в геометрической прогрессии.
Проанализируем ситуацию, когда даже правильный запрос работает медленно. Одной из специфических проблем 1С является то, что SQL-сервер не всегда успевает обновить статистику по только что созданным временным таблицам. Оптимизатор может ошибочно решить, что в ВТ1 всего одна строка (хотя их там тысячи), и выбрать неэффективный план Nested Loops вместо Hash Match.
В новых версиях платформы 1С и современных СУБД эта проблема частично нивелируется механизмом отложенной компиляции запроса, но общая рекомендация остается прежней: старайтесь индексировать поля, по которым происходит связь или фильтрация.
Подведем итог нашему анализу и определим, что и в каких случаях использовать:
ВТ1) записи уникальны, используйте ВНУТРЕННЕЕ СОЕДИНЕНИЕ. Это считается «золотым стандартом» разработки в 1С, так как план такого запроса наиболее предсказуем для оптимизатора.Рассмотрим пример правильной организации запроса с индексацией:
// Создаем первую временную таблицу с индексом
МенеджерВТ.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ Ссылка ИЗ Документ.РеализацияТоваровУслуг ГДЕ ... ПОМЕСТИТЬ ВТ1 ИНДЕКСИРОВАТЬ ПО Ссылка";
// Выполняем основной запрос с соединением
МенеджерВТ.Текст =
"ВЫБРАТЬ ВТ2.* ИЗ ВТ2 КАК ВТ2 ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ1 КАК ВТ1 ПО ВТ2.Ссылка = ВТ1.Ссылка";
Таким образом, выбор между соединением и условием — это баланс между чистотой кода, контролем над дублями и помощью оптимизатору СУБД. В большинстве случаев в 1С предпочтительнее явное соединение по проиндексированным полям.