3

典型的なレストラン タイプのデータベースでレポート機能を提供しようとしています。以下に問題の詳細を説明しますが、一言で言えば、階層型の自己結合「カテゴリ」テーブルに関連するアイテムの集計データ (合計とカウント) を取得できる必要があります。これは冗長でおそらく混乱を招くことはわかっているので、例を挙げて詳細を説明します。この問題に固有の 4 つのテーブルがあります。

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 (Drinks) を指定して、「Drinks」カテゴリ階層内のすべてのアイテムの合計を取得できるようにしたいと考えています。

ありがとう!

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

データベースを変更します。問題は、同じテーブルに 2 つの異なるクラスの項目があることです。

Food and Drink を CategoryType テーブルに入れます。残りはカテゴリ テーブルに残します。参加してください。

または、別のテーブルを避けるために、カテゴリ タイプ フィールドをカテゴリ テーブルに追加します。Type は Food または Drink です。「Food」と「Drink」、または「F」と「D」を使用できます。純粋主義者はこれを好まないかもしれませんが、利点は、よりシンプルで高速に実行できることです。

于 2011-06-07T01:58:07.557 に答える