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


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)
  • Випуск джерела бази даних Engine Edition: Microsoft SQL Server Standard Edition
  • Тип двигуна вихідної бази даних: Автономний SQL Server
  • Рівень сумісності: SQL Server 2008 (100)

Запит не має сенсу; це мені просто прийшло в голову, і мені зараз цікаво про це.

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

Здається, що left joins виключаються в плані виконання результатів, тоді як inner joins - ні. Досі не розумію, чому .

Відповіді:


18

По-перше, припустимо, що (id)це основний ключ таблиці. У цьому випадку так, з'єднання є (можна довести) надмірними і їх можна усунути.

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

Особисто це не звучить як одне (досить поширене). Запит - як ви визнаєте - виглядає досить нерозумно, і рецензент не повинен дозволяти йому пройти огляд, якщо тільки він не був покращений і не було видалено зайве приєднання.

Однак, є подібні запити, коли усунення відбувається. Є дуже приємна пов’язана публікація в блозі Роб Фарлі: ПРИЄДНАЙТЕ спрощення в 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 | Див. Приклади для розширеного використання    

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


2

У реляційному відношенні будь-яке самостійне приєднання без перейменування атрибутів є неоперативним і його можна безпечно виключити з планів виконання. На жаль, SQL не є реляційним, і ситуація, коли самостійне приєднання може бути усунено оптимізатором, обмежується невеликою кількістю крайових випадків.

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


1

Первинні ключі завжди унікальні, а нульові значення заборонені, тому приєднання таблиці до себе на первинних ключах (не з самовідсилається вторинним ключем і без висловлювань, де виписки) дасть таку ж кількість рядків, що і вихідна таблиця.

Щодо того, чому вони не оптимізують це, я б сказав, що це крайній випадок, якого вони або не планували, або вважали, що люди цього не робитимуть. Приєднання таблиці до себе на гарантованих унікальних первинних ключах не відповідає меті.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.