进行递归自联接的最简单方法?


100

在SQL Server中进行递归自联接的最简单方法是什么?我有一张这样的桌子:

PersonID | Initials | ParentID
1          CJ         NULL
2          EB         1
3          MB         1
4          SW         2
5          YT         NULL
6          IS         5

而且我希望能够只获取与特定人员开始的层次结构相关的记录。因此,如果我通过PersonID = 1请求CJ的层次结构,则会得到:

PersonID | Initials | ParentID
1          CJ         NULL
2          EB         1
3          MB         1
4          SW         2

对于EB,我会得到:

PersonID | Initials | ParentID
2          EB         1
4          SW         2

除了基于一堆联接的固定深度响应之外,我对此深感困惑。这样做会发生,因为我们没有很多级别,但我想正确地做。

谢谢!克里斯。


2
您正在使用哪个版本的SQL Server?即SQL 2000,2005,2008?
boydc7

2
关于递归查询的SO问题:stackoverflow.com/search?
q=sql

Answers:


112
WITH    q AS 
        (
        SELECT  *
        FROM    mytable
        WHERE   ParentID IS NULL -- this condition defines the ultimate ancestors in your chain, change it as appropriate
        UNION ALL
        SELECT  m.*
        FROM    mytable m
        JOIN    q
        ON      m.parentID = q.PersonID
        )
SELECT  *
FROM    q

通过添加排序条件,可以保留树的顺序:

WITH    q AS 
        (
        SELECT  m.*, CAST(ROW_NUMBER() OVER (ORDER BY m.PersonId) AS VARCHAR(MAX)) COLLATE Latin1_General_BIN AS bc
        FROM    mytable m
        WHERE   ParentID IS NULL
        UNION ALL
        SELECT  m.*,  q.bc + '.' + CAST(ROW_NUMBER() OVER (PARTITION BY m.ParentID ORDER BY m.PersonID) AS VARCHAR(MAX)) COLLATE Latin1_General_BIN
        FROM    mytable m
        JOIN    q
        ON      m.parentID = q.PersonID
        )
SELECT  *
FROM    q
ORDER BY
        bc

通过更改ORDER BY条件,您可以更改兄弟姐妹的顺序。


7
+1,但克里斯需要PersonID = theIdYouAreLookingFor代替ParentID IS NULL
Heinzi

我在SO上发布了一个新问题,stackoverflow.com
Kishore Kumar

@Aaroninus:父节点由WITH子句中最上面的(锚定)查询定义。如果需要特定信息,请在sqlfiddle.com上创建小提琴,然后在此处发布链接。
Quassnoi 2014年

24

使用CTE,您可以采用这种方式

DECLARE @Table TABLE(
        PersonID INT,
        Initials VARCHAR(20),
        ParentID INT
)

INSERT INTO @Table SELECT     1,'CJ',NULL
INSERT INTO @Table SELECT     2,'EB',1
INSERT INTO @Table SELECT     3,'MB',1
INSERT INTO @Table SELECT     4,'SW',2
INSERT INTO @Table SELECT     5,'YT',NULL
INSERT INTO @Table SELECT     6,'IS',5

DECLARE @PersonID INT

SELECT @PersonID = 1

;WITH Selects AS (
        SELECT *
        FROM    @Table
        WHERE   PersonID = @PersonID
        UNION ALL
        SELECT  t.*
        FROM    @Table t INNER JOIN
                Selects s ON t.ParentID = s.PersonID
)
SELECT  *
FROm    Selects

2
重要的WHERE PersonID = @PersonID的完整完整答案
Oli B

5

Quassnoi查询更改了大表。多于10个孩子的父母:将row_number()格式化为str(5)

带q AS 
        (
        SELECT m。*,CAST(str(ROW_NUMBER()OVER(OR BY BY m.ordernum),5)AS VARCHAR(MAX))COLLICA Latin1_General_BIN AS bc
        来自#tm
        父母ID = 0
        全联盟
        选择m。*,q.bc +'。' + str(ROW_NUMBER()OVER(PARTITION BY m.ParentID ORDER BY m.ordernum),5)COLLECTION Latin1_General_BIN
        来自#tm
        加入q
        开启m.parentID = q.DBID
        )
选择 *
从q
订购
        公元前


2

按照所示示例,SQL 2005或更高版本是CTE的标准处理方式。

SQL 2000,您可以使用UDF实现-

CREATE FUNCTION udfPersonAndChildren
(
    @PersonID int
)
RETURNS @t TABLE (personid int, initials nchar(10), parentid int null)
AS
begin
    insert into @t 
    select * from people p      
    where personID=@PersonID

    while @@rowcount > 0
    begin
      insert into @t 
      select p.*
      from people p
        inner join @t o on p.parentid=o.personid
        left join @t o2 on p.personid=o2.personid
      where o2.personid is null
    end

    return
end

(这将在2005年生效,但这不是标准的操作方式。也就是说,如果您发现更简单的工作方式,请使用它)

如果确实需要在SQL7中执行此操作,则可以在sproc中大致执行上述操作,但不能从中选择-SQL7不支持UDF。


2

检查以下内容有助于理解CTE递归的概念

DECLARE
@startDate DATETIME,
@endDate DATETIME

SET @startDate = '11/10/2011'
SET @endDate = '03/25/2012'

; WITH CTE AS (
    SELECT
        YEAR(@startDate) AS 'yr',
        MONTH(@startDate) AS 'mm',
        DATENAME(mm, @startDate) AS 'mon',
        DATEPART(d,@startDate) AS 'dd',
        @startDate 'new_date'
    UNION ALL
    SELECT
        YEAR(new_date) AS 'yr',
        MONTH(new_date) AS 'mm',
        DATENAME(mm, new_date) AS 'mon',
        DATEPART(d,@startDate) AS 'dd',
        DATEADD(d,1,new_date) 'new_date'
    FROM CTE
    WHERE new_date < @endDate
    )
SELECT yr AS 'Year', mon AS 'Month', count(dd) AS 'Days'
FROM CTE
GROUP BY mon, yr, mm
ORDER BY yr, mm
OPTION (MAXRECURSION 1000)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.