2010-04-11 114 views
3

您必須得到以下表格,其中銷售包含產品,並且產品可以放置在多個類別中。由此類有hierachly結構,如:SQL:使用公用表表達式遞歸獲取父記錄

Man 
Shoes 
    Sport 
    Casual 
Watches 
Women 
Shoes 
    Sport 
    Casual 
Watches 

表:

Sale: 
    id name 
    1 Sale1 

Product: 
    id saleidfk name 
    1 1  a 
    2 1  b 
    3 1  c 
    4 1  d 
    5 1  e 

ProductCategory : 
    productid categoryid 
    1   3 
    2   3   
    3   4 
    4   5 
    5   10  

Category: 
    id ParentCategoryIdFk name 
    1 null    Men 
    2 1     Shoes 
    3 2     Sport 
    4 2     Casual 
    5 1     Watches 
    6 null    Women 
    7 6     Shoes 
    8 7     Sport 
    9 7     Casual 
    10 6     Watches 

問:

現在在我的網站我想創造一個只有類別表現出一定的銷售控制以及這些類別是否包含銷售產品。我還希望 包含類別的分層結構。因此,如果我們有一個離開節點,請重新進入頂層節點。

因此,與sale1我應該有一個查詢結果如下:

Men 
    Shoes 
    Sport 
    Casual 
    Watches 
Women 
    Watches 

這件事情讓我發瘋了:-)

提前感謝!

的Gr

的Martijn

+0

也許你可以獲得基本模式獲得父子記錄從孩子開始使用這種方法:http: //stackoverflow.com/questions/1104977/how-to-get-the-parent-given-a-child-in-sql-server-2005/1107901#1107901 – 2010-04-12 03:52:33

+0

或者如果你想從上到下,檢查模式這裏:http://stackoverflow.com/questions/1686340 – 2010-04-12 03:54:16

回答

4

嘗試是這樣的 - 基本CTE,讓您的類別的分級目錄將類似於此:

WITH Categories AS 
(
    SELECT Cat.ID, Cat.NAME, Cat.ParentCategoryID, CAST('none' AS VARCHAR(50)) AS 'ParentCategory', 1 AS 'Level' 
    FROM dbo.MBCategory Cat 
    WHERE Cat.ParentCategoryID IS NULL 

    UNION ALL 

    SELECT Cat.ID, Cat.NAME, Cat.ParentCategoryID, c2.NAME AS 'ParentCategory', LEVEL + 1 
    FROM dbo.MBCategory CAT 
    INNER JOIN Categories c2 ON cat.ParentCategoryID = c2.ID 
) 
SELECT * FROM Categories 

現在,你需要做的是什麼加入您的其他表到這個CTE,最後得到以下查詢:

WITH Categories AS 
(
    SELECT Cat.ID, Cat.NAME, Cat.ParentCategoryID, CAST('none' AS VARCHAR(50)) AS 'ParentCategory', 1 AS 'Level' 
    FROM dbo.MBCategory Cat 
    WHERE Cat.ParentCategoryID IS NULL 

    UNION ALL 

    SELECT Cat.ID, Cat.NAME, Cat.ParentCategoryID, c2.NAME AS 'ParentCategory', LEVEL + 1 
    FROM dbo.MBCategory CAT 
    INNER JOIN Categories c2 ON cat.ParentCategoryID = c2.ID 
) 
SELECT DISTINCT s.*, c.* 
FROM dbo.Sale s 
INNER JOIN dbo.Product p ON p.SaleID = s.ID 
INNER JOIN dbo.ProductCategory pc ON p.ID = pc.ProductID 
INNER JOIN Categories c ON pc.CategoryID = c.ID 
ORDER BY Level 

這給了我一個輸出結果是這樣的:

ID Name CatID CatName ParentCatID ParentCatName Level 
1 Sale1 5 Watches  1   Men   2 
1 Sale1 10 Watches  6   Women  2 
1 Sale1 3 Sport  2   Shoes  3 
1 Sale1 3 Sport  2   Shoes  3 
1 Sale1 4 Casual  2   Shoes  3 
+0

它接近,但有缺失的位。因爲我不知道鞋子是否也有父母。 – 2010-04-11 19:02:44

+0

我懷疑你將能夠得到這個整個層次結構的事情(達到任意數量的級別)在SQL查詢中工作....... – 2010-04-11 20:18:46

1

我認爲,如果你創建一個額外的表,列出了所有祖先類(父母,祖父母,等你會得到最快的性能,也更清潔的SQL查詢。)對於每一個類別,是這樣的:

CategoryAncestor 
ID categoryid ancestorid  
1   1   1  -- Men, obligatory self reference (makes queries easier) 
2   2   2  -- Shoes, self reference 
3   2   1  -- Shoes is a subcategory of Men 
4   3   3  -- Sport, self reference 
5   3   2  -- Sport is a subcategory of Shoes 
6   3   1  -- Sport is ALSO a subcategory of Men 
-- etc. 

當您插入新的類別或刪除它們,但將允許您運行您查詢快得多這將創建一個更小的開銷SQL。

你可能要考慮做的下一件事是增加等級和水平列(再次,創造更多的工作時間和刪除類別)類:

id ParentCategoryIdFk name  level rank 
1 null    Men   0  1 
2 1     Shoes   1  2 
3 2     Sport   2  3 
4 2     Casual  2  4 
5 1     Watches  1  5 
6 null    Women   0  6 
7 6     Shoes   1  7 
8 7     Sport   2  8 
9 7     Casual  2  9 
10 6     Watches  1  10 

的排名列指定排序順序。

然後,您可以簡單地運行下面的查詢:

SELECT * FROM Category c 
    WHERE c.id IN (
    SELECT ancestorid FROM CategoryAncestor ca, ProductCategory pc, Product p 
     WHERE p.id = pc.productid 
     AND pc.categoryid = ca.categoryid 
     AND p.saleidfk = 1 
    ) 
    ORDER BY rank 

希望這有助於。

+0

感謝您的時間,你的真實,但我不是在現在改變這一點。謝謝! – 2010-04-12 09:16:58

0

是有點凌亂,但:

DROP TABLE #Sale 
GO 
DROP TABLE #PRoduct 
GO 
DROP TABLE #ProductCategory 
GO 
DROP TABLE #Category 
GO 
CREATE TABLE #Sale 
(
    ID INT, 
    Name VARCHAR(20)   
    ) 
GO 
INSERT INTO #Sale SELECT 1, 'Sale1' 
GO 
CREATE TABLE #Product 
(
ID INT, 
saleidfk INT, 
name VARCHAR(20) 
) 
GO 
INSERT INTO #Product 
SELECT 1,1,'a' 
UNION 
SELECT 2,1,'b' 
UNION 
SELECT 3,1,'c' 
UNION 
SELECT 4,1,'d' 
UNION 
SELECT 5,1,'e' 
UNION 
SELECT 6,1,'f' 
GO 
CREATE TABLE #ProductCategory 
(
ProductID INT, 
CategoryID INT 
) 
GO 
INSERT INTO #ProductCategory 
SELECT 1,3 
UNION 
SELECT 2,3 
UNION 
SELECT 3,4 
UNION 
SELECT 4,5 
UNION 
SELECT 5,10 
UNION 
SELECT 6,10 
GO 
CREATE TABLE #Category 
(
ID INT, 
ParentCategoryFK INT, 
Name varchar(20) 
) 
GO 
INSERT INTO #Category 
SELECT 1,NULL,'Men' 
UNION 
SELECT 2,1,'Shoes' 
UNION 
SELECT 3,2,'Sport' 
UNION 
SELECT 4,2,'Casual' 
UNION 
SELECT 5,1,'Watches' 
UNION 
SELECT 6,NULL,'Women' 
UNION 
SELECT 7,6,'Shoes' 
UNION 
SELECT 8,7,'Sport' 
UNION 
SELECT 9,7,'Casual' 
UNION 
SELECT 10,6,'Watches' 


GO 

WITH Categories (CategoryName,CategoryID, [Level], SortOrder) AS 
( 
    SELECT Cat.Name,cat.id, 1 AS [Level], CONVERT(VARCHAR(MAX), ROW_NUMBER() OVER (order by cat.Name)) AS SortOrder 
    FROM #Category Cat 
    WHERE Cat.ParentCategoryFK IS NULL 

    UNION ALL 

    SELECT CAT.Name,cat.ID, [Level] + 1, c2.SortOrder + CONVERT(VARCHAR(MAX), ROW_NUMBER() OVER (order by cat.Name)) 
    FROM #Category CAT 
    INNER JOIN Categories c2 ON cat.ParentCategoryFK = c2.CategoryID 
) 
SELECT #Sale.Name, Categories.CategoryName, #Product.name,Categories.Level,Categories.SortOrder FROM 
Categories 
LEFT JOIN 
#ProductCategory ON #ProductCategory.CategoryID = Categories.CategoryID 
LEFT JOIN 
#Product ON #Product.ID = #ProductCategory.ProductID 
LEFT JOIN 
#Sale ON #Product.saleidfk = #Sale.ID 
ORDER BY Categories.SortOrder, #Product.name 

的相關要點需要注意是,以獲得完整的層次是有道理的,你需要的類別是否有產品或沒有。此外,SortOrder的varchar允許層次結構以正確的順序顯示。

1

這不是特別有效,但如果你想要做的實際上是「爆炸」的整個層次結構和父葉獲得序列中的結果,這樣的事情會做到這一點:

WITH CategoryHierarchy AS 
(
    SELECT 
     ID, ParentCategoryIdFk, 0 AS Level, 
     ROW_NUMBER() OVER (ORDER BY ID) AS SubTreeID 
    FROM Category 
    WHERE CategoryID IN 
    (
     SELECT pc.CategoryID 
     FROM Sale s 
     INNER JOIN Product p 
      ON p.saleidfk = s.id 
     INNER JOIN ProductCategory pc 
      ON pc.productid = p.id 
     WHERE s.id = @SaleID 
    ) 

    UNION ALL 

    SELECT c.ID, c.ParentCategoryIdFk, h.Level + 1, h.SubTreeID 
    FROM CategoryHierarchy h 
    INNER JOIN Category c 
     ON c.ID = h.ParentID 
) 
SELECT c.ID, c.ParentCategoryIdFk AS ParentID, c.Name 
FROM CategoryHierarchy h 
INNER JOIN Category c 
    ON c.ID = h.ID 
ORDER BY h.SubTreeID ASC, h.Level DESC 

這應該讓你的結果類似以下內容:

ID | ParentID | Name 
---+----------+---------- 
1 |  NULL | Men 
2 |  1 | Shoes 
3 |  2 | Sport 
---+----------+---------- 
1 |  NULL | Men 
2 |  1 | Shoes 
4 |  2 | Casual 
---+----------+---------- 
1 |  NULL | Men 
5 |  1 | Watches 
---+----------+---------- 
6 |  NULL | Women 
10 |  6 | Watches 

當然,實際的結果不會有這樣的分隔符,我已經添加了這些,使結果更清晰的含義。

如果你不希望它完全爆炸這樣,你可以使用其他的rownum只返回每個父母的第一個實例:

WITH CategoryHierarchy AS 
(
    SELECT 
     ID, ParentCategoryIdFk, 0 AS Level, 
     ROW_NUMBER() OVER (ORDER BY ID) AS SubTreeID 
    FROM Category 
    WHERE CategoryID IN 
    (
     SELECT pc.CategoryID 
     FROM Sale s 
     INNER JOIN Product p 
      ON p.saleidfk = s.id 
     INNER JOIN ProductCategory pc 
      ON pc.productid = p.id 
     WHERE s.id = @SaleID 
    ) 

    UNION ALL 

    SELECT c.ID, c.ParentCategoryIdFk, h.Level + 1, h.SubTreeID 
    FROM CategoryHierarchy h 
    INNER JOIN Category c 
     ON c.ID = h.ParentID 
), 
Filter_CTE AS 
(
    SELECT 
     ID, Level, SubTreeID 
     ROW_NUMBER() OVER (PARTITION BY ID ORDER BY SubTreeID) AS RowNum 
    FROM CategoryHierarchy 
) 
SELECT c.ID, c.ParentCategoryIdFk AS ParentID, c.Name 
FROM Filter_CTE f 
INNER JOIN Category c 
    ON c.ID = f.ID 
WHERE f.RowNum = 1 
ORDER BY f.SubTreeID ASC, f.Level DESC 

...會給你類似的結果:

ID | ParentID | Name 
---+----------+---------- 
1 |  NULL | Men 
2 |  1 | Shoes 
3 |  2 | Sport 
4 |  2 | Casual 
5 |  1 | Watches 
6 |  NULL | Women 
10 |  6 | Watches 

注意:請小心第二個版本,因爲它不一定保證以層次結構返回結果。恰巧這個版本的確如此,因爲這些ID本身是按照等級順序排列的。你可以繞過這個限制,但是它會爲這個已經有點複雜的查詢增加更多的複雜性。

第二個版本確實保證主類別總是出現在其任何子類別之前,如果您計劃使用字典構建遞歸數據結構,那麼這很好。它可能不適用於更快的基於堆棧的樹建立或直接用戶報告。出於這些目的,您可能希望使用第一個版本。

+0

謝謝,我會檢查出來,並從此開始。 – 2010-04-12 09:19:14

0

我想我太晚了,但對於未來的同伴嘗試相同,我認爲這將工作。 :)(只是做了一個特定項目的父級層次結構,但與樹葉的內部連接將做同樣的技巧)

with 
hierarchy (id, parentId, level) 
as 
(
    select c.id, c.parentId, 0 as level 
    from categories c 
    where parentId = 0 
    union all 
    select c.id, c.parentId, level + 1 
    from categories c 
    inner join hierarchy p on c.parentId = p.id 
), 
parents (id, parentId, level) 
as 
(
    select l.id, l.parentId, l.level 
    from hierarchy l 
     [where id = *leafid* | inner join *insert_your_leaves_here*] 
    union all 
    select p.id, p.parentId, p.level 
    from hierarchy p 
    inner join parents l on p.id = l.parentId 
) 

select * from parents