【问题标题】:Join with recursive query加入递归查询
【发布时间】:2016-06-14 21:46:35
【问题描述】:

设置

我有以下表格(简化):

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;

这为某个人提供了此人分配到的所有 TimeSlot 的列表以及 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

我想做什么

我需要的是一个分配给一个人的 TimeSlot 列表,以及该 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;

【问题讨论】:

    标签: sql sql-server common-table-expression recursive-query


    【解决方案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】:

      这将处理递归类别并根据 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' 并没有达到其最初的目的,但仍然显示了您的 Person 和 Category 之间的相关性,例如BASE 0 表示该记录中的类别直接分配给该人。所以,我把它留给了那个。谢谢。

      【讨论】:

      • 感谢您的回答!这基本上返回了所有类别的层次结构。但是我仍然看不到如何为一个人获取所有分配的 TimeSlots,加入该 TimeSlot 所属的类别及其所有父类别。
      • 请查看我的更新回复。在 CTE 中包含正确的联接,您应该一切顺利。
      【解决方案3】:

      希望对你有帮助:)

      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
      

      【讨论】:

      • 感谢您的回答。这仅处理三个级别的类别,对吗?但是我的类别可以任意嵌套深度,所以我正在寻找具有适当递归的解决方案。
      • 我添加了另一个答案——请在下面查看
      • 对不起,它不允许编辑我自己的帖子,不允许添加新帖子
      【解决方案4】:

      声明@PersonId int =401;

      IF OBJECT_ID('tempdb.dbo.#TimeSlot') 不为空
      删除表#TimeSlot

      SELECT DISTINCT DENSE_RANK() OVER(ORDER BY t.TimeSlotId ) ID, t.TimeSlotId,c.CategoryId
      INTO #TimeSlot
      FROM PersonTimeSlotAssignment p
      INNER JOIN 时隙 t
      ON p.TimeSlotId=t.TimeSlotId
      内连接类别 c 开启 c.CategoryId=t.CategoryId
      WHERE PersonId=@PersonId

      IF OBJECT_ID('tempdb.dbo.#Output') 不为空
      删除表#输出
      创建表 #Output(timeSlotId INT,CategoryId INT)
      声明 @id INT=1 声明 @timeSlotId 为 INT 声明 @CategoryId INT
      WHILE(从 #TimeSlot 选择 id,其中 id=@id)不为空
      开始
      SELECT @timeSlotId=TimeSlotId,@CategoryId=CategoryId FROM #TimeSlot WHERE ID=@id
      插入#Output SELECT @timeSlotId,@CategoryId
      WHILE( SELECT ParentCategoryId FROM Category WHERE CategoryId=@CategoryId) 不为空
      开始
      SELECT @CategoryId=ParentCategoryId 从类别 WHERE CategoryId=@CategoryId
      插入#Output SELECT @timeSlotId,@CategoryId
      结束
      设置@id=@id+1
      结束

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

      【讨论】:

      • 当我尝试编辑我的代码时它不允许发布.. 显示一些错误对不起我的代码但适用于你的情况
      猜你喜欢
      • 2011-08-05
      • 1970-01-01
      • 1970-01-01
      • 2011-04-24
      • 1970-01-01
      • 1970-01-01
      • 2016-06-11
      • 2012-01-29
      • 2011-04-07
      相关资源
      最近更新 更多