Auto-join sulla chiave primaria


9

Considera questa query che consiste in Nself-join:

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

Produce un piano di esecuzione con N scansioni dell'indice cluster e join unione N-1.

Onestamente, non vedo alcun motivo per non ottimizzare tutti i join e fare solo una scansione dell'indice cluster, ovvero ottimizzare la query originale su questo:

select
    t1.*
from [Table] as t1

Domande

  • Perché i join non sono ottimizzati?
  • È matematicamente errato affermare che ogni join non cambia il set di risultati?

Testato su:

  • Versione server di origine: SQL Server 2014 (12.0.4213)
  • Edizione del motore di database di origine: Microsoft SQL Server Standard Edition
  • Tipo di motore di database di origine: SQL Server autonomo
  • Livello di compatibilità: SQL Server 2008 (100)

La query non è significativa; mi è appena venuto in mente e ora ne sono curioso.

Ecco il violino con la creazione della tabella e 3 query: con inner join's, con left join' e misto. Puoi anche guardare il piano di esecuzione anche lì.

Sembra che left joins siano eliminati nel piano di esecuzione del risultato mentre inner joins non lo sono. Comunque non capisco perché .

Risposte:


18

Innanzitutto, supponiamo che (id)sia la chiave primaria della tabella. In questo caso, sì, i join sono (possono essere dimostrati) ridondanti e potrebbero essere eliminati.

Ora questa è solo teoria - o matematica. Affinché l'ottimizzatore esegua un'eliminazione effettiva, la teoria deve essere stata convertita in codice e aggiunta nella suite di ottimizzazioni / ottimizzazioni / riscritture dell'ottimizzatore. Perché ciò accada, gli sviluppatori (DBMS) devono pensare che avrà buoni benefici in termini di efficienza e che sia un caso abbastanza comune.

Personalmente, non suona come uno (abbastanza comune). La query, come ammetti, sembra piuttosto sciocca e un revisore non dovrebbe lasciarla passare, a meno che non sia stato migliorato e il join ridondante rimosso.

Detto questo, ci sono domande simili in cui avviene l'eliminazione. Esiste un post di blog correlato molto carino di Rob Farley: semplificazione JOIN in SQL Server .

Nel nostro caso, tutto ciò che dobbiamo fare per cambiare i join in LEFTjoin. Vedi dbfiddle.uk . L'ottimizzatore in questo caso sa che il join può essere rimosso in modo sicuro senza modificare i risultati. (La logica di semplificazione è abbastanza generale e non è un caso speciale per i self-join.)

Ovviamente nella query originale, anche la rimozione dei INNERjoin non può modificare i risultati. Ma non è affatto comune auto-unirsi sulla chiave primaria, quindi l'ottimizzatore non ha implementato questo caso. È comune tuttavia unire (o unirsi a sinistra) dove la colonna unita è la chiave primaria di una delle tabelle (e spesso c'è un vincolo di chiave esterna). Il che porta a una seconda opzione per eliminare i join: aggiungere un vincolo di chiave esterna (autoreferenziale!):

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

E voilà, i join vengono eliminati! (testato nello stesso violino): qui

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 file interessate
--------------------------------------------------------------------------------
-- 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 | Inserisci un batch per campo, non utilizzare "GO"
 2 | I campi crescono mentre digiti                  
 3 | Utilizzare i pulsanti [+] per aggiungere altro          
 4 | Vedi gli esempi di seguito per un uso avanzato    

inserisci qui la descrizione dell'immagine

--------------------------------------------------------------------------------
-- 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 | Inserisci un batch per campo, non utilizzare "GO"
 2 | I campi crescono mentre digiti                  
 3 | Utilizzare i pulsanti [+] per aggiungere altro          
 4 | Vedi gli esempi di seguito per un uso avanzato    

inserisci qui la descrizione dell'immagine

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 | Inserisci un batch per campo, non utilizzare "GO"
 2 | I campi crescono mentre digiti                  
 3 | Utilizzare i pulsanti [+] per aggiungere altro          
 4 | Vedi gli esempi di seguito per un uso avanzato    

inserisci qui la descrizione dell'immagine


2

In termini relazionali, qualsiasi self join senza ridenominazione degli attributi è una no-op e può essere tranquillamente eliminato dai piani di esecuzione. Sfortunatamente SQL non è relazionale e la situazione in cui un ottimizzatore può essere eliminato dall'ottimizzatore è limitata a un piccolo numero di casi limite.

La sintassi SELECT di SQL dà la precedenza logica di join rispetto alla proiezione. Le regole di scoping di SQL per i nomi delle colonne e il fatto che siano consentiti nomi di colonne duplicati e colonne senza nome rende l'ottimizzazione delle query SQL significativamente più difficile dell'ottimizzazione dell'algebra relazionale. I fornitori di SQL DBMS dispongono di risorse limitate e devono essere selettivi su quali tipi di ottimizzazione desiderano supportare.


1

Le chiavi primarie sono sempre univoche e i valori null non sono consentiti, pertanto l'unione di una tabella a se stessa sulle chiavi primarie (non con una chiave secondaria autoreferenziale e senza istruzioni where) produrrà lo stesso numero di righe della tabella originale.

Per quanto riguarda il motivo per cui non lo ottimizzano via, direi che è un caso marginale che o non avevano pianificato o presunto che la gente non avrebbe fatto. Unire una tabella a se stesso su chiavi primarie univoche garantite non ha uno scopo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.