Почему мое левое соединение не возвращает нули?

В 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

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

Спасибо

ИЗМЕНИТЬ

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

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

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

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

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

Categories Table 
-------
CategoryID
Title

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

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

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

Ответ 1

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

ИЗМЕНИТЬ
Вы не можете фильтровать таблицы LEFT JOIN в предложении where, если вы не учитываете значения 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 на основе редактирования 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

Я не уверен на 100%, что будет после OP, это вернет всю информацию, которая может быть объединена, когда 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

OUTPUT:

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

OUTPUT:

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)

Ответ 2

Критерии "WHERE" на i.siteid означают, что на выходе должна быть строка "items". вам нужно написать (i.siteid равно null или i.siteid = 132) или поместить "i.siteid = 132" в предложение "ON" - то, что будет работать и для элемента itemcategories:

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

Ответ 3

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

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

ИЗМЕНИТЬ

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

i.siteid = 132

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

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

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

Ответ 4

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

Наличие i.siteID = 132 в вашей ситуации where по существу сводит на нет выполнение левого соединения по элементам.

Ответ 5

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

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

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

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

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

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

(Я предполагаю, что запрос до таблицы join to items дает вам то, что вы ожидали).

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

Ответ 6

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