Avertissement: j'ai compris le problème (je pense), mais je voulais ajouter ce problème à Stack Overflow car je ne pouvais (facilement) le trouver nulle part. Quelqu'un aurait peut-être une meilleure réponse que moi .
J'ai une base de données où une table "Common" est référencée par plusieurs autres tables. Je voulais voir quels enregistrements de la table commune étaient orphelins (c’est-à-dire qu’aucune référence ne figurait dans aucune autre table).
J'ai couru cette requête:
select *
from Common
where common_id not in (select common_id from Table1)
and common_id not in (select common_id from Table2)
Je sais qu'il y a des disques orphelins, mais aucun disque n'a été retourné. Pourquoi pas?
(Ceci est SQL Server, si cela compte.)
Mise à jour:
Ces articles de mon blog décrivent les différences entre les méthodes de manière plus détaillée:
NOT IN
_ VS. _NOT EXISTS
_ VS. _LEFT JOIN / IS NULL
_: _SQL Server
NOT IN
_ VS. _NOT EXISTS
_ VS. _LEFT JOIN / IS NULL
_: PostgreSQL
NOT IN
_ VS. _NOT EXISTS
_ VS. _LEFT JOIN / IS NULL
_: Oracle
NOT IN
_ VS. _NOT EXISTS
_ VS. _LEFT JOIN / IS NULL
_: MySQL
Il y a trois façons de faire une telle requête:
_LEFT JOIN / IS NULL
_:
_SELECT *
FROM common
LEFT JOIN
table1 t1
ON t1.common_id = common.common_id
WHERE t1.common_id IS NULL
_
_NOT EXISTS
_:
_SELECT *
FROM common
WHERE NOT EXISTS
(
SELECT NULL
FROM table1 t1
WHERE t1.common_id = common.common_id
)
_
_NOT IN
_:
_SELECT *
FROM common
WHERE common_id NOT IN
(
SELECT common_id
FROM table1 t1
)
_
Lorsque _table1.common_id
_ n'est pas nullable, toutes ces requêtes sont sémantiquement identiques.
Lorsqu'il est nullable, _NOT IN
_ est différent, puisque IN
(et donc _NOT IN
_) renvoie NULL
lorsqu'une valeur ne correspond à rien dans une liste contenant un NULL
.
Cela peut être déroutant mais peut devenir plus évident si nous rappelons la syntaxe alternative pour ceci:
_common_id = ANY
(
SELECT common_id
FROM table1 t1
)
_
Le résultat de cette condition est un produit booléen de toutes les comparaisons figurant dans la liste. Bien sûr, une seule valeur NULL
donne le résultat NULL
qui restitue également le résultat entier NULL
.
Nous ne pouvons jamais dire avec certitude que _common_id
_ n'est égal à rien de cette liste, car au moins une des valeurs est NULL
.
Supposons que nous ayons ces données:
_common
--
1
3
table1
--
NULL
1
2
_
_LEFT JOIN / IS NULL
_ et _NOT EXISTS
_ retournera _3
_, _NOT IN
_ retournera rien (puisqu'il sera toujours évalué à FALSE
ou NULL
).
Dans MySQL
, dans le cas d'une colonne non nullable, _LEFT JOIN / IS NULL
_ et _NOT IN
_ sont un peu (plusieurs pour cent) plus efficaces que _NOT EXISTS
_. Si la colonne est nullable, _NOT EXISTS
_ est le plus efficace (encore une fois, pas beaucoup).
Dans Oracle
, les trois requêtes donnent les mêmes plans (un _ANTI JOIN
_).
Dans _SQL Server
_, _NOT IN
_/_NOT EXISTS
_ sont plus efficaces, car _LEFT JOIN / IS NULL
_ ne peut pas être optimisé en un _ANTI JOIN
_ par son optimiseur.
Dans PostgreSQL
, _LEFT JOIN / IS NULL
_ et _NOT EXISTS
_ sont plus efficaces que _NOT IN
_, ils sont optimisés pour un _Anti Join
_, alors que _NOT IN
_ utilise _hashed subplan
_ (ou même un subplan
si la sous-requête est trop grande pour être hachée
Si vous voulez que le monde soit un lieu booléen à deux valeurs, vous devez éviter le cas nul (troisième valeur) vous-même.
N'écrivez pas de clauses IN qui autorisent des valeurs NULL dans la liste. Filtrez-les!
common_id not in
(
select common_id from Table1
where common_id is not null
)
Table1 ou Table2 a des valeurs nulles pour common_id. Utilisez cette requête à la place:
select *
from Common
where common_id not in (select common_id from Table1 where common_id is not null)
and common_id not in (select common_id from Table2 where common_id is not null)
select *
from Common c
where not exists (select t1.commonid from table1 t1 where t1.commonid = c.commonid)
and not exists (select t2.commonid from table2 t2 where t2.commonid = c.commonid)
Supposons ces valeurs pour common_id:
Common - 1
Table1 - 2
Table2 - 3, null
Nous voulons que la ligne dans Common renvoie, car elle n'existe dans aucune des autres tables. Cependant, le nul jette une clé à molette.
Avec ces valeurs, la requête est équivalente à:
select *
from Common
where 1 not in (2)
and 1 not in (3, null)
Cela équivaut à:
select *
from Common
where not (1=2)
and not (1=3 or 1=null)
C'est là où commence le problème. En comparant avec un null, la réponse est inconnue . Donc, la requête se réduit à
select *
from Common
where not (false)
and not (false or unkown)
faux ou inconnu est inconnu:
select *
from Common
where true
and not (unknown)
vrai et non inconnu est également inconnu:
select *
from Common
where unknown
La condition where ne renvoie pas d'enregistrements dont le résultat est inconnu, nous ne récupérons donc aucun enregistrement.
Une façon de résoudre ce problème consiste à utiliser l'opérateur Exist plutôt que In. Exists ne renvoie jamais inconnu, car il fonctionne sur des lignes plutôt que sur des colonnes. (Une ligne existe ou non; aucune ambiguïté nulle au niveau de la ligne!)
select *
from Common
where not exists (select common_id from Table1 where common_id = Common.common_id)
and not exists (select common_id from Table2 where common_id = Common.common_id)
Juste au dessus de ma tête ...
select c.commonID, t1.commonID, t2.commonID
from Common c
left outer join Table1 t1 on t1.commonID = c.commonID
left outer join Table2 t2 on t2.commonID = c.commonID
where t1.commonID is null
and t2.commonID is null
J'ai fait quelques tests et voici mes résultats avec w.r.t. La réponse de @ patmortech et les commentaires de @ rexem.
Si Table1 ou Table2 n'est pas indexé sur commonID, vous obtenez une analyse de table mais la requête de @ patmortech est toujours deux fois plus rapide (pour une table principale de 100 000 lignes).
Si aucun des deux n'est indexé sur commonID, vous obtenez deux analyses de table et la différence est négligeable.
Si les deux sont indexés sur commonID, la requête "pas existe" s'exécute dans 1/3 du temps.
SELECT T.common_id
FROM Common T
LEFT JOIN Table1 T1 ON T.common_id = T1.common_id
LEFT JOIN Table2 T2 ON T.common_id = T2.common_id
WHERE T1.common_id IS NULL
AND T2.common_id IS NULL
cela a fonctionné pour moi :)
sélectionnez * dans Common
où
common_id pas in (select ISNULL (common_id, 'données factices')) de Table1)
et common_id pas dans (sélectionnez ISNULL (common_id, 'dummy-data')) de Table2)
select *,
(select COUNT(ID) from ProductMaster where ProductMaster.CatID = CategoryMaster.ID) as coun
from CategoryMaster