3

我正在尝试在典型的餐厅类型数据库上提供报告功能。我在下面描述了问题的细节,但简而言之,我需要能够检索与分层自连接“类别”表相关的项目的聚合数据(总和和计数)。我知道这很冗长而且可能令人困惑,所以我将尝试用一个例子来传达细节。我有四个针对此问题的表:

Categories
Id    Name          ParentId
1     Food          NULL
2     Drinks        NULL
3     Beer          2
4     Draft Beer    3
5     Bottle Beer   4
6     Pizza         1
8     Sandwiches    1

ParentId 是返回类别的 FK

MenuItems
Id    Name              CategoryId  
1     6" Sausage        6
2     French Dip        8
3     Dogfish 60 IPA    4
4     Dogfish 60 IPA    5
5     Bud Light         5

Orders
Id    Total   DateReceived    DateCompleted
1     12.00   1/1/1970 1:00   1/1/1970 1:05 
2     11.50   1/1/1970 1:08   1/1/1970 1:18

OrderItems
Id    Price   OrderId   MenuItemId
1     9.00    1         1
2     3.00    1         5
3     3.50    2         3
4     8.00    2         2

此报告功能的目标是获取 categoryId 并返回给定时间段内的总和(或计数)。如果传入的类别是叶类别(例如瓶啤酒),我计算这个总数没有问题。但是,如果它的层次结构更高,例如“食物”,我无法弄清楚如何编写查询以总结所有子类别。

我正在使用 SQL Server 2008r2。我尝试使用 WITH common_table_expression 查询,但出现“递归 cte 的递归部分”中不允许聚合函数的错误。

我今天的 SQL 看起来像这样:

SELECT SUM(oi.Price) as Total, DAY(o.Completed)
FROM Orders o
JOIN OrderItems oi on oi.orderId = o.id
JOIN MenuItems mi on oi.MenuItemId = mi.id
JOIN Categories c on mi.CategoryId = c.id
WHERE o.Completed is not null
AND c.Id = @categoryId
AND o.Completed between @startDate and @endDate
GROUP BY DAY(o.completed)

同样,如果 @categoryId 是叶类别,则此查询将准确地提供我正在寻找的内容。如果不是,则不返回任何内容。我希望能够提供@category=2(饮料)并取回“饮料”类别层次结构中任何位置的所有项目的总和。

谢谢!

4

3 回答 3

2

Tobyb,
您实际上已经非常接近使用 CTE 递归方法解决这个问题的目标了。递归在最好的时候是令人困惑的,但它是这类问题的最佳解决方案。
我已经复制了您在问题中指定的架构,并提出了以下解决方案。

我已经对其进行了测试,它会产生以下输出...我假设这是您想要看到的...

CategoryName CategoryId TotalSaleAmount
Food               1    17.00
Drinks             2     6.50
Beer               3     6.50
Draft Beer         4     3.50
Bottle Beer        5     3.00
Pizza              6     9.00
Sandwiches         8     8.00

显然,17 美元的食物是由比萨饼和三明治组成的。同样,饮料的 6.50 美元是由啤酒的 6.50 美元组成,而啤酒又分别由生啤酒和瓶装啤酒的 3.50 美元和 3.00 美元组成……等等……
这与类别的层次结构相匹配。

代码如下,这应该适用于您的架构。如果您希望限制输出,请在 group by 之前添加一个 where 子句。

WITH CategorySearch(ParentId, Id) AS
(
SELECT ID AS ParentId, ID
FROM Categories
WHERE ID NOT IN (SELECT ParentId FROM Categories 
                WHERE ParentId IS NOT NULL)
UNION ALL
SELECT Categories.ParentId, CS.Id
    FROM Categories Categories INNER JOIN CategorySearch CS
    ON Categories.Id = CS.ParentId
    WHERE Categories.ParentId IS NOT NULL
)
SELECT CA.Name AS CategoryName, CS.ParentId AS CategoryID, SUM(OI.Price) AS TotalSaleAmount
FROM Categories CA INNER JOIN CategorySearch CS
    ON CS.ParentId = CA.ID
INNER JOIN MenuItems MI
    ON MI.CategoryId = CS.Id
INNER JOIN OrderItems OI
    ON OI.MenuItemId = MI.ID
INNER JOIN Orders O
    ON OI.OrderId = O.Id
GROUP BY CA.Name, CS.ParentId
ORDER BY CS.ParentId
于 2011-06-07T04:14:18.450 回答
0

您是否尝试过使用递归 CTE 来确定感兴趣的类别 ID,然后在进行聚合时加入这些结果?我认为是这样的:

WITH CatR AS
(
    SELECT Id FROM Categories WHERE Id = @CategoryId
    UNION ALL
    SELECT Categories.Id 
    FROM Cat
        INNER JOIN Categories ON Categories.ParentId = Cat.Id
)
SELECT SUM(Price)
FROM Orders
    INNER JOIN OrderItems ON OrderItems.OrderId = Orders.Id
    INNER JOIN MenuItems ON MenuItems.Id = OrderItems.MenuItemId
    INNER JOIN CatR ON CatR.Id = MenuItems.CategoryId
WHERE Orders.DateCompleted BETWEEN @StartDate AND @EndDate
于 2011-06-07T02:19:38.370 回答
-1

我会更改数据库。问题是您在同一张表中有两个不同类别的项目。

将 Food and Drink 放在 CategoryType 表中。将其余部分留在类别表中。加入他们。

或者,要避免使用另一个表,请将类别类型字段添加到类别表。类型可以是食物或饮料。您可以使用“食物”和“饮料”或“F”和“D”。纯粹主义者可能不喜欢这样,但优点是它更简单并且运行速度更快。

于 2011-06-07T01:58:07.557 回答