Vous pouvez en fait faire tout cela dans une seule requête de sélection CTE, sans utiliser de fonctions. Voici comment :
Considérons d'abord cette structure de table parent/enfant :
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));
(J'ai utilisé P et C pour économiser sur la frappe !)
Et ajoutons quelques données de test, correspondant à celles du demandeur :
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');
Puis enfin, l'expression CTE :
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
Résultat :
ID Description List
-- -------------- --------
20 Pah Pah Emily,Dave
36 Blah Blah Bob,Gary,Reginald
Pour expliquer ce qui se passe ici, je vais décrire chaque partie du CTE et ce qu'il fait.
Premiers éléments regarde tous les enfants et trouve l'ID le plus bas dans chaque groupe parent à utiliser comme point d'ancrage pour le prochain SELECT récursif :
FirstItems (PID, FirstCID) AS (
SELECT C.PID, MIN(C.ID)
FROM C
GROUP BY C.PID
)
Liste des sous-éléments est un SELECT récursif qui sélectionne l'enfant le plus bas de la requête précédente et alloue un numéro d'élément incrémentiel à chaque enfant à partir de 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
)
Le problème est qu'il duplique et répète beaucoup d'éléments, donc ItemList le filtre pour ne choisir que le maximum de chaque groupe :
ItemList (PID, CID, ItemNum) AS (
SELECT PID, CID, MAX(ItemNum)
FROM SubItemList
GROUP BY PID, CID
)
Nous avons maintenant une liste d'identification des parents avec chacun de leurs enfants numérotés de 1 à x :
PID CID ItemNum
----------- ----------- -----------
36 1 1
36 2 2
36 3 3
20 4 1
20 5 2
Liste des sous-tableaux prend les lignes enfants, se joint récursivement à la liste des nombres et commence à ajouter toutes les descriptions les unes aux autres, en commençant par une seule description :
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)
)
Le résultat est maintenant :
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
Il nous suffit donc de nous débarrasser de toutes les lignes partiellement concaténées.
MaxItems récupère simplement une liste de parents et leurs numéros d'articles les plus élevés, ce qui simplifie un peu la requête suivante :
MaxItems (PID, MaxItem) AS (
SELECT PID, MAX(ItemNum)
FROM SubItemList
GROUP BY PID
)
Liste de tableaux effectue l'élimination finale des lignes partiellement concaténées en utilisant le nombre maximum d'éléments acquis à partir de la requête précédente :
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)
)
Et enfin, il ne reste plus qu'à interroger le résultat :
SELECT P.ID, P.Description, AL.List
FROM ArrayList AL JOIN P ON P.ID = AL.PID
ORDER BY P.ID