Requête récursive utilisant CTE dans SQL Server 2005
-
05-07-2019 - |
Question
OK, voici ce que j'essaie de faire. J'utilise une requête CTE dans MSSQL2005. L’objectif de la requête est de récidiver dans les relations parent-enfant des catégories de produits et de renvoyer le nombre de produits sous chaque catégorie (y compris les produits contenus dans les catégories enfants).
Ma version actuelle ne renvoie que le nombre de produits de la catégorie affichée. Il ne tient pas compte des produits pouvant être contenus dans l'un de ses enfants.
Le vidage de la base de données pour reproduire le problème, ainsi que la requête que j'ai utilisée et l'explication qui suit suivent:
CREATE TABLE [Categories] (
[CategoryID] INT,
[Name] NCHAR(150)
)
GO
/* Data for the `Query_Result` table (Records 1 - 5) */
INSERT INTO [Categories] ([CategoryID], [Name])
VALUES (942, N'Diagnostic Equipment')
GO
INSERT INTO [Categories] ([CategoryID], [Name])
VALUES (943, N'Cardiology')
GO
INSERT INTO [Categories] ([CategoryID], [Name])
VALUES (959, N'Electrodes')
GO
INSERT INTO [Categories] ([CategoryID], [Name])
VALUES (960, N'Stress Systems')
GO
INSERT INTO [Categories] ([CategoryID], [Name])
VALUES (961, N'EKG Machines')
GO
CREATE TABLE [Categories_XREF] (
[CatXRefID] INT,
[CategoryID] INT,
[ParentID] INT
)
GO
/* Data for the `Query_Result` table (Records 1 - 5) */
INSERT INTO [Categories_XREF] ([CatXRefID], [CategoryID], [ParentID])
VALUES (827, 942, 0)
GO
INSERT INTO [Categories_XREF] ([CatXRefID], [CategoryID], [ParentID])
VALUES (828, 943, 942)
GO
INSERT INTO [Categories_XREF] ([CatXRefID], [CategoryID], [ParentID])
VALUES (928, 959, 943)
GO
INSERT INTO [Categories_XREF] ([CatXRefID], [CategoryID], [ParentID])
VALUES (929, 960, 943)
GO
INSERT INTO [Categories_XREF] ([CatXRefID], [CategoryID], [ParentID])
VALUES (930, 961, 943)
GO
CREATE TABLE [Products_Categories_XREF] (
[ID] INT,
[ProductID] INT,
[CategoryID] INT
)
GO
/* Data for the `Query_Result` table (Records 1 - 13) */
INSERT INTO [Products_Categories_XREF] ([ID], [ProductID], [CategoryID])
VALUES (252065, 12684, 961)
GO
INSERT INTO [Products_Categories_XREF] ([ID], [ProductID], [CategoryID])
VALUES (252066, 12685, 959)
GO
INSERT INTO [Products_Categories_XREF] ([ID], [ProductID], [CategoryID])
VALUES (252067, 12686, 960)
GO
INSERT INTO [Products_Categories_XREF] ([ID], [ProductID], [CategoryID])
VALUES (252068, 12687, 961)
GO
INSERT INTO [Products_Categories_XREF] ([ID], [ProductID], [CategoryID])
VALUES (252128, 12738, 961)
GO
INSERT INTO [Products_Categories_XREF] ([ID], [ProductID], [CategoryID])
VALUES (252129, 12739, 959)
GO
INSERT INTO [Products_Categories_XREF] ([ID], [ProductID], [CategoryID])
VALUES (252130, 12740, 959)
GO
INSERT INTO [Products_Categories_XREF] ([ID], [ProductID], [CategoryID])
VALUES (252131, 12741, 959)
GO
INSERT INTO [Products_Categories_XREF] ([ID], [ProductID], [CategoryID])
VALUES (252132, 12742, 959)
GO
INSERT INTO [Products_Categories_XREF] ([ID], [ProductID], [CategoryID])
VALUES (252133, 12743, 959)
GO
INSERT INTO [Products_Categories_XREF] ([ID], [ProductID], [CategoryID])
VALUES (252134, 12744, 959)
GO
INSERT INTO [Products_Categories_XREF] ([ID], [ProductID], [CategoryID])
VALUES (252135, 12745, 959)
GO
INSERT INTO [Products_Categories_XREF] ([ID], [ProductID], [CategoryID])
VALUES (252136, 12746, 959)
GO
CREATE TABLE [Products] (
[ProductID] INT
)
GO
/* Data for the `Query_Result` table (Records 1 - 13) */
INSERT INTO [Products] ([ProductID])
VALUES (12684)
GO
INSERT INTO [Products] ([ProductID])
VALUES (12685)
GO
INSERT INTO [Products] ([ProductID])
VALUES (12686)
GO
INSERT INTO [Products] ([ProductID])
VALUES (12687)
GO
INSERT INTO [Products] ([ProductID])
VALUES (12738)
GO
INSERT INTO [Products] ([ProductID])
VALUES (12739)
GO
INSERT INTO [Products] ([ProductID])
VALUES (12740)
GO
INSERT INTO [Products] ([ProductID])
VALUES (12741)
GO
INSERT INTO [Products] ([ProductID])
VALUES (12742)
GO
INSERT INTO [Products] ([ProductID])
VALUES (12743)
GO
INSERT INTO [Products] ([ProductID])
VALUES (12744)
GO
INSERT INTO [Products] ([ProductID])
VALUES (12745)
GO
INSERT INTO [Products] ([ProductID])
VALUES (12746)
GO
Voici la requête CTE que j'utilisais:
WITH ProductCategories (CategoryID, ParentID, [Name], Level)
AS
(
-- Anchor member definition
SELECT
C.CategoryID,
CXR.ParentID,
C.Name,
0 AS Level
FROM
Categories C,
Categories_XRef CXR
WHERE
C.CategoryID = CXR.CategoryID
AND CXR.ParentID = 0
UNION ALL
-- Recursive member definition
SELECT
C.CategoryID,
CXR.ParentID,
C.Name,
Level + 1
FROM
Categories C,
Categories_XRef CXR,
ProductCategories AS PC
WHERE
C.CategoryID = CXR.CategoryID
AND CXR.ParentID = PC.CategoryID
)
SELECT
PC.ParentID,
PC.CategoryID,
PC.Name,
PC.Level,
(SELECT
Count(P.ProductID)
FROM
Products P,
Products_Categories_XREF PCXR
WHERE
P.ProductID = PCXR.ProductID
AND PCXR.CategoryID = PC.CategoryID
) as ProductCount
FROM
Categories C,
ProductCategories PC
WHERE
PC.CategoryID = C.CategoryID
AND PC.ParentID = 943
ORDER BY
Level, PC.Name
Commencez par modifier le paramètre "PC.ParentID". à 943. Trois enregistrements renvoyés indiquent le nombre de produits pour chaque catégorie affichée.
Modifiez maintenant le ParentID de 943 à 942 , puis réexécutez-le. Vous verrez à présent 1 résultat renvoyé appelé "Cardiologie", mais il indique 0 produits Dans cette catégorie, il y a des enfants (que vous avez vus précédemment) qui contiennent des produits. Ma grande question est, à ce niveau (parent 942), comment puis-je lui faire compter les produits contenus dans les enfants ci-dessous pour afficher 13 comme "ProductCount"? Je pense que j'ai peut-être besoin d'une autre méthode de récursivité. J'ai essayé cela, mais sans succès.
Je suis ouvert à une procédure stockée qui ferait ce que je recherche. Je ne suis pas fixé sur un moyen en particulier. Donc, toute autre suggestion serait appréciée.
La solution
modifier OK, après avoir lu les conditions requises et réfléchi un peu, c’est assez facile (je pense!)
Le fait est que nous voulons deux choses: la hiérarchie des catégories et le nombre de produits. La hiérarchie est effectuée par un CTE récursif, et le comptage est fait en dehors de cela:
-- The CTE returns the cat hierarchy:
-- one row for each ancestor-descendant relationship
-- (including the self-relationship for each category)
WITH CategoryHierarchy AS (
-- Anchor member: self relationship for each category
SELECT CategoryID AS Ancestor, CategoryID AS Descendant
FROM Categories
UNION ALL
-- Recursive member: for each row, select the children
SELECT ParentCategory.Ancestor, Children.CategoryID
FROM
CategoryHierarchy AS ParentCategory
INNER JOIN Categories_XREF AS Children
ON ParentCategory.Descendant = Children.ParentID
)
SELECT CH.Ancestor, COUNT(ProductID) AS ProductsInTree
-- outer join to product-categories to include
-- all categories, even those with no products directly associated
FROM CategoryHierarchy CH
LEFT JOIN Products_Categories_XREF PC
ON CH.Descendant = PC.CategoryID
GROUP BY CH.Ancestor
Les résultats sont:
Ancestor ProductsInTree
----------- --------------
942 13
943 13
959 9
960 1
961 3
Je suis redevable à cet article de l'inestimable Itzik Ben-Gan pour avoir ma pensée a démarré. Son livre "Inside MS SQL Server 2005: Interrogation T-SQL" est vivement recommandé.
Autres conseils
Votre instruction WHERE limite le résultat à un parent. Si vous souhaitez voir tous les enfants de moins de 942 ans, spécifiez 942 en tant que racine dans le CTE. Par exemple:
WITH CTE (CategoryID, ParentID, [Name], [Level])
AS
(
SELECT C.CategoryID, CXR.ParentID, C.Name, 0 AS Level
FROM Categories C
INNER JOIN Categories_XRef CXR ON C.CategoryID = CXR.CategoryID
WHERE CXR.CategoryID = 943
UNION ALL
SELECT C.CategoryID, CXR.ParentID, C.Name, Level + 1
FROM Categories C
INNER JOIN Categories_XRef CXR ON C.CategoryID = CXR.CategoryID
INNER JOIN CTE PC ON PC.CategoryID = CXR.ParentID
)
SELECT * FROM CTE
À propos, les catégories peuvent-elles avoir plusieurs parents? Si ce n'est pas le cas, envisagez d'éliminer la table Categories_XREF et de stocker le ParentID dans la table Categories.