加入递归查询
Join with recursive query
设置
我有下表(简化):
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);
这里是一些测试数据:
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);
我能做什么
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;
这为某个人提供了此人分配到的所有 TimeSlots 的列表以及 TimeSlot 所属的叶类别的名称。例如,对于 ID 为 401 的人,它给出:
TimeSlotId Name
---------------------
301 cat 1.1.1
302 cat 1.2
通过以下递归查询,我还可以从某个类别中获取所有祖先直到根类别:
;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;
例如,对于 ID 为 111 的类别,我得到:
Name
---------
cat 1.1.1
cat 1.1
cat 1
我想做什么
我需要的是分配给一个人的时间段列表,以及该时间段的类别名称,直到根类别。因此,对于 ID 为 401 的人,结果应如下所示:
TimeSlotId Name
---------------------
301 cat 1.1.1
301 cat 1.1
301 cat 1
302 cat 1.2
302 cat 1
我无法弄清楚如何组合以上两个查询以获得预期的结果。
我试过的
我希望这些方面的东西可以奏效:
;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;
这将处理递归类别并提供基于 PersonId 或 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
可能有更好的写法,但会按照您的要求进行,并且应该会带您到达需要去的地方。 'BASE' 不符合其原始目的,但仍显示您的人物和类别之间的相关性,例如BASE 0 表示该记录中的类别直接分配给此人。所以,我为此留下了它。谢谢
希望对您有所帮助:)
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
用户定义函数 cross apply
在这种情况下非常有用。
--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;
声明 @PersonId int =401;
IF OBJECT_ID('tempdb.dbo.#TimeSlot') 不为空
下降 TABLE #TimeSlot
SELECT DISTINCT DENSE_RANK() OVER(ORDER BY t.TimeSlotId ) ID, t.TimeSlotId,c.CategoryId
进入#TimeSlot
来自 PersonTimeSlotAssignment p
INNER JOIN 时隙 t
ON p.TimeSlotId=t.TimeSlotId
INNER JOIN 类别 c
ON c.CategoryId=t.CategoryId
WHERE PersonId=@PersonId
IF OBJECT_ID('tempdb.dbo.#Output') 不为空
下降 TABLE #输出
创建 TABLE #Output(timeSlotId INT,CategoryId INT)
声明@id INT=1 声明@timeSlotId 为 INT 声明@CategoryId INT
WHILE( SELECT id FROM #TimeSlot WHERE id=@id) 不为空
开始
SELECT @timeSlotId=TimeSlotId,@CategoryId=CategoryId 来自 #TimeSlot WHERE ID=@id
插入#Output SELECT @timeSlotId,@CategoryId
WHILE( SELECT ParentCategoryId FROM Category WHERE CategoryId=@CategoryId) 不为空
开始
SELECT @CategoryId=ParentCategoryId FROM Category WHERE CategoryId=@CategoryId
插入#Output SELECT @timeSlotId,@CategoryId
结束
SET @id=@id+1
结束
SELECT a.timeSlotId,c.Name FROM #Output a INNER JOIN 类别 c ON a.CategoryId=c.CategoryId
设置
我有下表(简化):
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);
这里是一些测试数据:
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);
我能做什么
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;
这为某个人提供了此人分配到的所有 TimeSlots 的列表以及 TimeSlot 所属的叶类别的名称。例如,对于 ID 为 401 的人,它给出:
TimeSlotId Name
---------------------
301 cat 1.1.1
302 cat 1.2
通过以下递归查询,我还可以从某个类别中获取所有祖先直到根类别:
;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;
例如,对于 ID 为 111 的类别,我得到:
Name
---------
cat 1.1.1
cat 1.1
cat 1
我想做什么
我需要的是分配给一个人的时间段列表,以及该时间段的类别名称,直到根类别。因此,对于 ID 为 401 的人,结果应如下所示:
TimeSlotId Name
---------------------
301 cat 1.1.1
301 cat 1.1
301 cat 1
302 cat 1.2
302 cat 1
我无法弄清楚如何组合以上两个查询以获得预期的结果。
我试过的
我希望这些方面的东西可以奏效:
;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;
这将处理递归类别并提供基于 PersonId 或 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
可能有更好的写法,但会按照您的要求进行,并且应该会带您到达需要去的地方。 'BASE' 不符合其原始目的,但仍显示您的人物和类别之间的相关性,例如BASE 0 表示该记录中的类别直接分配给此人。所以,我为此留下了它。谢谢
希望对您有所帮助:)
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
用户定义函数 cross apply
在这种情况下非常有用。
--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;
声明 @PersonId int =401;
IF OBJECT_ID('tempdb.dbo.#TimeSlot') 不为空
下降 TABLE #TimeSlot
SELECT DISTINCT DENSE_RANK() OVER(ORDER BY t.TimeSlotId ) ID, t.TimeSlotId,c.CategoryId
进入#TimeSlot
来自 PersonTimeSlotAssignment p
INNER JOIN 时隙 t
ON p.TimeSlotId=t.TimeSlotId
INNER JOIN 类别 c
ON c.CategoryId=t.CategoryId
WHERE PersonId=@PersonId
IF OBJECT_ID('tempdb.dbo.#Output') 不为空
下降 TABLE #输出
创建 TABLE #Output(timeSlotId INT,CategoryId INT)
声明@id INT=1 声明@timeSlotId 为 INT 声明@CategoryId INT
WHILE( SELECT id FROM #TimeSlot WHERE id=@id) 不为空
开始
SELECT @timeSlotId=TimeSlotId,@CategoryId=CategoryId 来自 #TimeSlot WHERE ID=@id
插入#Output SELECT @timeSlotId,@CategoryId
WHILE( SELECT ParentCategoryId FROM Category WHERE CategoryId=@CategoryId) 不为空
开始
SELECT @CategoryId=ParentCategoryId FROM Category WHERE CategoryId=@CategoryId
插入#Output SELECT @timeSlotId,@CategoryId
结束
SET @id=@id+1
结束
SELECT a.timeSlotId,c.Name FROM #Output a INNER JOIN 类别 c ON a.CategoryId=c.CategoryId