SQL Server 2005에서 CTE를 사용한 재귀 쿼리
-
05-07-2019 - |
문제
좋아, 여기 내가하려고하는 일이있다. MSSQL2005에서 CTE 쿼리를 사용하고 있습니다. 쿼리의 목적은 제품 카테고리의 부모 아동 관계를 통해 되풀이하고 각 범주의 제품 수를 반환하는 것입니다 (여기에는 어린이 카테고리에 포함 된 모든 제품이 포함됨)
내 현재 버전은 표시되는 카테고리의 제품 수만 반환합니다. 어린이에 포함될 수있는 제품을 설명하지 않습니다.
문제를 재현하기위한 데이터베이스 덤프, 내가 사용한 쿼리와 함께 다음과 같이 설명합니다.
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
다음은 내가 사용하고있는 CTE 쿼리입니다.
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
먼저 "PC.ParentID"를 943으로 변경합니다. 각 카테고리에 대한 제품 수를 보여주는 3 개의 레코드가 반환됩니다.
이제 부모를 바꾸십시오 943 에게 942 그리고 그것을 다시 실행하십시오. 이제 "Cardiology"라는 1 개의 결과가 반환되었지만이 범주에 따라 0 개의 제품이 표시되며 제품이 포함 된 어린이 (이전에 본 사람)가 있습니다. 나의 가장 큰 문제는,이 수준에서 (부모 942) 아래 어린이들에 포함 된 제품을 어떻게 계산하여 13을 "ProductCount"로 표시 할 수있는 방법입니다. 나는 그것을 시도했지만 성공하지 못했습니다.
나는 내가 찾고있는 일을하는 저장된 절차에 열려 있습니다. 나는 하나의 특별한 방식으로 설정되어 있지 않습니다. 그래서 다른 제안은 감사 할 것입니다.
해결책
편집하다 OK 실제로 요구 사항을 읽고 조금 생각한 것은 이것이 실제로 매우 쉽다고 생각했습니다 (저는 생각합니다!)
요점은 우리가 카테고리 계층 구조와 제품 수의 두 가지를 원한다는 것입니다. 계층 구조는 재귀 CTE에 의해 수행되며 계산은 그 외부에서 수행됩니다.
-- 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
결과는 다음과 같습니다.
Ancestor ProductsInTree
----------- --------------
942 13
943 13
959 9
960 1
961 3
나는 빚을졌다 이 기사에서는 무시할 수없는 Itzik Ben-Gan 의이 기사 내 생각을 시작하여 시작했습니다. 그의 책 'Inside MS SQL Server 2005 : T-SQL 쿼리'가 적극 권장됩니다.
다른 팁
귀하의 Where 진술은 결과를 한 부모로 제한합니다. 942 미만의 모든 어린이를보고 싶다면 942를 CTE의 뿌리로 지정하십시오. 예를 들어:
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
그건 그렇고, 카테고리가 여러 부모를 가질 수 있습니까? 그렇지 않은 경우 카테고리 _xref 테이블을 제거하고 카테고리 테이블에 Parentid를 저장하는 것이 좋습니다.