SQL查询对等及以上

时间:2017-01-11 21:38:02

标签: sql sql-server

有一些递归查询可用于查找层次结构中的所有子项或父项(例如,在员工 - 经理层次结构中),但我找不到任何同行和上面/父项的示例

EmpID   FName   LName       MgrID
11  Sally   Smith       NULL
1   Alex    Adams       11
2   Barry   Brown       11
3   Lee Osaka       11
4   David   Kennson     11
5   Eric    Bender      11
7   David   Lonning     11
6   Lisa    Kendall     4
8   John    Marshbank   4
12  Barbara ONeill      4
13  Phil    Wilconkinski    4
9   James   Newton      3
10  Terry   OHaire      3

因此,如果我想要EmpID = 6的peer-above,那么它应该返回

EmpID   FName   LName       MgrID
11  Sally   Smith       NULL    grandparent/peer
1   Alex    Adams       11  parent-peer
2   Barry   Brown       11  parent-peer
3   Lee Osaka       11  parent-peer
4   David   Kennson     11  (parent of id=6)
5   Eric    Bender      11  parent-peer
7   David   Lonning     11  parent-peer
6   Lisa    Kendall     4   (id=6)
8   John    Marshbank   4   peer
12  Barbara ONeill      4   peer
13  Phil    Wilconkinski    4   peer

非常感谢任何帮助

1 个答案:

答案 0 :(得分:0)

根据您的表当前结构的方式,实现此目的的一种方法是在递归CTE中包含路径,然后解析路径以确定某人是否属于层次结构中的该路径。

例如,

DECLARE @Table TABLE 
(
    EmpID INT NOT NULL, 
    FName VARCHAR(20) NOT NULL,
    LName VARCHAR(20) NOT NULL,
    MgrID INT
);

INSERT @Table (EmpID, FName, LName, MgrID)
VALUES
    (11, 'Sally', 'Smith', NULL),
    (1, 'Alex', 'Adams', 11),
    (2, 'Barry', 'Brown', 11),
    (3, 'Lee', 'Osaka', 11),
    (4, 'David', 'Kennson', 11),
    (5, 'Eric', 'Bender', 11),
    (7, 'David', 'Lonning', 11),
    (6, 'Lisa', 'Kendall', 4),
    (8, 'John', 'Marshbank', 4),
    (12, 'Barbara', 'ONeill', 4),
    (13, 'Phil', 'Wilconkinski', 4),
    (9, 'James', 'Newton', 3),
    (10, 'Terry', 'OHaire', 3);

WITH CTE AS 
(
    SELECT EmpID, 
           FName, 
           LName, 
           MgrID, 
           Tree = CAST(EmpID AS VARCHAR(255)), 
           Lvl = 1
    FROM @Table
    WHERE MgrID IS NULL

    UNION ALL

    SELECT T.EmpID, 
           T.FName, 
           T.LName, 
           T.MgrID, 
           CAST(CTE.Tree + '-' + CAST(T.EmpID AS VARCHAR(255)) AS VARCHAR(255)), -- Your paths will look like '11-4-6'.
           CTE.Lvl + 1
    FROM CTE
    JOIN @Table T ON T.MgrID = CTE.EmpID
)
SELECT EmpID, 
       FName, 
       LName, 
       MgrID,
       Lvl,
       Direct = CASE MIN(CASE WHEN EmpID = Item THEN 1 ELSE 2 END) WHEN 1 THEN 'Direct' ELSE 'Peer' END
FROM CTE
JOIN 
(
    SELECT Item
    FROM CTE
    CROSS APPLY dbo.[DelimitedSplit8K](Tree, '-') D
    WHERE CTE.EmpID = 6
) D ON D.Item = CTE.EmpID 
    OR D.Item = CTE.MgrID
GROUP BY EmpID, FName, LName, MgrID, Lvl;

这使用拆分功能来分解路径并仅显示该路径中的人员。可以找到分割函数here,如下所示:

CREATE FUNCTION [dbo].[DelimitedSplit8K]
--===== Define I/O parameters
        (@pString VARCHAR(8000), @pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE!  IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
     -- enough to cover VARCHAR(8000)
  WITH E1(N) AS (
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
                ),                          --10E+1 or 10 rows
       E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
       E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
 cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
                     -- for both a performance gain and prevention of accidental "overruns"
                 SELECT TOP (ISNULL(DATALENGTH(@pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
                ),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
                 SELECT 1 UNION ALL
                 SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString,t.N,1) = @pDelimiter
                ),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
                 SELECT s.N1,
                        ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000)
                   FROM cteStart s
                )
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
 SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
        Item       = SUBSTRING(@pString, l.N1, l.L1)
   FROM cteLen l
;