web-dev-qa-db-fra.com

SQL "sélectionner les sous-requêtes" ne renvoie aucun résultat

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.)

118
Jeremy Stein

Mise à jour:

Ces articles de mon blog décrivent les différences entre les méthodes de manière plus détaillée:


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

212
Quassnoi

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
)
35
Amy B

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)
5
Jeremy Stein
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)
4
patmortech

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)
3
Jeremy Stein

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.

3
Austin Salonen
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
3
manji

cela a fonctionné pour moi :)

sélectionnez * dans Common

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)

2
arced
select *,
(select COUNT(ID)  from ProductMaster where ProductMaster.CatID = CategoryMaster.ID) as coun 
from CategoryMaster
0
Donga jayesh