Özyinelemeli bir kendi kendine katılmanın en basit yolu?


102

SQL Server'da özyinelemeli kendi kendine birleştirme yapmanın en basit yolu nedir? Bunun gibi bir masam var:

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

Ve sadece belirli bir kişiden başlayarak bir hiyerarşi ile ilgili kayıtları alabilmek istiyorum. Bu yüzden CJ'nin hiyerarşisini PersonID = 1 ile talep etsem şunu elde ederim:

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

Ve EB'ler için şunları alırdım:

PersonID | Initials | ParentID
2          EB         1
4          SW         2

Bu konuda biraz takılıp kaldım, bir grup birleşime dayanan sabit derinlikli bir yanıttan ayrı olarak nasıl yapılacağını düşünemiyorum. Bu olduğu gibi yapardı çünkü çok fazla seviyemiz olmayacak ama bunu düzgün bir şekilde yapmak istiyorum.

Teşekkürler! Chris.


2
Hangi SQL Server sürümünü kullanıyorsunuz? yani Sql 2000, 2005, 2008?
boydc7

2
Özyinelemeli sorgularla ilgili SO soruları: stackoverflow.com/search?q=sql-server+recursive
OMG Ponies

Yanıtlar:


115
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

Sıralama koşulunu ekleyerek ağaç sırasını koruyabilirsiniz:

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

Değiştirerek ORDER BYkoşulunu Eğer kardeşler sıralamasını değiştirebilir.


7
1 Chris ihtiyacı olacağını hariç PersonID = theIdYouAreLookingForyerine ParentID IS NULL.
Heinzi


@Aaroninus: Ana düğüm, cümlede en üstteki (çapa) sorgu tarafından tanımlanır WITH. Ayrıntılara ihtiyacınız varsa, lütfen sqlfiddle.com'da bir keman oluşturun ve bağlantıyı buraya gönderin.
Quassnoi

Bu sorgu benim için ilk satırı "RECURSIVE q AS İLE" olarak değiştirene kadar çalışmaz, referans olarak "10.3.23-MariaDB-0 + deb10u1" kullanıyorum
Josh McGee

@JoshMcGee: Neden bir SQL Server sorgusunun MariaDB'de değişiklik yapmadan çalışmasını bekliyordunuz?
Quassnoi

25

CTE'leri kullanarak bunu bu şekilde yapabilirsiniz

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
Önemli WHERE PersonID = @PersonID
Oli B

5

Büyük tablo için değişiklikle Quassnoi sorgusu. 10'dan fazla çocuğu olan ebeveynler: str (5) satır_sayısı () olarak biçimlendirme

Q AS İLE 
        (
        SELECT m. *, CAST (str (ROW_NUMBER () OVER (ORDER BY m.ordernum), 5) AS VARCHAR (MAX)) COLLATE Latin1_General_BIN AS bc
        #Tm'den
        WHERE ParentID = 0
        BİRLİĞİ TÜMÜ
        SEÇİN m. *, Q.bc + '.' + str (ROW_NUMBER () OVER (PARTITION BY m.ParentID ORDER BY m.ordernum), 5) COLLATE Latin1_General_BIN
        #Tm'den
        JOIN q
        ON m.parentID = q.DBID
        )
SEÇ *
Q'DAN
TARAFINDAN SİPARİŞ
        M.Ö


2

SQL 2005 veya sonrası, CTE'ler gösterilen örneklere göre standart yöntemdir.

SQL 2000, bunu UDF'leri kullanarak yapabilirsiniz -

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'te işe yarayacak, bunu yapmanın standart yolu değil. Bununla birlikte, çalışmanın daha kolay yolunu bulursanız, onunla koşun)

Bunu gerçekten SQL7'de yapmanız gerekiyorsa, yukarıdakileri bir sproc'ta yapabilirsiniz, ancak ondan seçim yapamazsınız - SQL7, UDF'leri desteklemez.


2

CTE özyineleme kavramını anlamaya yardımcı olmak için aşağıdakileri kontrol edin

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)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.