Du kan faktisk gøre alt dette i én CTE-valgsforespørgsel uden at bruge nogen funktioner. Sådan gør du:
Overvej først denne overordnede/underordnede tabelstruktur:
CREATE TABLE P (ID INT PRIMARY KEY, Description VARCHAR(20));
CREATE TABLE C (ID INT PRIMARY KEY, PID INT,
Description VARCHAR(20),
CONSTRAINT fk FOREIGN KEY (PID) REFERENCES P(ID));
(Jeg har brugt P og C til at spare på at skrive!)
Og lad os tilføje nogle testdata, der matcher spørgsmålsstillerens:
INSERT INTO P VALUES (36, 'Blah Blah');
INSERT INTO P VALUES (20, 'Pah Pah');
INSERT INTO C VALUES (1, 36, 'Bob');
INSERT INTO C VALUES (2, 36, 'Gary');
INSERT INTO C VALUES (3, 36, 'Reginald');
INSERT INTO C VALUES (4, 20, 'Emily');
INSERT INTO C VALUES (5, 20, 'Dave');
Så til sidst, CTE-udtrykket:
WITH
FirstItems (PID, FirstCID) AS (
SELECT C.PID, MIN(C.ID)
FROM C
GROUP BY C.PID
),
SubItemList (PID, CID, ItemNum) AS (
SELECT C.PID, C.ID, 1
FROM C JOIN FirstItems FI ON (C.ID = FI.FirstCID)
UNION ALL
SELECT C.PID, C.ID, IL.ItemNum + 1
FROM C JOIN SubItemList IL ON C.PID = IL.PID AND C.ID > CID
),
ItemList (PID, CID, ItemNum) AS (
SELECT PID, CID, MAX(ItemNum)
FROM SubItemList
GROUP BY PID, CID
),
SubArrayList (PID, CID, Array, ItemNum) AS (
SELECT IL.PID, IL.CID, CAST(C.Description AS VARCHAR(MAX)), IL.ItemNum
FROM ItemList IL JOIN C ON IL.CID = C.ID
WHERE IL.ItemNum = 1
UNION ALL
SELECT IL.PID, IL.CID, AL.Array + ',' + CAST(C.Description AS VARCHAR(MAX)), IL.ItemNum
FROM ItemList IL
JOIN SubArrayList AL ON (IL.PID = AL.PID AND IL.ItemNum = AL.ItemNum + 1)
JOIN C ON (IL.CID = C.ID)
),
MaxItems (PID, MaxItem) AS (
SELECT PID, MAX(ItemNum)
FROM SubItemList
GROUP BY PID
),
ArrayList (PID, List) AS (
SELECT SAL.PID, SAL.Array
FROM SubArrayList SAL
JOIN MaxItems MI ON (SAL.PID = MI.PID AND SAL.ItemNum = MI.MaxItem)
)
SELECT P.ID, P.Description, AL.List
FROM ArrayList AL JOIN P ON P.ID = AL.PID
ORDER BY P.ID
Resultat:
ID Description List
-- -------------- --------
20 Pah Pah Emily,Dave
36 Blah Blah Bob,Gary,Reginald
For at forklare, hvad der foregår her, vil jeg beskrive hver del af CTE'en, og hvad den gør.
FirstItems ser på alle børnene og finder det laveste ID i hver forældregruppe, der skal bruges som anker for det næste rekursive SELECT:
FirstItems (PID, FirstCID) AS (
SELECT C.PID, MIN(C.ID)
FROM C
GROUP BY C.PID
)
SubItemList er et rekursivt SELECT, der opfanger det laveste barn fra den forrige forespørgsel og tildeler et stigende varenummer til hvert barn fra 1:
SubItemList (PID, CID, ItemNum) AS (
SELECT C.PID, C.ID, 1
FROM C JOIN FirstItems FI ON (C.ID = FI.FirstCID)
UNION ALL
SELECT C.PID, C.ID, IL.ItemNum + 1
FROM C JOIN SubItemList IL ON C.PID = IL.PID AND C.ID > CID
)
Problemet er, at det dupper og gentager mange af emnerne, så ItemList filtrerer det til bare at vælge maks. fra hver gruppe:
ItemList (PID, CID, ItemNum) AS (
SELECT PID, CID, MAX(ItemNum)
FROM SubItemList
GROUP BY PID, CID
)
Nu har vi en ID-liste over forældre med hvert af deres børn nummereret fra 1 til x:
PID CID ItemNum
----------- ----------- -----------
36 1 1
36 2 2
36 3 3
20 4 1
20 5 2
SubArrayList tager børnerækkerne, slutter sig rekursivt til tallisten og begynder at tilføje alle beskrivelserne til hinanden, begyndende med en enkelt beskrivelse:
SubArrayList (PID, CID, Array, ItemNum) AS (
SELECT IL.PID, IL.CID, CAST(C.Description AS VARCHAR(MAX)), IL.ItemNum
FROM ItemList IL JOIN C ON IL.CID = C.ID
WHERE IL.ItemNum = 1
UNION ALL
SELECT IL.PID, IL.CID, AL.Array + ',' + CAST(C.Description AS VARCHAR(MAX)), IL.ItemNum
FROM ItemList IL
JOIN SubArrayList AL ON (IL.PID = AL.PID AND IL.ItemNum = AL.ItemNum + 1)
JOIN C ON (IL.CID = C.ID)
)
Resultatet er nu:
PID CID Array ItemNum
----------- ----------- ----------------- -----------
36 1 Bob 1
20 4 Emily 1
20 5 Emily,Dave 2
36 2 Bob,Gary 2
36 3 Bob,Gary,Reginald 3
Så alt, hvad vi skal gøre, er at slippe af med alle de delvist sammenkædede rækker.
MaxItems griber blot en liste over forældre og deres højeste varenumre, hvilket gør følgende forespørgsel en smule enklere:
MaxItems (PID, MaxItem) AS (
SELECT PID, MAX(ItemNum)
FROM SubItemList
GROUP BY PID
)
ArrayList udfører den endelige aflivning af de delvist sammenkædede rækker ved hjælp af det maksimale varenummer hentet fra den forrige forespørgsel:
ArrayList (PID, List) AS (
SELECT SAL.PID, SAL.Array
FROM SubArrayList SAL
JOIN MaxItems MI ON (SAL.PID = MI.PID AND SAL.ItemNum = MI.MaxItem)
)
Og til sidst er der kun tilbage at spørge efter resultatet:
SELECT P.ID, P.Description, AL.List
FROM ArrayList AL JOIN P ON P.ID = AL.PID
ORDER BY P.ID