替换“存在”相关子查询中的递归CTE?

时间:2011-12-01 11:13:56

标签: sql sql-server-2005 recursion common-table-expression correlated-subquery

我的情况是,我需要能够查看某个人是否在用户/经理层次结构中。

我需要能够针对一组规则为一组用户执行此操作(不要担心这一点,只是为了给它一些上下文)

理想情况下,我想在where子句的相关存在子查询中使用递归CTE。

但这会带来很多问题。

  1. 我认为你根本不能在子查询中使用CTE。
  2. 我在SQL 2005上使用兼容模式80 - 所以我不能使用交叉应用...所以没有列参数进入UDF对我来说: - (
  3. 我认为我要做的是:

    WITH UserHierarchy(UserId, ManagerId)
        AS
        (
            --Anchor Definition
            SELECT [UserId], [ManagerId] FROM [Users] WHERE [ManagerId] = [Rules].[RuleAddedByUserId] -- this needs to bind to an outer query....
            UNION ALL
            --Recursive Member definiation
            SELECT [Users].[UserId], [Users].[ManagerId] FROM [Users] 
            INNER JOIN [UserHierarchy] ON [Users].[ManagerId] = [UserHierarchy].[UserId]
            WHERE [Users].[UserId] <> [Users].[ManagerId] --don't recurse if the anchor definition matches itself (to avoid an infinate loop).
        )
    

    无论如何在兼容模式80中使锚定义动态化吗?还是另一种方法?

3 个答案:

答案 0 :(得分:2)

这样做的一种方法是创建一个递归CTE,对于每个用户,树中该用户的每个祖先都有一行。然后,您可以使用CTE过滤祖先。例如,使用此树:

Bob
|-Alice
  |-Jim

CTE会返回类似的内容:

User  Ancestor Level
----  -------- -----
Bob   NULL     1
Alice Bob      1
Jim   Alice    1
Jim   Bob      2

Level列最终不是很重要,但我在编写查询时发现它有所帮助。

这是一个示例脚本,用于标识层次结构中Alice下的所有用户:

CREATE TABLE Users(
    UserId int NOT NULL PRIMARY KEY,
    Name nvarchar(25),
    ManagerId int
);
GO

INSERT INTO Users (UserId, Name, ManagerId)
SELECT 1, 'Bob', NULL UNION ALL
SELECT 2, 'Steve', 1 UNION ALL
SELECT 3, 'Chris', 2 UNION ALL
SELECT 4, 'Alice', 1 UNION ALL
SELECT 5, 'Roger', 4 UNION ALL
SELECT 6, 'Tony', 5;
GO

WITH all_ancestors AS (
    SELECT
        u.UserId,
        u.Name,
        u.ManagerId AS AncestorId,
        1 AS level
    FROM
        Users AS u
    UNION ALL
    SELECT
        alla.UserId,
        alla.Name,
        u.ManagerId AS AncestorId,
        alla.level + 1
    FROM
            all_ancestors AS alla
        INNER JOIN
            Users AS u
        ON
            alla.AncestorId = u.UserId
)
SELECT
    u.*
FROM
        Users AS u
    INNER JOIN
        all_ancestors AS a
    ON
        u.UserId = a.UserId
WHERE
    a.AncestorId = 4; -- Alice
GO

DROP TABLE Users;
GO

答案 1 :(得分:1)

Users表中可以有多少级别的层次结构?我希望它相当低。我想知道它是否足够低,可以尝试多个嵌套EXISTS测试,如下所示:

… /* your main query here */
WHERE …
  AND EXISTS (
    SELECT *
    FROM [Users] u1
    WHERE [UserID] = @UserID
      AND (
        [ManagerId] = [Rules].[RuleAddedByUserId]
        OR EXISTS (
          SELECT *
          FROM [Users] u2
          WHERE [UserID] = u1.[ManagerID]
            AND (
              [ManagerId] = [Rules].[RuleAddedByUserId]
              OR EXISTS (
                SELECT *
                FROM [Users] u3
                WHERE [UserID] = u2.[ManagerID]
                  AND (
                    [ManagerId] = [Rules].[RuleAddedByUserId]
                    OR EXISTS ( … /* and so on, until you've covered
                                     all possible levels */
                    )
                  )
              )
            )
        )
      )
  )

答案 2 :(得分:0)

我最终到了那里!谢谢大家的帮助。

这是我正在研究的S​​QL的片段。

我只需要改变我的想法,而不是看看在where子句中的管理员下是否存在用户。我需要考虑CTE一个预过滤器,然后构建我需要的所有细节,然后在存在语句之后进行常规过滤(n.b.我没有包括那些为了简洁)。

RulesUserHierarchy(UserId, ManagerId, PushRuleId, OnlyForSubOrdinates) -- Gets only subordinates for rules created by managers. And all users for those created by admin.
AS
(
    --Anchor Definition
    SELECT
         [Users].[UserId]
        ,[Users].[ManagerId]
        ,[RulesAnchor].[PushRuleId]
        ,[RulesAnchor].[OnlyForSubOrdinates]
    FROM [Users]
        CROSS JOIN [Rules] [RulesAnchor] --assume every user is doing every rule at this point (because the recursive statement has to be the first statement), we'll filter later.
    WHERE (([OnlyForSubOrdinates]) = 0 OR ([OnlyForSubOrdinates] = 1 AND [UserId] = [RulesAnchor].[AddedByUserId]))
    UNION ALL

    --Recursive Member definiation
    SELECT
         [Users].[UserId]
        ,[Users].[ManagerId]
        ,[RulesUserHierarchy].[PushRuleId]
        ,[RulesUserHierarchy].[OnlyForSubOrdinates]
    FROM [Users]
        INNER JOIN [RulesUserHierarchy]
            ON [Users].[ManagerId] = [RulesUserHierarchy].[UserId] --recursive hook 
            AND [RulesUserHierarchy].[OnlyForSubOrdinates] = 1 -- no point recursing if it's for everyone, as the anchor will pull back everything for us.
    WHERE [Users].[UserId] <> [Users].[ManagerId] --don't recurse if the anchor definition matches itself (to avoid an infinate loop).
)       
-- simple statement to test recursion above, will be filtering the inclusions here (e.g. the other mega exists statements)
SELECT [UserId], [ManagerId], [PushRuleId], [OnlyForSubOrdinates] FROM [RulesUserHierarchy]

修改

我意识到我不需要递归定义中的交叉连接。虽然它不会影响后者存在的陈述的结果,但它对表现并不好。