Self-Join auf Primärschlüssel

9

Betrachten Sie diese Abfrage, die aus NSelf-Joins besteht:

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

Es wird ein Ausführungsplan mit N Cluster-Index-Scans und N-1-Zusammenführungsverknüpfungen erstellt.

Ehrlich gesagt sehe ich keine Gründe, nicht alle Joins zu optimieren und nur einen Clustered-Index-Scan durchzuführen, dh die ursprüngliche Abfrage dahingehend zu optimieren:

select
    t1.*
from [Table] as t1

Fragen

  • Warum werden Joins nicht weg optimiert?
  • Ist es mathematisch falsch zu sagen, dass jeder Join die Ergebnismenge nicht ändert?

Getestet am:

  • Quellserver-Version: SQL Server 2014 (12.0.4213)
  • Quelldatenbank-Engine-Edition: Microsoft SQL Server Standard Edition
  • Typ des Quelldatenbankmoduls: Standalone SQL Server
  • Kompatibilitätsstufe: SQL Server 2008 (100)

Die Abfrage ist nicht aussagekräftig. es kam mir gerade in den Sinn und ich bin jetzt neugierig darauf.

Hier ist die Geige mit der Tabellenerstellung und 3 Abfragen: mit inner join's, mit left join' s und gemischt. Dort können Sie sich auch den Ausführungsplan ansehen.

Es scheint, dass left joins im Ergebnisausführungsplan eliminiert werden, während inner joins dies nicht sind. Ich verstehe immer noch nicht warum .

pkuderov
quelle

Antworten:

18

Nehmen wir zunächst an, dass dies (id)der Primärschlüssel der Tabelle ist. In diesem Fall sind die Verknüpfungen (können nachgewiesen werden) redundant und können entfernt werden.

Das ist nur Theorie - oder Mathematik. Damit der Optimierer eine tatsächliche Eliminierung durchführen kann, muss die Theorie in Code konvertiert und in die Suite der Optimierungen / Umschreibungen / Eliminierungen des Optimierers aufgenommen worden sein. Dazu müssen die (DBMS-) Entwickler davon ausgehen, dass dies gute Vorteile für die Effizienz hat und dass dies häufig genug der Fall ist.

Persönlich klingt es nicht nach einem (häufig genug). Die Abfrage sieht - wie Sie zugeben - ziemlich albern aus, und ein Prüfer sollte sie nicht bestehen lassen, es sei denn, sie wurde verbessert und der redundante Join entfernt.

Es gibt jedoch ähnliche Abfragen, bei denen die Beseitigung erfolgt. Es gibt einen sehr schönen verwandten Blog-Beitrag von Rob Farley: JOIN-Vereinfachung in SQL Server .

In unserem Fall müssen wir nur die Verknüpfungen in LEFTVerknüpfungen ändern . Siehe dbfiddle.uk . Der Optimierer weiß in diesem Fall, dass der Join sicher entfernt werden kann, ohne die Ergebnisse zu ändern. (Die Vereinfachungslogik ist recht allgemein gehalten und nicht speziell für Self-Joins vorgesehen.)

In der ursprünglichen Abfrage kann das Entfernen der INNERVerknüpfungen natürlich auch die Ergebnisse nicht ändern. Es ist jedoch überhaupt nicht üblich, sich auf dem Primärschlüssel selbst zu verbinden, sodass der Optimierer diesen Fall nicht implementiert hat. Es ist jedoch üblich, eine Verknüpfung (oder eine Linksverknüpfung) vorzunehmen, bei der die verknüpfte Spalte der Primärschlüssel einer der Tabellen ist (und häufig eine Fremdschlüsseleinschränkung besteht). Dies führt zu einer zweiten Option zum Entfernen der Verknüpfungen: Hinzufügen einer (selbstreferenzierenden!) Fremdschlüsseleinschränkung:

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

Und voila, die Verknüpfungen sind beseitigt! (in derselben Geige getestet): hier

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 Zeilen betroffen
--------------------------------------------------------------------------------
-- 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 | doc                                      
-: | : ----------------------------------------
 1 | Geben Sie einen Stapel pro Feld ein und verwenden Sie nicht 'GO'.
 2 | Felder wachsen während der Eingabe                  
 3 | Verwenden Sie die Tasten [+], um weitere hinzuzufügen          
 4 | In den folgenden Beispielen finden Sie Informationen zur erweiterten Verwendung    

Geben Sie hier die Bildbeschreibung ein

--------------------------------------------------------------------------------
-- 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 | doc                                      
-: | : ----------------------------------------
 1 | Geben Sie einen Stapel pro Feld ein und verwenden Sie nicht 'GO'.
 2 | Felder wachsen während der Eingabe                  
 3 | Verwenden Sie die Tasten [+], um weitere hinzuzufügen          
 4 | In den folgenden Beispielen finden Sie Informationen zur erweiterten Verwendung    

Geben Sie hier die Bildbeschreibung ein

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 | doc                                      
-: | : ----------------------------------------
 1 | Geben Sie einen Stapel pro Feld ein und verwenden Sie nicht 'GO'.
 2 | Felder wachsen während der Eingabe                  
 3 | Verwenden Sie die Tasten [+], um weitere hinzuzufügen          
 4 | In den folgenden Beispielen finden Sie Informationen zur erweiterten Verwendung    

Geben Sie hier die Bildbeschreibung ein

ypercubeᵀᴹ
quelle
2

In relationaler Hinsicht ist jeder Self-Join ohne Umbenennung von Attributen ein No-Op und kann sicher aus Ausführungsplänen entfernt werden. Leider ist SQL nicht relational und die Situation, in der ein Self-Join vom Optimierer beseitigt werden kann, ist auf eine kleine Anzahl von Randfällen beschränkt.

Die SELECT-Syntax von SQL gibt der Verknüpfung logischen Vorrang vor der Projektion. Die SQL-Gültigkeitsregeln für Spaltennamen und die Tatsache, dass doppelte Spaltennamen und nicht benannte Spalten zulässig sind, machen die SQL-Abfrageoptimierung erheblich schwieriger als die Optimierung der relationalen Algebra. SQL DBMS-Anbieter verfügen über begrenzte Ressourcen und müssen selektiv auswählen, welche Arten von Optimierung sie unterstützen möchten.

nvogel
quelle
1

Primärschlüssel sind immer eindeutig und Nullwerte sind nicht zulässig. Wenn Sie also eine Tabelle auf den Primärschlüsseln mit sich selbst verbinden (nicht mit einem selbstreferenzierenden Sekundärschlüssel und ohne where-Anweisungen), wird dieselbe Anzahl von Zeilen wie in der ursprünglichen Tabelle erzeugt.

In Bezug darauf, warum sie es nicht wegoptimieren, würde ich sagen, dass es ein Randfall ist, den sie entweder nicht geplant haben oder von dem angenommen wird, dass die Leute es nicht tun würden. Das Verknüpfen einer Tabelle mit garantierten eindeutigen Primärschlüsseln hat keinen Zweck.

indiri
quelle