Почему мое левое соединение не возвращает нули? - PullRequest
19 голосов
/ 19 марта 2010

В SQL Server 2008 у меня есть следующий запрос:

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

Я пытаюсь получить элементы в их подкатегориях, но все еще хочу вернуть запись, если в категории или подкатегории нет элементов. Подкатегории, у которых нет элементов, никогда не возвращаются. Что я делаю не так?

Спасибо

EDIT

Изменен запрос со вторым левым соединением и предложением where, но он по-прежнему не возвращает нулевые значения. : /

РЕДАКТИРОВАТЬ 2

Перемещен siteid к элементу left join. Когда я делаю это, я получаю больше записей, чем ожидалось. Некоторые элементы имеют нулевой siteid, и я хочу включить их, только если у них есть определенный идентификатор.

РЕДАКТИРОВАТЬ 3

Структура таблицы:

Categories Table 
-------
CategoryID
Title

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

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

Items Table 
--------
ItemID
Title
SiteID

Ответы [ 6 ]

33 голосов
/ 19 марта 2010

измените join items i ... на LEFT join items i ... и ваш запрос должен работать так, как вы ожидаете.

РЕДАКТИРОВАТЬ
Вы не можете фильтровать таблицы LEFT JOIN впредложение where, если только вы не учитываете значения NULL, потому что левое соединение позволяет этим столбцам иметь значение или быть NULL, если ни одна строка не соответствует:

and i.siteid = 132 отбросит любую из ваших строк, имеющих NULL i.siteid там, где их не было.Переместите это в положение ON:

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

или задайте для WHERE значение NULL:

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

EDIT основанный на редактировании ОП 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

Я не уверен на 100%, что ОП после этого, это вернет всю информацию, которая может быть объединена, когда siteid=132, как указано в вопросе

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

ВЫХОД:

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)

В этом списке перечислены все категории, даже если нет совпадений с 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

ВЫХОД:

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)
6 голосов
/ 19 марта 2010

Ваш критерий "WHERE" для i.siteid означает, что в выводе должна быть строка "items". вам нужно написать (i.siteid имеет значение null или i.siteid = 132) или поместить «i.siteid = 132» в предложение «ON» - что-то, что также будет работать для объединения элементов категории:

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
1 голос
/ 19 марта 2010

Может быть, это соединение также должно быть левым?

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

EDIT

Теперь вы выбираете только существующие идентификаторы сайта в предложении where:

i.siteid = 132

Это должно разрешить нулевые значения, попробуйте что-то вроде этого:

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

или вы можете переместить i.siteid = 132 обратно в состояние соединения

0 голосов
/ 22 марта 2010

2-я попытка, я думаю, у меня сейчас твоя проблема. Если я правильно понимаю, то получается, что в вашем случае вы получаете два типа NULL в SiteID (случай, когда вы видите результаты в 10000e, но это все еще на правильном пути).

Есть NULL, которые приходят из левого соединения, и те, которые приходят из фактического siteid таблицы Предметов. Вы хотите, чтобы те, которые исходили из левого соединения, но не нужны из данных.

Это очень распространенная ошибка с внешними объединениями при проверке на наличие совпавших строк.

Имейте в виду, что вы, если вам нужны несопоставленные строки, которые всегда должны проверять на NULL только в столбцах, которые определены как NOT NULL (первичный ключ из внешней таблицы является естественным кандидатом здесь). В противном случае вы не сможете различить строки, имеющие значение NULL, из-за LEFT-соединения и строки, которые будут иметь значение NULL, даже если это было INNER-соединение

По крайней мере, два способа сделать это: a) левое объединение в подзапросе, который отфильтровывает строки с siteid, будет нулевым, прежде чем левое объединение вступит в силу
б) переписать критерии (при условии, что ItemID требуется в Items), чтобы сказать

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

(я предполагаю, что запрос до таблицы объединения элементов дал вам то, что вы ожидали).

Если itemid требуется в элементах, то вышеупомянутое условие говорит - строки с siteid 132 или строки, которые на самом деле происходят из несопоставленного левого соединения (обратите внимание, что условие на i.itemid равно нулю, а i.siteid не равно нулю).

0 голосов
/ 19 марта 2010
where (ic.isactive = 1 or ic.isactive is null) and i.siteid = 132

Наличие i.siteID = 132 в предложении where по существу сводит на нет выполнение левого объединения элементов.

0 голосов
/ 19 марта 2010

Попробуйте изменить объединение элементов на левое соединение.

...