Auto-jointure sur la clé primaire

9

Considérez cette requête qui consiste en Nauto-jointures:

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

Il produit un plan d'exécution avec N analyses d'index cluster et N-1 jointures de fusion.

Honnêtement, je ne vois aucune raison de ne pas optimiser toutes les jointures et de ne faire qu'une seule analyse d'index en cluster, c'est-à-dire d'optimiser la requête d'origine à ceci:

select
    t1.*
from [Table] as t1

Des questions

  • Pourquoi les jointures ne sont-elles pas optimisées?
  • Est-il mathématiquement incorrect de dire que chaque jointure ne change pas l'ensemble de résultats?

Testé sur:

  • Version du serveur source: SQL Server 2014 (12.0.4213)
  • Édition du moteur de base de données source: Microsoft SQL Server Standard Edition
  • Type de moteur de base de données source: SQL Server autonome
  • Niveau de compatibilité: SQL Server 2008 (100)

La requête n'a pas de sens; cela m'est venu à l'esprit et je suis curieux à ce sujet maintenant.

Voici le violon avec la création de table et 3 requêtes: avec inner join, avec left join, et mélangé. Vous pouvez également y consulter le plan d'exécution.

Il semble que left joins soient éliminés dans le plan d'exécution des résultats alors que inner joins ne le sont pas. Mais je ne comprends toujours pas pourquoi .

pkuderov
la source

Réponses:

18

Tout d'abord, supposons que (id)c'est la clé primaire de la table. Dans ce cas, oui, les jointures sont (peuvent être prouvées) redondantes et pourraient être éliminées.

Maintenant, c'est juste de la théorie - ou des mathématiques. Pour que l'optimiseur fasse une élimination réelle, la théorie doit avoir été convertie en code et ajoutée dans la suite d'optimisations / réécritures / éliminations de l'optimiseur. Pour que cela se produise, les développeurs (SGBD) doivent penser que cela aura de bons avantages en termes d'efficacité et que c'est un cas assez courant.

Personnellement, cela ne ressemble pas à un (assez commun). La requête - comme vous l'admettez - semble plutôt idiote et un réviseur ne devrait pas la laisser passer en revue, sauf si elle a été améliorée et la jointure redondante supprimée.

Cela dit, il existe des requêtes similaires où l'élimination se produit. Il y a un très bon article de blog connexe de Rob Farley: JOIN simplification dans SQL Server .

Dans notre cas, tout ce que nous avons à faire pour changer les jointures en LEFTjointures. Voir dbfiddle.uk . L'optimiseur dans ce cas sait que la jointure peut être supprimée en toute sécurité sans modifier les résultats. (La logique de simplification est assez générale et n'a pas de cas particulier pour les auto-jointures.)

Dans la requête d'origine, bien sûr, la suppression des INNERjointures ne peut pas non plus modifier les résultats. Mais il n'est pas du tout courant de s'auto-joindre sur la clé primaire, donc l'optimiseur n'a pas ce cas implémenté. Il est cependant courant de joindre (ou de joindre à gauche) où la colonne jointe est la clé primaire de l'une des tables (et il y a souvent une contrainte de clé étrangère). Ce qui conduit à une deuxième option pour éliminer les jointures: Ajoutez une contrainte de clé étrangère (auto-référencement!):

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

Et le tour est joué, les jointures sont éliminées! (testé dans le même violon): ici

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 lignes affectées
--------------------------------------------------------------------------------
-- 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 | Entrez un lot par champ, n'utilisez pas 'GO'
 2 | Les champs grandissent à mesure que vous tapez                  
 3 | Utilisez les boutons [+] pour ajouter plus          
 4 | Voir les exemples ci-dessous pour une utilisation avancée    

entrez la description de l'image ici

--------------------------------------------------------------------------------
-- 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 | Entrez un lot par champ, n'utilisez pas 'GO'
 2 | Les champs grandissent à mesure que vous tapez                  
 3 | Utilisez les boutons [+] pour ajouter plus          
 4 | Voir les exemples ci-dessous pour une utilisation avancée    

entrez la description de l'image ici

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 | Entrez un lot par champ, n'utilisez pas 'GO'
 2 | Les champs grandissent à mesure que vous tapez                  
 3 | Utilisez les boutons [+] pour ajouter plus          
 4 | Voir les exemples ci-dessous pour une utilisation avancée    

entrez la description de l'image ici

ypercubeᵀᴹ
la source
2

En termes relationnels, toute auto-jointure sans changement d'attribut est un no-op et peut être éliminée en toute sécurité des plans d'exécution. Malheureusement, SQL n'est pas relationnel et la situation où une auto-jointure peut être éliminée par l'optimiseur est limitée à un petit nombre de cas marginaux.

La syntaxe SELECT de SQL donne la priorité logique de jointure à la projection. Les règles de portée de SQL pour les noms de colonnes et le fait que les noms de colonnes en double et les colonnes non nommées sont autorisées rendent l'optimisation des requêtes SQL beaucoup plus difficile que l'optimisation de l'algèbre relationnelle. Les fournisseurs de SGBD SQL disposent de ressources limitées et doivent être sélectifs quant aux types d'optimisation qu'ils souhaitent prendre en charge.

nvogel
la source
1

Les clés primaires sont toujours uniques et les valeurs nulles ne sont pas autorisées, donc joindre une table à elle-même sur les clés primaires (pas avec une clé secondaire auto-référencée et sans instructions where) produira le même nombre de lignes que la table d'origine.

Quant à savoir pourquoi ils ne l'optimisent pas, je dirais que c'est un cas de pointe qu'ils n'ont pas prévu ou que les gens ne feraient pas. Joindre une table à elle-même sur des clés primaires uniques garanties ne sert à rien.

indiri
la source