2009-05-09 7 views
0

Ceci est une version simplifiée d'une requête que nous exécutons où nous devons trouver toutes les lignes de la table parent principale où les lignes enfants correspondent. La requête ci-dessous ne renvoie aucun résultat lorsque l'une des tables enfant est vide.Pourquoi cette requête renvoie-t-elle uniquement des résultats avec des tables enfants non vides?

Le tableau principal a deux tables enfant:

CREATE TABLE main (id INT PRIMARY KEY, name VARCHAR(8)); 

CREATE TABLE child1(id INT PRIMARY KEY, main_id int, name VARCHAR(8)); 
ALTER TABLE child1 add constraint fk_child1_main foreign key (main_id) references main (id); 

CREATE TABLE child2(id INT PRIMARY KEY, main_id int, name VARCHAR(8)); 
ALTER TABLE child2 add constraint fk_child2_main foreign key (main_id) references main (id); 

INSERT INTO main (id, name) VALUES (1, 'main'); 
INSERT INTO child1 (id, main_id, name) VALUES (2, 1, 'child1'); 

Il n'y a pas de lignes dans child2 et la requête suivante renvoie aucune ligne quand il est vide:

SELECT 
    main.* 
FROM 
    main 
INNER JOIN 
    child1 
ON 
    main.id = child1.main_id 
INNER JOIN 
    child2 
ON 
    main.id = child2.main_id 
WHERE 
    child1.name = 'child1' OR 
    child2.name = 'DOES NOT EXIST'; 

Si une ligne est ajoutée à child2, même s'il ne correspond pas à la clause WHERE, le SELECT renvoie la ligne dans la table principale. Je l'ai testé sur Derby et SQLite, donc cela semble être quelque chose de général avec les bases de données.

Pourquoi cela se comporte-t-il de cette façon?

Que puis-je faire pour le réparer?

Je pourrais passer à des SELECT séparés UNION, mais c'est beaucoup plus bavard, et plus, nous générons dynamiquement le SQL et je préfère ne pas avoir à changer notre code.

Une autre solution consiste simplement à ajouter une ligne bête à la base de données, mais c'est compliqué. PS La table principale est une table de session dans un système de gestion des actifs qui enregistre les actifs que les clients recherchent. Il existe différents types de recherches et chaque type obtient une table enfant distincte, plus une table d'attributs enfant pour les paires clé/valeur de la session sur laquelle la recherche peut être effectuée.

Répondre

4

Lorsque child2 n'a pas de lignes, la requête ne renvoie aucune ligne à cause de la jointure interne de la table child2. Si vous vous joignez à une table qui n'a pas de ligne, vous n'obtiendrez jamais de résultats - vous devrez rejoindre externe à child2 à la place si vous voulez obtenir des résultats lorsque child2 est vide.

Lorsque child2 a une ligne, la raison pour laquelle votre requête retourne des résultats est en raison de la clause where:

 
WHERE 
    child1.name = 'child1' OR 
    child2.name = 'DOES NOT EXIST'; 

La jointure interne dit qu'il doit y avoir quelque chose dans child2 avec un ID correspondant, mais où clause a un OR, donc vous obtiendrez des résultats juste parce que child1.name = 'child1'. Après cela, la base de données n'a pas à s'inquiéter de regarder les tables child2.

Pour résoudre ce problème:

J'ai l'intuition que vous voulez seulement retourner les lignes enfants lorsque certaines conditions sont remplies. Vous devez jointure externe à deux d'entre eux, et peut-être déplacer aussi vos conditions supplémentaires de la clause where à la clause de jointure, comme ceci:

 
SELECT 
    main.* 
FROM 
    main 
LEFT OUTER JOIN 
    child1 
ON 
    main.id = child1.main_id 
    AND child1.name = 'child1' 
LEFT OUTER JOIN 
    child2 
ON 
    main.id = child2.main_id 
    AND child2.name = 'whatever' 
  • jointures externes signifie que vous avez la chance d'obtenir des résultats même si une table est vide. Si vous déplacez les conditions supplémentaires (child1.name = ...) de la clause WHERE vers la jointure externe, vous obtiendrez uniquement les informations sur les tables si la condition est vraie.(Je pense que cela pourrait être ce que vous essayez de faire, mais peut-être pas, dans ce cas, laisser les conditions de la clause WHERE où vous les aviez à l'origine.)

2

Il est de retour rien parce que vous utilisez les jointures internes .

Changer votre intérieur joint à gauche rejoint

+0

Alors pourquoi ajouter une ligne non-assortie à child2 change le résultat de la requête? Selon cette instruction, même après l'ajout d'une ligne non-assortie à child2, la requête ne devrait toujours renvoyer aucune ligne. –

+1

Peu importe, je vois que c'est le main.id = child2.main_id qui empêchait la requête de retourner des résultats, même si child2.name ne correspond pas. J'ignorais juste cette partie de la requête. –

2

Quand vous dites INNER JOIN vous demandez la requête pour renvoyer les lignes qui ont des résultats des deux côtés de la jointure. Cela signifie que toutes les lignes qui n'ont pas de lignes enfants correspondantes seront supprimées. Il semble que ce que vous cherchez est LEFT JOIN qui inclura toutes les lignes sur le côté gauche de la jointure (main) même si elles n'ont pas d'entrée correspondante sur le côté droit (child1, child2) .

Ceci est un comportement standard et un problème très courant pour les personnes ne connaissant pas SQL. Wikipedia a tous les détails, sinon un rapide Google search apporte beaucoup de résultats.

+0

Alors pourquoi ajouter une ligne non-assortie à child2 change le résultat de la requête? Selon cette instruction, même après l'ajout d'une ligne non-assortie à child2, la requête ne devrait toujours renvoyer aucune ligne. –

+0

Peu importe, je vois que c'est le main.id = child2.main_id qui empêchait la requête de retourner des résultats, même si child2.name ne correspondait pas. J'ignorais juste cette partie de la requête. –