В среде разработчиков 1С часто возникают споры о том, какой способ фильтрации данных эффективнее: использование ВНУТРЕННЕЕ СОЕДИНЕНИЕ с временной таблицей или оператор В (IN) с подзапросом. На первый взгляд может показаться, что результат идентичен, однако с точки зрения теории СУБД, работы платформы 1С и итоговой производительности между ними есть существенная разница. Рассмотрим этот вопрос детально, разберем поведение системы "под капотом" и определим лучшие практики индексации.
Прежде чем переходить к быстродействию, проанализируем фундаментальное различие в логике этих операций. Разберем ситуацию, когда во временной таблице ВТ содержатся неуникальные записи по полю Контрагент.
ВТ одному контрагенту соответствует две и более записи, то в результирующей выборке строки из таблицы Документ.ПоступлениеДенег будут продублированы (размножены) столько раз, сколько совпадений найдено в правой таблице.SEMI JOIN. Она лишь проверяет наличие ссылки в подзапросе. Даже если в ВТ контрагент встречается 100 раз, в итоговый результат строка документа попадет только один раз.Таким образом, если ваша задача — просто отфильтровать документы по списку, оператор В безопаснее, так как он гарантирует отсутствие дублей. Если же вы используете соединение, вам необходимо быть уверенными в уникальности записей во временной таблице или использовать ключевое слово РАЗЛИЧНЫЕ.
Проанализируем, как эти конструкции интерпретируются на уровне СУБД (например, MS SQL Server). В современных версиях платформы 1С 8.3 оптимизатор запросов СУБД часто строит похожие планы для обоих вариантов, но есть нюансы:
Рассмотрим вариант с оператором В:
ВЫБРАТЬ
ПоступлениеДенег.СуммаПоступления
ИЗ
Документ.ПоступлениеДенег КАК ПоступлениеДенег
ГДЕ
ПоступлениеДенег.Контрагент В (ВЫБРАТЬ ВТ.Контрагент ИЗ ВТ)
СУБД чаще всего преобразует такой запрос в Left Semi Join. Особенность этого оператора в том, что сканирование правой таблицы (нашей ВТ) прекращается сразу же, как только найдено первое совпадение для текущей строки основной таблицы. Это может дать выигрыш в скорости, если правая таблица велика и не индексирована.
Теперь посмотрим на вариант с ВНУТРЕННЕЕ СОЕДИНЕНИЕ:
ВЫБРАТЬ
ПоступлениеДенег.СуммаПоступления
ИЗ
Документ.ПоступлениеДенег КАК ПоступлениеДенег
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ КАК ВТ
ПО ПоступлениеДенег.Контрагент = ВТ.Контрагент
Здесь СУБД вынуждена выполнять полноценное сопоставление. Если оптимизатор видит, что поле Контрагент в документе не индексировано, он, скорее всего, выберет алгоритм Hash Match или Merge Join (если обе таблицы отсортированы). При отсутствии индекса на основной таблице СУБД в любом случае выполнит Table Scan (полное сканирование таблицы документов), поэтому радикальной разницы в "секундах" на средних объемах данных вы можете не заметить.
В обсуждении часто упоминается, что индексировать временные таблицы (ВТ) не нужно. Это миф, требующий уточнения. Выясним причину, почему 1С рекомендует индексировать ВТ при соединениях:
Статистика — это ключевой фактор. У временных таблиц в SQL Server нет актуальной статистики распределения данных. Создавая индекс в 1С командой ИНДЕКСИРОВАТЬ ПО, мы фактически заставляем СУБД построить индекс в tempdb. Для оптимизатора SQL наличие индекса является мощным сигналом о типе данных и их количестве. Это помогает ему правильно выбрать "ведущую" таблицу в соединении.
Рассмотрим рекомендации по индексации:
Nested Loops при огромном количестве итераций), что приведет к катастрофическому падению производительности.Важный момент: если поле Контрагент в основной таблице (документе) не индексировано, то индекс на маленькой временной таблице при внутреннем соединении почти не даст эффекта, так как СУБД все равно придется читать всю таблицу документов целиком.
Стоит упомянуть ситуацию, когда вместо временной таблицы используется параметр-массив: ГДЕ Контрагент В (&Массив). Выясним, как ведет себя платформа:
Это сделано для оптимизации передачи данных на сервер СУБД и предотвращения ошибок, связанных с ограничением длины запроса.
Проанализировав теорию и практику, мы можем сформулировать следующие правила для разработчика:
1. Предпочтение Соединениям. В стандартах разработки 1С принято использовать ВНУТРЕННЕЕ СОЕДИНЕНИЕ вместо В (ВЫБРАТЬ...). Это делает запрос более прозрачным для отладки и анализа в консоли запросов. Однако помните о риске дублирования строк.
2. Контроль вложенных запросов. Старайтесь избегать вложенных запросов непосредственно в условии ГДЕ. Лучше сначала выгрузить данные во временную таблицу, проиндексировать её (если данных много) и затем соединять. Это позволяет СУБД строить более стабильные планы выполнения.
3. Правило "Курилки": Если вы спорите о быстродействии, всегда смотрите план запроса. Без анализа плана на конкретных данных (объем таблицы документов, селективность данных контрагентов) спор остается чисто теоретическим. Если поле в большой таблице не индексировано — это всегда будет "узким местом", независимо от того, используете вы JOIN или IN.
4. Отладка. Помните, что запросы с ВНУТРЕННЕЕ СОЕДИНЕНИЕ легче визуализировать. Использование оператора В с подзапросом может скрыть от разработчика тот факт, что подзапрос выполняется неэффективно или возвращает избыточные данные.
В заключение отметим: если ваша база данных работает на PostgreSQL, разница может быть более ощутимой. Postgres в старых версиях (до 12) иногда хуже справлялся с оптимизацией IN подзапросов по сравнению с MS SQL, поэтому для кросс-платформенных решений JOIN с предварительно подготовленной временной таблицей является более надежным и предсказуемым выбором.