Dois-je utiliser SQL JOIN ou IN Clause?

13

J'ai une question sur la meilleure approche. Je ne sais pas quelle approche est la meilleure lorsque les données sont considérées comme de taille variable.

Considérez les 3 TABLEAUX suivants:

EMPLOYÉ

EMPLOYEE_ID, EMP_NAME

PROJET

PROJECT_ID, PROJ_NAME

EMP_PROJ (plusieurs à plusieurs des deux tables ci-dessus)

EMPLOYEE_ID, PROJECT_ID

Problème : étant donné un EmployeeID, recherchez TOUS les employés de TOUS les projets auxquels cet employé est associé.

J'ai essayé cela de deux manières .. les deux approches ne diffèrent que de quelques millisecondes, quelle que soit la taille des données utilisées.

SELECT EMP_NAME FROM EMPLOYEE
WHERE EMPLOYEE_ID IN (
    SELECT EMPLOYEE_ID FROM EMP_PROJ    
    WHERE PROJECT_ID IN (
        SELECT PROJECT_ID FROM EMP_PROJ p, EMPLOYEE e
        WHERE p.EMPLOYEE_ID = E.EMPLOYEE_ID 
        AND  E.EMPLOYEE_ID = 123)

aller

select c.EMP_NAME FROM
(SELECT PROJECT_ID FROM EMP_PROJ
WHERE EMPLOYEE_ID = 123) a
JOIN 
EMP_PROJ b
ON a.PROJECT_ID = b.PROJECT_ID
JOIN 
EMPLOYEE c
ON b.EMPLOYEE_ID = c.EMPLOYEE_ID

Pour l'instant, je m'attends à environ 5000 employés et projets chacun ... mais je n'ai aucune idée de la relation qui existe. Quelle approche recommanderiez-vous? Merci!

EDIT: Plan d'exécution de l'approche 1

"Hash Join  (cost=86.55..106.11 rows=200 width=98)"
"  Hash Cond: (employee.employee_id = emp_proj.employee_id)"
"  ->  Seq Scan on employee  (cost=0.00..16.10 rows=610 width=102)"
"  ->  Hash  (cost=85.07..85.07 rows=118 width=4)"
"        ->  HashAggregate  (cost=83.89..85.07 rows=118 width=4)"
"              ->  Hash Semi Join  (cost=45.27..83.60 rows=118 width=4)"
"                    Hash Cond: (emp_proj.project_id = p.project_id)"
"                    ->  Seq Scan on emp_proj  (cost=0.00..31.40 rows=2140 width=8)"
"                    ->  Hash  (cost=45.13..45.13 rows=11 width=4)"
"                          ->  Nested Loop  (cost=0.00..45.13 rows=11 width=4)"
"                                ->  Index Scan using employee_pkey on employee e  (cost=0.00..8.27 rows=1 width=4)"
"                                      Index Cond: (employee_id = 123)"
"                                ->  Seq Scan on emp_proj p  (cost=0.00..36.75 rows=11 width=8)"
"                                      Filter: (p.employee_id = 123)"

Plan d'exécution de l'approche 2:

"Nested Loop  (cost=60.61..112.29 rows=118 width=98)"
"  ->  Index Scan using employee_pkey on employee e  (cost=0.00..8.27 rows=1 width=4)"
"        Index Cond: (employee_id = 123)"
"  ->  Hash Join  (cost=60.61..102.84 rows=118 width=102)"
"        Hash Cond: (b.employee_id = c.employee_id)"
"        ->  Hash Join  (cost=36.89..77.49 rows=118 width=8)"
"              Hash Cond: (b.project_id = p.project_id)"
"              ->  Seq Scan on emp_proj b  (cost=0.00..31.40 rows=2140 width=8)"
"              ->  Hash  (cost=36.75..36.75 rows=11 width=8)"
"                    ->  Seq Scan on emp_proj p  (cost=0.00..36.75 rows=11 width=8)"
"                          Filter: (employee_id = 123)"
"        ->  Hash  (cost=16.10..16.10 rows=610 width=102)"
"              ->  Seq Scan on employee c  (cost=0.00..16.10 rows=610 width=102)"

Il semble donc que le plan d'exécution de l'approche 2 soit légèrement meilleur, car le «coût» est de 60 contre 85 pour l'approche 1. Est-ce la bonne façon d'analyser cela?

Comment sait-on que cela restera vrai même pour toutes sortes de combinaisons plusieurs-plusieurs?

rk2010
la source
3
On dirait qu'un Postgres m'explique le plan. Personnellement, j'opterais pour l'approche basée sur les jointures, mais lisez certaines des réponses ci-dessous sur la réécriture de la requête. Oh, et je suggérerais que l'utilisation de l'OP explique l'analyse plutôt que de simplement expliquer.
xzilla
Je suis d'accord avec xzilla: explain analyzepourrait révéler plus de différences entre les plans
a_horse_with_no_name

Réponses:

14

Dans SQL Server, avec quelques hypothèses comme «ces champs ne peuvent pas contenir de valeurs NULL», ces requêtes devraient donner le même plan.

Mais considérez également le type de jointure que vous effectuez. Une clause IN comme celle-ci est une semi-jointure, pas une jointure interne. Une jointure interne peut projeter sur plusieurs lignes, donnant ainsi des doublons (par rapport à l'utilisation de IN ou EXISTS). Par conséquent, vous souhaiterez peut-être tenir compte de ce comportement lors du choix de la façon dont vous écrivez votre requête.

Rob Farley
la source
2
Je suis d'accord avec l'utilisation de existe plutôt que d'une jointure lorsque vous essayez d'éviter la duplication. D'après ma propre expérience avec SQL Server, la jointure existante et interne a de toute façon produit le même plan de requête. J'avais quelques problèmes de performances concernant les instructions 'in', mais elles n'ont fait surface que lorsque la sélection dans l'instruction in a commencé à renvoyer plusieurs milliers de lignes.
GrumpyMonkey
6
@GrumpyMonkey - Dans SQL Server 2005+ INet EXISTStoujours donner le même plan selon mon expérience. NOT INet NOT EXISTSsont différents cependant avec NOT EXISTSpréféré - Quelques comparaisons de performances ici
Martin Smith
8

Ce que votre requête recherche est juste

SELECT EMP_NAME 
FROM EMPLOYEE e
WHERE E.EMPLOYEE_ID = 123
and exists (select * from EMP_PROJ  where  EMPLOYEE_ID = 123);

ou

SELECT EMP_NAME 
FROM EMPLOYEE e
WHERE E.EMPLOYEE_ID = 123
and exists (select * from EMP_PROJ ep where  ep.EMPLOYEE_ID = E.EMPLOYEE_ID );
bernd_k
la source
La sous-requête ne serait-elle pas plus rapide si elle était SELECT 1 au lieu de SELECT *?
Daniel Serodio
Peut dépendre du SGBD. Je sais avec certitude que SQL-Server optimise Select *. (cf. Itzik Ben-Gan dans Microsoft® SQL Server® 2012 T-SQL Fundamentals)
bernd_k
0

Vous pouvez essayer cette requête:


select distinct e2.employee_id, ep.project_id 
from employee e, employee e2, emp_proj ep
where
e.employee_id = 123
and e.employee_id = ep.employee_id
and e2.project_id = ep.project_id;
techExplorer
la source