Самостоятельное соединение по первичному ключу

9

Рассмотрим этот запрос, который состоит из Nсамостоятельных соединений:

select
    t1.*
from [Table] as t1
join [Table] as t2 on
    t1.Id = t2.Id
-- ...
join [Table] as tN on
    t1.Id = tN.Id

Он создает план выполнения с N сканированием кластеризованного индекса и N-1 объединениями слиянием.

Честно говоря, я не вижу причин не оптимизировать все объединения и выполнить только одно сканирование кластерного индекса, то есть оптимизировать исходный запрос для этого:

select
    t1.*
from [Table] as t1

Вопросов

  • Почему объединения не оптимизированы?
  • С математической точки зрения неверно говорить, что каждое объединение не меняет набор результатов?

Проверено на:

  • Версия исходного сервера: SQL Server 2014 (12.0.4213)
  • Исходный выпуск Database Engine Edition: Microsoft SQL Server Standard Edition
  • Тип ядра СУБД: автономный SQL Server
  • Уровень совместимости: SQL Server 2008 (100)

Запрос не имеет смысла; это просто пришло мне в голову, и мне сейчас интересно.

Вот скрипка с созданием таблицы и 3 запроса: с inner join's, с left join' и смешанный. Вы также можете посмотреть план выполнения там.

Кажется, что left joins исключены в плане выполнения результата, а inner joins нет. Тем не менее, не понимаю почему .

pkuderov
источник

Ответы:

18

Сначала предположим, что (id)это первичный ключ таблицы. В этом случае, да, объединения являются (могут быть доказаны) избыточными и могут быть исключены.

Теперь это просто теория - или математика. Для того, чтобы оптимизатор выполнил фактическое исключение, теория должна быть преобразована в код и добавлена ​​в набор оптимизаторов / переписываний / исключений оптимизатора. Чтобы это произошло, разработчики (СУБД) должны подумать, что это будет иметь хорошие преимущества для эффективности и что это достаточно распространенный случай.

Лично это не похоже на один (достаточно распространенный). Запрос - как вы признаете - выглядит довольно глупо, и рецензент не должен позволять ему проходить проверку, если только он не был улучшен и избыточное соединение удалено.

Тем не менее, есть похожие запросы, где устранение происходит. Роб Фарли написал очень хороший пост в блоге: упрощение JOIN в SQL Server .

В нашем случае все, что нам нужно сделать, это изменить соединения на LEFTобъединения. Смотрите dbfiddle.uk . Оптимизатор в этом случае знает, что соединение может быть безопасно удалено без возможного изменения результатов. (Логика упрощения довольно общая и не предназначена для самостоятельных соединений.)

Конечно, в исходном запросе удаление INNERобъединений также не может изменить результаты. Но самоприсоединение по первичному ключу вообще не является обычным, поэтому в оптимизаторе этот случай не реализован. Однако обычно объединение (или левое соединение) выполняется, когда столбец объединения является первичным ключом одной из таблиц (и часто существует ограничение внешнего ключа). Что приводит ко второму варианту устранения объединений: добавьте (внешняя ссылка!) Ограничение внешнего ключа:

ALTER TABLE "Table"
    ADD FOREIGN KEY (id) REFERENCES "Table" (id) ;

И вуаля, соединения устранены! (проверено в той же скрипке): здесь

create table docs
(id int identity primary key,
 doc varchar(64)
) ;
GO
insert
into docs (doc)
values ('Enter one batch per field, don''t use ''GO''')
     , ('Fields grow as you type')
     , ('Use the [+] buttons to add more')
     , ('See examples below for advanced usage')
  ;
GO
4 ряда пострадали
--------------------------------------------------------------------------------
-- Or use XML to see the visual representation, thanks to Justin Pealing and
-- his library: https://github.com/JustinPealing/html-query-plan
--------------------------------------------------------------------------------
set statistics xml on;
select d1.* from docs d1 
    join docs d2 on d2.id=d1.id
    join docs d3 on d3.id=d1.id
    join docs d4 on d4.id=d1.id;
set statistics xml off;
GO
id | доктор                                      
-: | : ----------------------------------------
 1 | Введите одну партию в поле, не используйте «GO»
 2 | Поля растут по мере ввода                  
 3 | Используйте кнопки [+], чтобы добавить больше          
 4 | Смотрите примеры ниже для расширенного использования    

введите описание изображения здесь

--------------------------------------------------------------------------------
-- Or use XML to see the visual representation, thanks to Justin Pealing and
-- his library: https://github.com/JustinPealing/html-query-plan
--------------------------------------------------------------------------------
set statistics xml on;
select d1.* from docs d1 
    left join docs d2 on d2.id=d1.id
    left join docs d3 on d3.id=d1.id
    left join docs d4 on d4.id=d1.id;
set statistics xml off;
GO
id | доктор                                      
-: | : ----------------------------------------
 1 | Введите одну партию в поле, не используйте «GO»
 2 | Поля растут по мере ввода                  
 3 | Используйте кнопки [+], чтобы добавить больше          
 4 | Смотрите примеры ниже для расширенного использования    

введите описание изображения здесь

alter table docs
  add foreign key (id) references docs (id) ;
GO
--------------------------------------------------------------------------------
-- Or use XML to see the visual representation, thanks to Justin Pealing and
-- his library: https://github.com/JustinPealing/html-query-plan
--------------------------------------------------------------------------------
set statistics xml on;
select d1.* from docs d1 
    join docs d2 on d2.id=d1.id
    join docs d3 on d3.id=d1.id
    join docs d4 on d4.id=d1.id;
set statistics xml off;
GO
id | доктор                                      
-: | : ----------------------------------------
 1 | Введите одну партию в поле, не используйте «GO»
 2 | Поля растут по мере ввода                  
 3 | Используйте кнопки [+], чтобы добавить больше          
 4 | Смотрите примеры ниже для расширенного использования    

введите описание изображения здесь

ypercubeᵀᴹ
источник
2

В реляционных терминах любое самостоятельное объединение без переименования атрибута не допускается и может быть безопасно исключено из планов выполнения. К сожалению, SQL не является реляционным, и ситуация, в которой оптимизатор может исключить самостоятельное соединение, ограничена небольшим числом пограничных случаев.

Синтаксис SQL SELECT дает объединению логический приоритет над проекцией. Правила SQL для имен столбцов и тот факт, что допускаются дублирующие имена столбцов и неназванные столбцы, значительно усложняет оптимизацию SQL-запросов, чем оптимизация реляционной алгебры. Поставщики СУБД SQL имеют ограниченные ресурсы и должны тщательно выбирать, какие виды оптимизации они хотят поддерживать.

nvogel
источник
1

Первичные ключи всегда уникальны, и нулевые значения недопустимы, поэтому объединение таблицы с первичными ключами (без использования вторичного ключа с собственной ссылкой и без операторов where) приведет к тому же числу строк, что и в исходной таблице.

Что касается того, почему они не оптимизируют это, я бы сказал, что это крайний случай, который они либо не планировали, либо предполагали, что люди этого не сделают. Присоединение таблицы к себе на гарантированных уникальных первичных ключах не имеет смысла.

indiri
источник