加入递归查询

时间:2016-06-14 21:46:35

标签: sql sql-server common-table-expression 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

我想做什么

我需要的是一个人被分配的TimeSlots列表,并与该TimeSlot的类别名称一起连接到根类别。因此,对于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;

4 个答案:

答案 0 :(得分:1)

这将处理递归类别并提供基于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

可能有更好的方式来写这个,但做了你所问的,应该把你带到你需要去的地方。 &#39; BASE&#39;不符合其原始目的,但仍显示您的人员和类别之间的相关性,例如BASE 0表示该记录中的类别直接分配给该人。所以,我为此留下了它。感谢。

答案 1 :(得分:1)

用户定义的函数和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;

答案 2 :(得分:0)

希望这可以帮助你:)

List

答案 3 :(得分:0)

DECLARE @PersonId int = 401;

如果OBJECT_ID(&#39; tempdb.dbo。#TimeSlot&#39;)不为空 DROP TABLE #TimeSlot

SELECT DISTINCT DENSE_RANK()OVER(ORDER BY t.TimeSlotId)ID,t.TimeSlotId,c.CategoryId
INTO #TimeSlot
来自PersonTimeSlotAssignment p
INNER JOIN TimeSlot t
ON p.TimeSlotId = t.TimeSlotId
INNER JOIN类别c ON c.CategoryId = t.CategoryId
WHERE PersonId = @ PersonId

如果OBJECT_ID(&#39; tempdb.dbo。#Output&#39;)不为空 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(从类别WHERE CategoryId = @ CategoryId中选择ParentCategoryId)不为空 BEGIN
SELECT @ CategoryId = ParentCategoryId FROM Category WHERE CategoryId = @ CategoryId
INSERT INTO #Output SELECT @ timeSlotId,@ CategoryId
END
SET @ id = @ id + 1
结束

SELECT a.timeSlotId,c.Name FROM #Output a INNER JOIN Category c ON a.CategoryId = c.CategoryId