web-dev-qa-db-fra.com

Pourquoi ma jointure gauche ne renvoie-t-elle pas de null?

Dans SQL Server 2008, j'ai la requête suivante:

select      
    c.title as categorytitle,
    s.title as subcategorytitle,
    i.title as itemtitle
from categories c
join subcategories s on c.categoryid = s.categoryid
left join itemcategories ic on s.subcategoryid = ic.subcategoryid 
left join items i on ic.itemid = i.itemid and i.siteid = 132
where (ic.isactive = 1 or ic.isactive is null)
order by c.title, s.title

J'essaie d'obtenir des éléments dans leurs sous-catégories, mais je souhaite toujours renvoyer un enregistrement s'il n'y a aucun élément dans la catégorie ou la sous-catégorie. Les sous-catégories qui n'ont pas d'articles ne sont jamais retournées. Qu'est-ce que je fais mal?

Je vous remercie

MODIFIER

Requête modifiée avec une deuxième jointure gauche et une clause where, mais elle ne renvoie toujours pas de null. : /

MODIFIER 2

Siteid déplacé vers la jointure gauche de l'élément. Quand je fais cela, j'obtiens bien plus d'enregistrements que prévu. Certains éléments ont un siteid nul et je ne veux les inclure que lorsqu'ils ont un identifiant spécifique.

MODIFIER

Structure du tableau:

Categories Table 
-------
CategoryID
Title

SubCategories Table
-------
SubCategoryID
CategoryID
Title

ItemCategories Table
-------
ItemCategoryID
ItemID
SubCategoryID
IsActive

Items Table 
--------
ItemID
Title
SiteID
21
jimj

changez join items i ... en LEFT join items i ... et votre requête devrait fonctionner comme prévu.

MODIFIER
Vous ne pouvez pas filtrer les tables LEFT JOIN dans la clause where, sauf si vous tenez compte des valeurs NULL, car la jointure gauche permet à ces colonnes d'avoir une valeur ou d'être Null lorsqu'aucune ligne ne correspond:

and i.siteid = 132 Supprimera toutes vos lignes qui ont un NULL i.siteid, Là où il n'en existait pas. Déplacez-le sur ON:

left join items i on ic.itemid = i.itemid and i.siteid = 132

ou faites les WHERE gérer NULLs:

WHERE ... AND (i.siteid = 132 OR i.siteid IS NULL)

MODIFIER basé sur le montage OP 3

SET NOCOUNT ON
DECLARE @Categories table (CategoryID int,Title varchar(30))
INSERT @Categories VALUES (1,'Cat AAA')
INSERT @Categories VALUES (2,'Cat BBB')
INSERT @Categories VALUES (3,'Cat CCC')

DECLARE @SubCategories table (SubCategoryID int,CategoryID int,Title varchar(30))
INSERT @SubCategories VALUES (1,1,'SubCat AAA A')
INSERT @SubCategories VALUES (2,1,'SubCat AAA B')
INSERT @SubCategories VALUES (3,1,'SubCat AAA C')
INSERT @SubCategories VALUES (4,2,'SubCat BBB A')

DECLARE @ItemCategories table (ItemCategoryID int, ItemID int, SubCategoryID int, IsActive char(1))
INSERT @ItemCategories VALUES (1,1,2,'Y')
INSERT @ItemCategories VALUES (2,2,2,'Y')
INSERT @ItemCategories VALUES (3,3,2,'Y')
INSERT @ItemCategories VALUES (4,4,2,'Y')
INSERT @ItemCategories VALUES (5,7,2,'Y')

DECLARE @Items table (ItemID int, Title varchar(30), SiteID int)
INSERT @Items VALUES (1,'Item A',111)
INSERT @Items VALUES (2,'Item B',111)
INSERT @Items VALUES (3,'Item C',132)
INSERT @Items VALUES (4,'Item D',111)
INSERT @Items VALUES (5,'Item E',111)
INSERT @Items VALUES (6,'Item F',132)
INSERT @Items VALUES (7,'Item G',132)
SET NOCOUNT OFF

Je ne suis pas sûr à 100% de ce que l'OP est après, cela retournera toutes les informations qui peuvent être jointes lorsque le siteid=132 Comme indiqué dans la question

SELECT
    c.title as categorytitle
        ,s.title as subcategorytitle
        ,i.title as itemtitle
        --,i.itemID, ic.SubCategoryID, s.CategoryID
    FROM @Items                          i
        LEFT OUTER JOIN @ItemCategories ic ON i.ItemID=ic.ItemID
        LEFT OUTER JOIN @SubCategories   s ON ic.SubCategoryID=s.SubCategoryID
        LEFT OUTER JOIN @Categories      c ON s.CategoryID=c.CategoryID
    WHERE i.siteid = 132

PRODUCTION:

categorytitle                  subcategorytitle               itemtitle
------------------------------ ------------------------------ ------------------------------
Cat AAA                        SubCat AAA B                   Item C
NULL                           NULL                           Item F
Cat AAA                        SubCat AAA B                   Item G

(3 row(s) affected)

Cela répertoriera toutes les catégories, même s'il n'y a pas de correspondance avec le siteid=132

;WITH AllItems AS
(
SELECT
    s.CategoryID, ic.SubCategoryID, ItemCategoryID, i.ItemID
        ,c.title AS categorytitle, s.title as subcategorytitle, i.title as itemtitle
    FROM @Items                          i
        LEFT OUTER JOIN @ItemCategories ic ON i.ItemID=ic.ItemID
        LEFT OUTER JOIN @SubCategories   s ON ic.SubCategoryID=s.SubCategoryID
        LEFT OUTER JOIN @Categories      c ON s.CategoryID=c.CategoryID
    WHERE i.siteid = 132
)
SELECT
    categorytitle, subcategorytitle,itemtitle
    FROM AllItems
UNION
SELECT
    c.Title, s.Title, null
    FROM @Categories                     c
        LEFT OUTER JOIN @SubCategories   s ON c.CategoryID=s.CategoryID
        LEFT OUTER JOIN @ItemCategories ic ON s.SubCategoryID=ic.SubCategoryID
        LEFT OUTER JOIN AllItems         i ON c.CategoryID=i.CategoryID AND  s.SubCategoryID=i.SubCategoryID
    WHERE i.ItemID IS NULL
ORDER BY categorytitle,subcategorytitle

PRODUCTION:

categorytitle                  subcategorytitle               itemtitle
------------------------------ ------------------------------ ------------------------------
NULL                           NULL                           Item F
Cat AAA                        SubCat AAA A                   NULL
Cat AAA                        SubCat AAA B                   Item C
Cat AAA                        SubCat AAA B                   Item G
Cat AAA                        SubCat AAA C                   NULL
Cat BBB                        SubCat BBB A                   NULL
Cat CCC                        NULL                           NULL

(7 row(s) affected)
34
KM.

Vos critères "WHERE" sur i.siteid signifient qu'il doit y avoir une ligne "items" dans la sortie. vous devez écrire (i.siteid est nul ou i.siteid = 132) ou mettre le "i.siteid = 132" dans la clause "ON" - quelque chose qui fonctionnera également pour la jointure des catégories d'élément:

select      
    c.title as categorytitle,
    s.title as subcategorytitle,
    i.title as itemtitle
from categories c
join subcategories s on c.categoryid = s.categoryid
left join itemcategories ic on s.subcategoryid = ic.subcategoryid and ic.isactive = 1
left join items i on ic.itemid = i.itemid and i.siteid = 132
order by c.title, s.title
7
araqnid

Peut-être que cette jointure devrait également être une jointure gauche?

join items i on ic.itemid = i.itemid and i.siteid = 132

MODIFIER :

Maintenant, vous sélectionnez uniquement les identifiants de site existants dans la clause where:

i.siteid = 132

Il doit autoriser les valeurs nulles, essayez quelque chose comme ceci:

(i.siteid = 132 or i.siteid is null)

ou vous pouvez déplacer i.siteid = 132 retour à la condition de jointure

1
Andrew Bezzub
where (ic.isactive = 1 or ic.isactive is null) and i.siteid = 132

Avoir l'i.siteID = 132 dans votre clause where annule essentiellement la réalisation d'une jointure gauche sur les éléments.

0
JupiterP5

2ème tentative, je pense avoir votre problème maintenant. Si je comprends bien, ce qui se passe, c'est que vous vous retrouvez avec deux types de NULL dans SiteID dans votre cas (le cas lorsque vous voyez 10 000e résultats, mais c'est toujours sur la bonne voie).

Il y a des NULL qui proviennent de la jointure gauche et ceux qui proviennent de la table Itemid siteid réelle. Vous voulez que ceux qui viennent de la jointure gauche, mais vous ne voulez pas ceux des données.

Il s'agit d'une erreur très courante avec les jointures externes lors du test de l'existence de lignes correspondantes.

Gardez à l'esprit que si vous voulez des lignes sans correspondance qui doivent toujours tester NULL uniquement sur les colonnes définies comme NOT NULL (la clé primaire de la table externe est un candidat naturel ici). Sinon, vous ne pouvez pas faire la distinction entre les lignes NULL en raison de la jointure LEFT et les lignes qui seraient NULL même s'il s'agissait d'une jointure INNER

Au moins deux façons de procéder: a) la jointure gauche sur la sous-requête qui filtrera les lignes avec siteid est nulle avant que la jointure gauche ne se déclenche
b) réécrire les critères (en supposant que ItemID est requis dans Items) pour dire

select      
    c.title as categorytitle,
    s.title as subcategorytitle,
    i.title as itemtitle
from categories c
join subcategories s on c.categoryid = s.categoryid
left join itemcategories ic on s.subcategoryid = ic.subcategoryid 
left join items i on ic.itemid = i.itemid
where (ic.isactive = 1 or ic.isactive is null) AND (i.siteid = 132 or i.itemid is null)
order by c.title, s.title

(Je suppose que la requête jusqu'à la table join to items vous a donné ce que vous attendiez).

Si itemid est requis dans les articles, la condition ci-dessus dit - lignes avec siteid 132 ou lignes qui proviennent vraiment d'une jointure gauche inégalée (notez que la condition est sur i.itemid est null et non i.siteid est null).

0
Unreason