2009-09-12 8 views
49

J'ai une relation 1: 1 entre deux tables. Je veux trouver toutes les lignes du tableau A qui ne sont pas une ligne correspondante dans le tableau B. J'utilise cette requête:Comment trouver des lignes dans une table qui n'ont pas de ligne correspondante dans une autre table

SELECT id 
    FROM tableA 
WHERE id NOT IN (SELECT id 
        FROM tableB) 
ORDER BY id desc 

id est la clé primaire dans les deux tableaux. En dehors des indices de clés primaires, j'ai aussi un index sur tableA (id desc). En utilisant H2 (base de données Java intégrée), ceci entraîne un balayage de table complet de la table B.

Je veux éviter une analyse de table complète.

Comment puis-je réécrire cette requête pour qu'elle s'exécute rapidement? Quel indice devrais-je devoir?

+1

chaque fois que vous écrivez 'WHERE col [NOT] IN (SELECT COL FROM othertable)', il est préférable de refactoriser en utilisant [NOT] EXISTS. – topchef

Répondre

72
select tableA.id from tableA left outer join tableB on (tableA.id = tableB.id) 
where tableB.id is null 
order by tableA.id desc 

Si votre db sait comment faire des intersections d'index, cela ne touche l'index de clé primaire

+4

C'est pourquoi j'aime Stack Overflow. Samedi, problème SQL - question répondu avec précision et avec succès en 5 minutes! –

+2

vous avez aussi de bonnes suggestions dans les autres réponses. Naturellement je pense que le mien sera le plus rapide :-) mais les implémentations de db varient considérablement, et je n'ai aucune expérience avec H2. Ce serait bien si vous avez comparé les différentes approches et mis à jour la question avec vos résultats. – SquareCog

25

Vous pouvez également utiliser exists, car il est parfois plus rapide que left join. Vous devrez les comparer pour déterminer celui que vous voulez utiliser.

select 
    id 
from 
    tableA a 
where 
    not exists 
    (select 1 from tableB b where b.id = a.id) 

Pour montrer que exists peut être plus efficace qu'un left join, voici les plans d'exécution de ces requêtes dans SQL Server 2008:

left join - coût subtree total: 1,09724:

left join

exists - coût total du sous-site: 1.074 21:

exists

+1

+1: La condition EXISTS est considérée comme "à satisfaire" si la sous-requête (corrigée dans ce cas) renvoie au moins une ligne. –

+0

l'analyse comparative est une bonne idée. Je suis en train d'essayer de comprendre ce qu'un db pourrait faire sous les couvertures pour exister + sous-requête corrélée qui le rendrait plus rapide qu'une jointure de hash indexée. Savez-vous? – SquareCog

+2

'Exists' n'utilise pas votre sous-requête corrélée standard. Il utilise une semi-jointure. Le plan d'exécution sur SQL Server 2008 pour 'left join' est deux analyses d'index pour une correspondance de hachage avec un filtre pour un select. Pour le 'n'existe pas ', ce sont deux balayages d'index vers un hachage qui correspondent à un filtre select-no. La correspondance hash 'exists' est en fait légèrement plus rapide que la' left join'. 'left join' a un coût total de 1,09,' n'existe pas' de 1,07 sur 'DimCustomer' pour' AdventureWorksDW' à 'AdventureWorksDW2008'. – Eric

5

Vous devez vérifier chaque ID dans tableA contre chaque ID dans tableB. Un SGBDR complet (tel qu'Oracle) serait capable d'optimiser cela dans un SCAN INTEX FULL FAST et de ne pas toucher à la table du tout. Je ne sais pas si l'optimiseur de H2 est aussi intelligent que ça.

H2 supporte la syntaxe MINUS afin que vous devriez essayer

select id from tableA 
minus 
select id from tableB 
order by id desc 

Cela peut fonctionner plus rapidement; cela vaut certainement la peine d'être comparé.

+0

+1 Cela peut en effet être plus efficace dans Oracle. –

4

Pour mon petit jeu de données, Oracle donne à presque toutes ces requêtes exactement le même plan qui utilise les index de clé primaire sans toucher à la table. L'exception est la version MINUS qui réussit à faire moins de transactions cohérentes malgré le coût plus élevé du plan.

--Create Sample Data. 
d r o p table tableA; 
d r o p table tableB; 

create table tableA as (
    select rownum-1 ID, chr(rownum-1+70) bb, chr(rownum-1+100) cc 
     from dual connect by rownum<=4 
); 

create table tableB as (
    select rownum ID, chr(rownum+70) data1, chr(rownum+100) cc from dual 
    UNION ALL 
    select rownum+2 ID, chr(rownum+70) data1, chr(rownum+100) cc 
     from dual connect by rownum<=3 
); 

a l t e r table tableA Add Primary Key (ID); 
a l t e r table tableB Add Primary Key (ID); 

--View Tables. 
select * from tableA; 
select * from tableB; 

--Find all rows in tableA that don't have a corresponding row in tableB. 

--Method 1. 
SELECT id FROM tableA WHERE id NOT IN (SELECT id FROM tableB) ORDER BY id DESC; 

--Method 2. 
SELECT tableA.id FROM tableA LEFT JOIN tableB ON (tableA.id = tableB.id) 
WHERE tableB.id IS NULL ORDER BY tableA.id DESC; 

--Method 3. 
SELECT id FROM tableA a WHERE NOT EXISTS (SELECT 1 FROM tableB b WHERE b.id = a.id) 
    ORDER BY id DESC; 

--Method 4. 
SELECT id FROM tableA 
MINUS 
SELECT id FROM tableB ORDER BY id DESC; 
+1

pour l'écrire 'd r o p', cela fait que les gens lisent le code, vous obtenez +1 – Flavius

Questions connexes