Join with recursive query - sql

Setup
I have the following tables (simplyfied):
CREATE TABLE Category(
CategoryId int NOT NULL PRIMARY KEY,
ParentCategoryId int NULL,
Name nvarchar(255) NOT NULL,
FOREIGN KEY (ParentCategoryId) REFERENCES Category(CategoryId) ON UPDATE NO ACTION ON DELETE NO ACTION);
CREATE TABLE TimeSlot(
TimeSlotId int NOT NULL PRIMARY KEY,
CategoryId int NOT NULL,
FOREIGN KEY (CategoryId) REFERENCES Category(CategoryId) ON UPDATE NO ACTION ON DELETE NO ACTION);
CREATE TABLE PersonTimeSlotAssignment(
PersonId int NOT NULL,
TimeSlotId int NOT NULL,
PRIMARY KEY (PersonId, TimeSlotId),
FOREIGN KEY (TimeSlotId) REFERENCES TimeSlot(TimeSlotId) ON UPDATE NO ACTION ON DELETE NO ACTION);
and here is some test data:
INSERT INTO Category(CategoryId, ParentCategoryId, Name) VALUES (100, NULL, 'cat 1');
INSERT INTO Category(CategoryId, ParentCategoryId, Name) VALUES (110, 100, 'cat 1.1');
INSERT INTO Category(CategoryId, ParentCategoryId, Name) VALUES (111, 110, 'cat 1.1.1');
INSERT INTO Category(CategoryId, ParentCategoryId, Name) VALUES (120, 100, 'cat 1.2');
INSERT INTO Category(CategoryId, ParentCategoryId, Name) VALUES (200, NULL, 'cat 2');
INSERT INTO TimeSlot(TimeSlotId, CategoryId) VALUES (301, 111);
INSERT INTO TimeSlot(TimeSlotId, CategoryId) VALUES (302, 120);
INSERT INTO TimeSlot(TimeSlotId, CategoryId) VALUES (303, 200);
INSERT INTO PersonTimeSlotAssignment(PersonId, TimeSlotId) VALUES (401, 301);
INSERT INTO PersonTimeSlotAssignment(PersonId, TimeSlotId) VALUES (401, 302);
INSERT INTO PersonTimeSlotAssignment(PersonId, TimeSlotId) VALUES (402, 302);
INSERT INTO PersonTimeSlotAssignment(PersonId, TimeSlotId) VALUES (402, 303);
What I can do
SELECT ts.TimeSlotId, pc.Name
FROM PersonTimeSlotAssignment
JOIN TimeSlot AS ts ON PersonTimeSlotAssignment.TimeSlotId = ts.TimeSlotId
JOIN Category AS pc ON ts.CategoryId = pc.CategoryId
WHERE PersonTimeSlotAssignment.PersonId = #PERSON_ID;
This gives me for some person a list of all TimeSlots to which this person is assigned and the name of the leaf category which the TimeSlot belongs to. For example for person with ID 401 it gives:
TimeSlotId Name
---------------------
301 cat 1.1.1
302 cat 1.2
With the following recursive query I can also get from some category all the ancestors up to the root category:
;WITH Parents AS (
SELECT * FROM Category
WHERE CategoryId=#CATEGORY_ID
UNION ALL SELECT c.* FROM Category c JOIN Parents p ON p.ParentCategoryId=c.CategoryId
)
SELECT Name FROM Parents;
For example for category with ID 111 I get:
Name
---------
cat 1.1.1
cat 1.1
cat 1
What I want to do
What I need is a list of TimeSlots a person is assigned with, joined with the category names for that TimeSlot up to the root category. So for person with ID 401 the result should look like this:
TimeSlotId Name
---------------------
301 cat 1.1.1
301 cat 1.1
301 cat 1
302 cat 1.2
302 cat 1
I was not able to figure out how to combine the above two queries so that I get the expected result.
What I tried
I was hoping that something along these lines could work:
;WITH Parents AS (
SELECT * FROM Category
WHERE CategoryId=<<'How to get CategoryId for each assigned TimeSlot here?'>>
UNION ALL SELECT c.* FROM Category c JOIN Parents p ON p.ParentCategoryId=c.CategoryId
)
SELECT ts.TimeSlotId, pc.Name
FROM PersonTimeSlotAssignment
JOIN TimeSlot AS ts ON PersonTimeSlotAssignment.TimeSlotId = ts.TimeSlotId
JOIN Parents AS pc ON <<'How should this look like?'>>
WHERE PersonTimeSlotAssignment.PersonId = #PERSON_ID;

User defined function and cross apply is very useful in this case.
--1. Create function
create function fn_Category(#id int)
returns table
as
return
with tbl as (
--anckor query
select CategoryId, ParentCategoryId,Name, 1 lvl
from Category where CategoryId = #id
union all
--recursive query
select c.CategoryId, c.ParentCategoryId,c.Name, lvl+1
from Category c
inner join tbl on tbl.ParentCategoryId=c.CategoryId--go up the tree
)
select * from tbl
go
--end of function
--2. and now we can use it
declare #PERSON_ID int = 401
SELECT ts.TimeSlotId, pc.Name
FROM PersonTimeSlotAssignment
JOIN TimeSlot AS ts ON PersonTimeSlotAssignment.TimeSlotId = ts.TimeSlotId
--JOIN Category AS pc ON ts.CategoryId = pc.CategoryId
--use cross apply instead
cross apply fn_Category(ts.CategoryId) pc
WHERE PersonTimeSlotAssignment.PersonId = #PERSON_ID;

This will handle recursing the categories and providing all the data based on a PersonId or TimeSlotId:
WITH Categories (PersonId, CategoryId, ParentCategoryId, TimeSlotId, Name, BASE)
AS
(
SELECT PersonId, c.CategoryId, c.ParentCategoryId, pts.TimeSlotId, c.Name, 0 AS BASE
FROM Category c
INNER JOIN TimeSlot ts ON c.CategoryId = ts.CategoryId
INNER JOIN PersonTimeSlotAssignment pts ON ts.TimeSlotId = pts.TimeSlotId
UNION ALL
SELECT PersonId, pc.CategoryId, pc.ParentCategoryId, TimeSlotId, pc.Name, BASE + 1
FROM Category pc
INNER JOIN Categories cs ON cs.ParentCategoryId = pc.CategoryId
)
SELECT * FROM Categories
WHERE PersonId = 401
--WHERE TimeSlotId = 301
There may be a better way to write this, but does what you've asked and should get you where you need to go. The 'BASE' does not serve its original purpose, but does still show the correlation between your Person and Category, e.g. BASE 0 means the category from that record is assigned directly to the person. So, I left it for that. Thanks.

Hope this helps you :)
DECLARE #PersonId INT= 401;
WITH CTE AS
(
SELECT
t.*,
c.CategoryId AS CategoryId_c,
c.ParentCategoryId as ParentCategoryId_c,
c.Name AS Name_c,
c1.CategoryId AS CategoryId_c2,
c1.ParentCategoryId AS ParentCategoryId_c2,
c1.Name AS Name_c2,
c2.CategoryId as CategoryId_c3,
c2.ParentCategoryId AS ParentCategoryId_c3,
c2.Name AS Name_c3
FROM
PersonTimeSlotAssignment p
INNER JOIN TimeSlot t ON t.TimeSlotId=p.TimeSlotId
INNER JOIN Category c ON t.CategoryId=c.CategoryId
LEFT JOIN Category c1 ON c1.CategoryId=c.ParentCategoryId
LEFT JOIN Category c2 ON c2.CategoryId=c1.ParentCategoryId
WHERE p.PersonId=#PersonId
)
SELECT * FROM (
SELECT TimeSlotId,Name_c FROM CTE
UNION
SELECT TimeSlotId,Name_c2 FROM CTE
UNION
SELECT TimeSlotId,Name_c3 FROM CTE
)a WHERE Name_c IS NOT NULL

DECLARE #PersonId int =401;
IF OBJECT_ID('tempdb.dbo.#TimeSlot') is not null
DROP TABLE #TimeSlot
SELECT DISTINCT DENSE_RANK() OVER(ORDER BY t.TimeSlotId ) ID, t.TimeSlotId,c.CategoryId
INTO #TimeSlot
FROM PersonTimeSlotAssignment p
INNER JOIN TimeSlot t
ON p.TimeSlotId=t.TimeSlotId
INNER JOIN Category c
ON c.CategoryId=t.CategoryId
WHERE PersonId=#PersonId
IF OBJECT_ID('tempdb.dbo.#Output') is not null
DROP TABLE #Output
CREATE TABLE #Output(timeSlotId INT,CategoryId INT)
DECLARE #id INT=1 DECLARE #timeSlotId AS INT DECLARE #CategoryId INT
WHILE( SELECT id FROM #TimeSlot WHERE id=#id) IS NOT NULL
BEGIN
SELECT #timeSlotId=TimeSlotId,#CategoryId=CategoryId FROM #TimeSlot WHERE ID=#id
INSERT INTO #Output SELECT #timeSlotId,#CategoryId
WHILE( SELECT ParentCategoryId FROM Category WHERE CategoryId=#CategoryId) is not null
BEGIN
SELECT #CategoryId=ParentCategoryId FROM Category WHERE CategoryId=#CategoryId
INSERT INTO #Output SELECT #timeSlotId,#CategoryId
END
SET #id=#id+1
END
SELECT a.timeSlotId,c.Name FROM #Output a INNER JOIN Category c ON a.CategoryId=c.CategoryId

Related

Get hierarchical data is SQL SERVER with fallback logic

Consider the below schema
dbo.Cultures (Id, CultureCode, ParentId)
Culture table stores the data in the parent-child relationship.
Suppose we have below demo data
5 es-ES 3
Now I have another table which stores the multilingual data for the different cultures.
Schema for the table is as following
dbo.LangData(KeyName, CultureId, Value)
here cultureId is the foreign key of dbo.Cultures table.
Suppose this table has following data
Now I require to fetch the data for all the cultures which are in the Culture table and the corresponding value column in the LangData table.
The culture Ids which are not in the LangData table, for those the Value column will the value of the corresponding parent culture Id columns value. I.e. Data will be retrieved using fallback logic
E.g. For the above values the Result set will be following.
5 es-ES Colour_IN
Here for de-DE data is missing in LangData so it's value will be the data in it's parent culture i.e. en-IN, if in case data also not found in en-IN then it will pick the data of it's parent en-US.
Tried Soloution
First I fetch the culture hierarchy using CTE
CREATE FUNCTION [dbo].[ufnGetCultureHierarchyAll] ()
RETURNS #hierarchyResult TABLE(RowNo INT, CultureId INT, ParentCultureId INT)
AS
BEGIN
WITH CultureHierarchy_CTE(RowNo, CultureId, ParentCultureId)
AS (
SELECT 1,
Id,
ParentId
FROM [dbo].Cultures
UNION ALL
SELECT RowNo + 1,
ou.Id,
ou.ParentId
FROM [dbo].Cultures ou
JOIN CultureHierarchy_CTE cte
ON ou.Id = cte.ParentCultureId
)
-- inserting desired records into table and returning
INSERT INTO #hierarchyResult (RowNo,CultureId,ParentCultureId )
SELECT RowNo, CultureId , ParentCultureId FROM CultureHierarchy_CTE
RETURN;
END
This will return the hierarchy of the all the cultures
Now I attempted to apply join of the result set with the LangData table,
DECLARE #cultureHierarchy AS TABLE(
RowNumber INT,
CultureId INT,
ParentCultureId INT
)
--SELECT * FROM master.Cultures
----Get and store culture hierarchy
INSERT INTO #cultureHierarchy
SELECT RowNo, CultureId, ParentCultureId
FROM ufnGetCultureHierarchyAll()
SELECT c.Code AS [CultureCode],
c.CultureId AS [CultureId],
rv.Value
FROM dbo.LangData rv WITH (NOLOCK)
JOIN #cultureHierarchy c ON rv.CultureId = c.CultureId
END
but it is not working.
Is someone have any Idea regarding same.
Solution using Itzik Ben-Gan's hierarchy model. If you can extend the dbo.Cultures table with Hierarchy, Lvl and Root columns and index on Hierarchy, query will be faster. It has to be rewrited in that case though.
drop table if exists dbo.Cultures;
create table dbo.Cultures (
ID int
, Code varchar(50)
, ParentID int
);
insert into dbo.Cultures (ID, Code, ParentID)
values (1, 'en-US', null), (2, 'en-IN', 1), (3, 'de-DE', 2), (4, 'hi-HI', 2)
drop table if exists dbo.LangData;
create table dbo.LangData (
KeyName varchar(100)
, CultureID int
, Value varchar(100)
);
insert into dbo.LangData (KeyName, CultureID, Value)
values ('lblColourName', 1, 'Color'), ('lblColourName', 2, 'Colour-IN');
with cteCultures as (
select
c.ID, c.Code, c.ParentID, 0 as Lvl
, convert(varchar(max), '.' + CONVERT(varchar(50), c.ID) + '.') as Hierarchy
, c.ID as Root
from dbo.Cultures c
where c.ParentID is null
union all
select
c.ID, c.Code, c.ParentID, cc.Lvl + 1 as Lvl
, cc.Hierarchy + convert(varchar(50), c.ID) + '.' as Hierarchy
, cc.Root as Root
from dbo.Cultures c
inner join cteCultures cc on c.ParentID = cc.ID
)
select
ccr.ID
, ccr.Code
, coalesce(ld.Value, ld2.Value) as Value
from cteCultures ccr
left join dbo.LangData ld on ccr.ID = ld.CultureID
outer apply (
select
top (1) tcc.ID
from cteCultures tcc
inner join dbo.LangData tld on tcc.ID = tld.CultureID
where ld.KeyName is null
and ccr.Hierarchy like tcc.Hierarchy + '%'
and ccr.Hierarchy <> tcc.Hierarchy
order by tcc.Lvl desc
) tt
left join dbo.LangData ld2 on tt.ID = ld2.CultureID
If I understand your question:
We just build your hierarchy (SEQ and Lvl are optional) and then perform TWO left joins in concert with a Coalesce().
Example
Declare #Cultures table (id int,ParentId int,Code varchar(50))
Insert into #Cultures values
( 1, NULL,'en-US')
,( 2, 1 ,'en-IN')
,( 3, 2 ,'de-DE')
,( 4, 2 ,'hi-HI')
Declare #LangData table (keyName varchar(50),CultureId int,Value varchar(50))
Insert Into #LangData values
('lblColourName',1,'Color')
,('lblColourName',2,'Color_IN')
;with cteP as (
Select Seq = cast(10000+Row_Number() over (Order by Code) as varchar(500))
,ID
,ParentId
,Lvl=1
,Code
From #Cultures
Where ParentId is null
Union All
Select Seq = cast(concat(p.Seq,'.',10000+Row_Number() over (Order by r.Code)) as varchar(500))
,r.ID
,r.ParentId
,p.Lvl+1
,r.Code
From #Cultures r
Join cteP p on r.ParentId = p.ID)
Select CultureId = A.ID
,A.Code
,Value = Coalesce(C.Value,B.Value)
From cteP A
Left Join #LangData B on (A.ParentId=B.CultureId)
Left Join #LangData C on (A.Id=C.CultureId)
Order By Seq
Returns
CultureId Code Value
1 en-US Color
2 en-IN Color_IN
3 de-DE Color_IN
4 hi-HI Color_IN

Query different type data in to a single row with the master record

I just created the following sample data to demonstrate what I am after.
I need to query the above table to get my information as 1 single row
i.e. like the following
I was planning to create two temp tables and insert the two different types of addresses seperately. Then, inner join them with the main company table. I am not sure, this is a good solution. I appreaciate if anyone share their thoughts or code to my problem.
Try this..
Select companyId,CompanyName,homesddress1
,homeaddress2,HomePostCode,OfficeAddress1,OfficeAddress2,OfficePostCode
From tblCompany a
Outer apply ( select address1 homesddress1, address2 homeaddress2,postcode HomePostCode
From tblAddress t
Where AddressType='home' and t.companyid=a.companyid)
Outer apply (select address1 OfficeAddress1, address2 Officeaddress2,postcode OfficePostCode
From tblAddress t2
Where AddressType='Office ' and t2.companyid=a.companyid)
You can do it with a simple select using two outer joins. Note that you need the joins to be outer because for some companies you may only have one address.
DECLARE #company TABLE (
CompanyId int,
CompanyName varchar(50)
)
DECLARE #companyAddress TABLE (
Id int,
AddressType varchar(10),
Address1 varchar(50),
Address2 varchar(50),
Postcode varchar(10),
CompanyId int
)
INSERT INTO #company VALUES (1, 'Test Company')
INSERT INTO #companyAddress VALUES (1, 'Home', '25 Street', 'City 1', 'BA3 1PE', 1)
INSERT INTO #companyAddress VALUES (2, 'Office', '25 Street', 'City 2', 'NA1 4TW', 1)
SELECT c.CompanyId, c.CompanyName,
h.Address1 AS HomeAddress1, h.Address2 AS HomeAddress2, h.Postcode AS HomePostcode,
o.Address1 AS OfficeAddress1, o.Address2 AS OfficeAddress2, o.Postcode AS OfficePostcode
FROM #company c
LEFT JOIN
#companyAddress h ON h.CompanyId = c.CompanyId AND h.AddressType = 'Home'
LEFT JOIN
#companyAddress o ON o.CompanyId = c.CompanyId AND o.AddressType = 'Office'
Here is an Almost Dynamic version. You just have to specify the field list in the final pivot
Declare #YourTable table (ID int,AddressType varchar(25),Address1 varchar(50),Address2 varchar(50),CompanyID int)
Insert Into #YourTable values
(1,'Home' ,'25 Street','City 1',1),
(2,'Office','10 Avenue','City 2',1)
Declare #XML xml = (Select * from #YourTable for XML RAW) --<<< Initial Query
;with cteBase as (
Select ID = R.value('#CompanyID','int') --<<< Key ID
,AddressType = R.value('#AddressType','varchar(50)')
,Item = R.value('#AddressType','varchar(50)')+Attr.value('local-name(.)','varchar(100)')
,Value = Attr.value('.','varchar(max)')
From #XML.nodes('/row') as A(R)
Cross Apply A.r.nodes('./#*[local-name(.)!="CompanyID"]') as B(Attr) --<<< Key ID
),cteDist as (Select Distinct ID,Item from cteBase
),cteComp as (
Select A.*,B.Value
From cteDist A
Cross Apply (Select Value=Stuff((Select Distinct ',' + Value
From cteBase
Where ID=A.ID
and Item=A.Item
For XML Path ('')),1,1,'') ) B
)
Select *
From (Select * From cteComp) as s
Pivot (max(value)
For Item in (HomeID,HomeAddressType,HomeAddress1,HomeAddress2,OfficeID,OfficeAddressType,OfficeAddress1,OfficeAddress2)) as pvt
Returns
ID HomeID HomeAddressType HomeAddress1 HomeAddress2 OfficeID OfficeAddressType OfficeAddress1 OfficeAddress2
1 1 Home 25 Street City 1 2 Office 10 Avenue City 2

Find and insert dummy rows - possible scenario for OUTER APPLY

This is a mock up of the situation we've got:
IF OBJECT_ID('TEMPDB..#People') IS NOT NULL BEGIN DROP TABLE #People END;
CREATE TABLE #People
(
Name VARCHAR(100),
Category VARCHAR(20),
ID INT
);
INSERT INTO #People
values
('x','Bronze',1),
('y','Bronze',2),
('z','Silver',3),
('j','Gold',4),
('q','Bronze',5),
('x','Silver',1);
IF OBJECT_ID('TEMPDB..#Category') IS NOT NULL BEGIN DROP TABLE #Category END;
CREATE TABLE #Category
(
Category VARCHAR(100)
);
INSERT INTO #Category
values
('Gold'),
('Silver'),
('Bronze');
If a name does not have a Category e.g. x does not have Gold then I'd like a row creating and adding into the table #People with an ID of -1.
Current solution I have is this:
WITH x AS
(
SELECT DISTINCT
x.Name,
s.Category
FROM #People x
CROSS JOIN #Category s
)
INSERT INTO #People
SELECT J.Name,
J.Category,
ID = -1
FROM x J
WHERE NOT EXISTS
(
SELECT 1
FROM #People Q
WHERE J.Name = Q.Name
AND J.Category = Q.Category
);
See it works!...:
SELECT *
FROM #People;
I have a feeling CROSS APPLY might be a good operator to use in order to simplify the above - What is the simplest way to find, create and insert these rows?
insert into People(Name, Category, Id)
(
select distinct
p.Name,
c.Category,
p.Id
from
people p
cross join category c
where
Concat(p.id, c.Category) not in (select Concat(id, Category) from people)
);
http://www.sqlfiddle.com/#!6/92cd5/14

How to find nth child of a record in sql query

I have a table which stores categories and sub categories (self join)
The table structure is like this:
-CategoryId
-CategoryName
-ParentCategoryId
How to find nth child/childs of a category in sql query
How about something like this
DECLARE #Categories TABLE(
CategoryId INT,
CategoryName VARCHAR(20),
ParentCategoryId INT
)
INSERT INTO #Categories SELECT 1, '1',NULL
INSERT INTO #Categories SELECT 2, '2',NULL
INSERT INTO #Categories SELECT 3, '1.3',1
INSERT INTO #Categories SELECT 4, '1.4',1
INSERT INTO #Categories SELECT 5, '1.3.5',3
INSERT INTO #Categories SELECT 6, '1.3.6',3
INSERT INTO #Categories SELECT 7, '1.3.6.7',6
INSERT INTO #Categories SELECT 8, '1.4.8',4
DECLARE #CatID INT,
#NthLevel INT
SELECT #CatID = 1,
#NthLevel = 2
;WITH Vals AS (
SELECT *,
1 AS CatLevel
FROM #Categories c
WHERE CategoryId IS NULL
UNION ALL
SELECT c.*,
CatLevel + 1 AS CatLevel
FROM Vals v INNER JOIN
#Categories c ON c.ParentCategoryId = v.CategoryID
)
SELECT *
FROM Vals
WHERE CatLevel = #NthLevel
This will recursively build the tree structure, and limit it on the tree level you are looking for.
SELECT ParentCategoryId,
, CategoryId AS [n-th child]
FROM (
SELECT CategoryId
, ParentCategoryId
, ROW_NUMBER() OVER (PARTITION BY ParentCategoryId ORDER BY CategoryId) as ChildNumber
FROM Categories
) p
WHERE ChildNumber = %N
To find the Nth child, ordered by CategoryId, of category M:
select *
from (
select row_number() over (order by c.CategoryId) as rn
, c.*
from Categories p
join Categories c
on p.CategoryId = c.ParentCategoryId
where p.CategoryId = M
)
where rn = N

SELECT item types not provided by an entity

So I've got some data. There are entities. Entities have an arbitrary number of items. Items can be one of a defined set of types. An entity can have more than one item of a given type. I can get a list of items that an entity has. What I want is to get a list of types that an entity doesn't have an item for.
Here's my schema:
entities
id name
1 Bob
2 Alice
item_types
id name
1 red
2 yellow
3 green
4 blue
5 orange
items
entity_id item_type_id name
1 1 apple
1 2 banana
1 3 lime
1 3 tree
2 3 money
2 5 traffic cone
I would like to query Bob's id (1) and get this list:
4 blue
5 orange
And query Alice's id (2) and get:
1 red
2 yellow
4 blue
It's probably starting me in the face. I'm gonna keep working on it but I bet you SO peeps beat me to it. Thank you kindly for your time.
select id, name
from item_types
where id not in
(select i.item_type_id
from items i
inner join entities e
on e.id = t.entity_id
where e.Name = 'Bob')
or (sometimes faster, but optimizers are getting better all the time):
select disctinct t.id, t.name
from item_types t
left outer join items i
on i.item_type_id = t.id
left outer join entities e
on e.id = i.entity_id
and e.Name = 'Bob'
where e.id is null
for Bob
SELECT
t.id, t.name
FROM
items i
INNER JOIN
entities e ON e.id = i.entity_id
INNER JOIN
item_types t ON t.id = i.item_type_id
WHERE
e.id <> 1
for Alice just swap e.id <> 1 to e.id <> 2
I think this is what you are looking for:
SELECT id, name
FROM item_types
WHERE id NOT IN
(
SELECT DISTINCT item_type_id
FROM items
WHERE entity_id = 1
)
The "entity_id = 1" represents Bob, change it as necessary.
I will rework this to make it better, but here is a working solution
set nocount on
go
drop table #entities
drop table #itemtype
drop table #items
create table #Entities
(
EntityId int,
EntityName varchar (250)
)
create table #ItemType
(
ItemTypeId int,
ItemTypeName varchar (250)
)
create table #Items
(
EntityId int,
ItemTypeId int,
ItemName varchar (250)
)
go
insert into #entities values (1, 'Bob')
insert into #entities values (2, 'Alice')
go
insert into #ItemType values (1, 'red')
insert into #ItemType values (2, 'yellow')
insert into #ItemType values (3, 'green')
insert into #ItemType values (4, 'blue')
insert into #ItemType values (5, 'orange')
go
insert into #Items values (1, 1, 'apple')
insert into #Items values (1, 2, 'banana')
insert into #Items values (1, 3, 'lime')
insert into #Items values (1, 3, 'tree')
insert into #Items values (2, 3, 'money')
insert into #Items values (2, 5, 'traffic cone')
go
;WITH ENTITY AS (
SELECT #Entities.EntityId, EntityName, ItemTypeId, ItemName
FROM #Entities, #Items
WHERE #Entities.EntityId = #Items.EntityId
AND #Entities.EntityName = 'Bob'
)
SELECT #ItemType.* FROM ENTITY
RIGHT JOIN #ItemType ON ENTITY.ItemTypeId = #ItemType.ItemTypeId
WHERE EntityId is NULL
;WITH ENTITY AS (
SELECT #Entities.EntityId, EntityName, ItemTypeId, ItemName
FROM #Entities, #Items
WHERE #Entities.EntityId = #Items.EntityId
AND #Entities.EntityName = 'Alice'
)
SELECT #ItemType.* FROM ENTITY
RIGHT JOIN #ItemType ON ENTITY.ItemTypeId = #ItemType.ItemTypeId
WHERE EntityId is NULL