2017-09-24 199 views
0

我有一個遞歸查詢按預期工作,用於計算庫存計算的加權平均成本。我的問題是,我需要根據不同列分組的相同查詢得到多個加權平均值。我知道我可以通過多次計算來解決問題,每個鍵列都有一個問題。但是由於查詢性能的考慮,我希望它被遍歷一次。有時我有1M +行。遞歸查詢分組結果(SQL Server)

我簡化了數據,並將加權平均值換成了一個簡單的總和,使我的問題更容易遵循。

如何使用遞歸cte得到下面的結果?請記住,我必須使用遞歸查詢來計算加權平均成本。我的SQL Server 2016上

實例數據(ID也排列順序。ID和密鑰都是唯一的一起。)

Id Key1 Key2 Key3 Value 
1 1  1  1  10 
2 1  1  1  10 
3 1  2  1  10 
4 2  2  1  10 
5 1  2  1  10 
6 1  1  2  10 
7 1  1  1  10 
8 3  3  1  10 

預期結果

Id Key1 Key2 Key3 Value Key1Sum Key2Sum Key3Sum 
1 1  1  1  10  10  10  10 
2 1  1  1  10  20  20  20 
3 1  2  1  10  30  10  30 
4 2  2  1  10  10  20  40 
5 1  2  1  10  40  30  50 
6 1  1  2  10  50  30  10 
7 1  1  1  10  60  40  60 
8 3  3  1  10  10  10  70 

編輯

在經歷了一些值得批評的批評之後,我必須在如何提出問題方面做得更好。

這裏是一個例子,爲什麼我需要一個遞歸查詢。在這個例子中,我得到了Key1的結果,但我同樣需要它在Key2和Key3中。我知道我可以重複三次相同的查詢,但這不是可取的。

DECLARE @InventoryItem AS TABLE (
    IntentoryItemId INT NULL, 
    InventoryOrder INT, 
    Key1 INT NULL, 
    Key2 INT NULL, 
    Key3 INT NULL, 
    Quantity NUMERIC(22,9) NOT NULL, 
    Price NUMERIC(16,9) NOT NULL 
); 

INSERT INTO @InventoryItem (
    IntentoryItemId, 
    InventoryOrder, 
    Key1, 
    Key2, 
    Key3, 
    Quantity, 
    Price 
) 
VALUES 
(1, NULL, 1, 1, 1, 10, 1), 
(2, NULL, 1, 1, 1, 10, 2), 
(3, NULL, 1, 2, 1, 10, 2), 
(4, NULL, 2, 2, 1, 10, 1), 
(5, NULL, 1, 2, 1, 10, 5), 
(6, NULL, 1, 1, 2, 10, 3), 
(7, NULL, 1, 1, 1, 10, 3), 
(8, NULL, 3, 3, 1, 10, 1); 


--The steps below will give me the cost "grouped" by Key1 
WITH Key1RowNumber AS (
    SELECT 
     IntentoryItemId, 
     ROW_NUMBER() OVER (PARTITION BY Key1 ORDER BY IntentoryItemId) AS RowNumber 
    FROM @InventoryItem 
) 

UPDATE @InventoryItem 
    SET InventoryOrder = Key1RowNumber.RowNumber 
FROM @InventoryItem InventoryItem 
INNER JOIN Key1RowNumber 
ON Key1RowNumber.IntentoryItemId = InventoryItem.IntentoryItemId; 

WITH cte AS (
    SELECT 
     IntentoryItemId, 
     InventoryOrder, 
     Key1, 
     Quantity, 
     Price, 
     CONVERT(NUMERIC(22,9), InventoryItem.Quantity) AS CurrentQuantity, 
     CONVERT(NUMERIC(22,9), (InventoryItem.Quantity * InventoryItem.Price)/NULLIF(InventoryItem.Quantity, 0)) AS AvgPrice 
    FROM @InventoryItem InventoryItem 
    WHERE InventoryItem.InventoryOrder = 1 
    UNION ALL 
    SELECT 
     Sub.IntentoryItemId, 
     Sub.InventoryOrder, 
     Sub.Key1, 
     Sub.Quantity, 
     Sub.Price, 
     CONVERT(NUMERIC(22,9), Main.CurrentQuantity + Sub.Quantity) AS CurrentQuantity, 
     CONVERT(NUMERIC(22,9), 
       ((Main.CurrentQuantity) * Main.AvgPrice + Sub.Quantity * Sub.price) 
        /
       NULLIF((Main.CurrentQuantity) + Sub.Quantity, 0) 
     ) AS AvgPrice 
    FROM CTE Main 
    INNER JOIN @InventoryItem Sub 
    ON Main.Key1 = Sub.Key1 
    AND Sub.InventoryOrder = main.InventoryOrder + 1 
) 

SELECT cte.IntentoryItemId, cte.AvgPrice 
FROM cte 
ORDER BY IntentoryItemId 
+0

你嘗試過什麼?那就是,你失去了什麼?請回顧[如何創建最小,完整和可驗證示例](https://stackoverflow.com/help/mcve)並修改您的問題。 – jhenderson2099

+0

如果您使用的是SQL Server 2012或更高版本,使用窗口函數的性能可能會比遞歸更好。 –

+0

檢查我的最新答案。 – KumarHarsh

回答

0

爲什麼你要計算在100萬+行?

其次我認爲你的db設計是錯誤的? key1 ,key2,key3應該是unpivoted,另一列是Keys,另有1列用於標識每個關鍵組。

在下面的例子中將會清楚你。

如果我能夠優化我的查詢,那麼我可以考慮計算很多行,我嘗試限制行數。

另外,如果可能的話,您可以考慮保留Avg Price.i.e的計算列。當表填充時,您可以計算並存儲它。

首先讓我們知道,如果輸出正確與否。

DECLARE @InventoryItem AS TABLE (
    IntentoryItemId INT NULL, 
    InventoryOrder INT, 
    Key1 INT NULL, 
    Key2 INT NULL, 
    Key3 INT NULL, 
    Quantity NUMERIC(22,9) NOT NULL, 
    Price NUMERIC(16,9) NOT NULL 
); 

INSERT INTO @InventoryItem (
    IntentoryItemId, 
    InventoryOrder, 
    Key1, 
    Key2, 
    Key3, 
    Quantity, 
    Price 
) 
VALUES 
(1, NULL, 1, 1, 1, 10, 1), 
(2, NULL, 1, 1, 1, 10, 2), 
(3, NULL, 1, 2, 1, 10, 2), 
(4, NULL, 2, 2, 1, 10, 1), 
(5, NULL, 1, 2, 1, 10, 5), 
(6, NULL, 1, 1, 2, 10, 3), 
(7, NULL, 1, 1, 1, 10, 3), 
(8, NULL, 3, 3, 1, 10, 1); 
--select * from @InventoryItem 
--return  
;with cte as 
(
select * 
, ROW_NUMBER() OVER (PARTITION BY Key1 ORDER BY IntentoryItemId) AS rn1 
, ROW_NUMBER() OVER (PARTITION BY Key2 ORDER BY IntentoryItemId) AS rn2 
, ROW_NUMBER() OVER (PARTITION BY Key3 ORDER BY IntentoryItemId) AS rn3 
from @InventoryItem 
) 
,cte1 AS (
     SELECT 
     IntentoryItemId, 

     Key1 keys, 
     Quantity, 
     Price 
     ,rn1 
     ,rn1 rn 
     ,1 pk 
    FROM cte c 

    union ALL 

    SELECT 
     IntentoryItemId, 

     Key2 keys, 
     Quantity, 
     Price 
     ,rn1 
     ,rn2 rn 
     ,2 pk 
    FROM cte c 

    union ALL 

    SELECT 
     IntentoryItemId, 

     Key3 keys, 
     Quantity, 
     Price 
     ,rn1 
     ,rn3 rn 
     ,3 pk 
    FROM cte c 

) 

, cte2 AS (
    SELECT 
     IntentoryItemId, 
     rn, 
     Keys, 
     Quantity, 
     Price, 
     CONVERT(NUMERIC(22,9), InventoryItem.Quantity) AS CurrentQuantity, 
     CONVERT(NUMERIC(22,9), (InventoryItem.Quantity * InventoryItem.Price)) a, 
      CONVERT(NUMERIC(22,9), InventoryItem.Price) b, 

     CONVERT(NUMERIC(22,9), (InventoryItem.Quantity * InventoryItem.Price)/NULLIF(InventoryItem.Quantity, 0)) AS AvgPrice 
     ,pk 
    FROM cte1 InventoryItem 
    WHERE InventoryItem.rn = 1 
    UNION ALL 
    SELECT 
     Sub.IntentoryItemId, 
     sub.rn, 
     Sub.Keys, 
     Sub.Quantity, 
     Sub.Price, 
     CONVERT(NUMERIC(22,9), Main.CurrentQuantity + Sub.Quantity) AS CurrentQuantity, 
     CONVERT(NUMERIC(22,9),Main.CurrentQuantity * Main.AvgPrice), 
     CONVERT(NUMERIC(22,9),Sub.Quantity * Sub.price), 

     CONVERT(NUMERIC(22,9), 
       ((Main.CurrentQuantity * Main.AvgPrice) + (Sub.Quantity * Sub.price)) 
        /
       NULLIF(((Main.CurrentQuantity) + Sub.Quantity), 0) 
     ) AS AvgPrice 
     ,sub.pk 
    FROM CTE2 Main 
    INNER JOIN cte1 Sub 
    ON Main.Keys = Sub.Keys and main.pk=sub.pk 
    AND Sub.rn = main.rn + 1 
    --and Sub.InventoryOrder<=2 
) 
select * 
,(select AvgPrice from cte2 c1 where pk=2 and c1.IntentoryItemId=c.IntentoryItemId) AvgPrice2 
,(select AvgPrice from cte2 c1 where pk=2 and c1.IntentoryItemId=c.IntentoryItemId) AvgPrice3 
from cte2 c 

where pk=1 
ORDER BY pk,rn 

替代的解決方案(對於SQL 2012+),並非常感謝傑森,

SELECT * 
,CONVERT(NUMERIC(22,9),avg((Quantity * Price)/NULLIF(Quantity, 0)) 
OVER(PARTITION BY Key1 ORDER by IntentoryItemId ROWS UNBOUNDED PRECEDING))AvgKey1Price 
,CONVERT(NUMERIC(22,9),avg((Quantity * Price)/NULLIF(Quantity, 0)) 
OVER(PARTITION BY Key2 ORDER by IntentoryItemId ROWS UNBOUNDED PRECEDING))AvgKey2Price 
,CONVERT(NUMERIC(22,9),avg((Quantity * Price)/NULLIF(Quantity, 0)) 
OVER(PARTITION BY Key3 ORDER by IntentoryItemId ROWS UNBOUNDED PRECEDING))AvgKey3Price 
from @InventoryItem 
order by IntentoryItemId 
+0

1M +,因爲我的經理不喜歡堅持計算的數據來對付別人。我會回答你的答案,因爲如你所說,最好改變數據的準備,而不是試圖同時計算所有數據。我有一個夢想,我可以在更少的迭代中做到這一點,因爲遞歸操作非常昂貴。 – Senno

0

這裏是如何做到這一點在2012年以後&的SQL Server ...

IF OBJECT_ID('tempdb..#TestData', 'U') IS NOT NULL 
DROP TABLE #TestData; 

CREATE TABLE #TestData (
    Id INT, 
    Key1 INT, 
    Key2 INT, 
    Key3 INT, 
    [Value] INT 
    ); 
INSERT #TestData(Id, Key1, Key2, Key3, Value) VALUES 
    (1, 1, 1, 1, 10), 
    (2, 1, 1, 1, 10), 
    (3, 1, 2, 1, 10), 
    (4, 2, 2, 1, 10), 
    (5, 1, 2, 1, 10), 
    (6, 1, 1, 2, 10), 
    (7, 1, 1, 1, 10), 
    (8, 3, 3, 1, 10); 

--============================================================= 

SELECT 
    td.Id, td.Key1, td.Key2, td.Key3, td.Value, 
    Key1Sum = SUM(td.[Value]) OVER (PARTITION BY td.Key1 ORDER BY td.Id ROWS UNBOUNDED PRECEDING), 
    Key2Sum = SUM(td.[Value]) OVER (PARTITION BY td.Key2 ORDER BY td.Id ROWS UNBOUNDED PRECEDING), 
    Key3Sum = SUM(td.[Value]) OVER (PARTITION BY td.Key3 ORDER BY td.Id ROWS UNBOUNDED PRECEDING) 
FROM 
    #TestData td 
ORDER BY 
    td.Id; 

結果...

Id   Key1  Key2  Key3  Value  Key1Sum  Key2Sum  Key3Sum 
----------- ----------- ----------- ----------- ----------- ----------- ----------- ----------- 
1   1   1   1   10   10   10   10 
2   1   1   1   10   20   20   20 
3   1   2   1   10   30   10   30 
4   2   2   1   10   10   20   40 
5   1   2   1   10   40   30   50 
6   1   1   2   10   50   30   10 
7   1   1   1   10   60   40   60 
8   3   3   1   10   10   10   70 
+0

謝謝,但我簡化了原來的問題,這導致了我的問題是什麼的一些誤解。現在我已經用一個例子來更新這個問題,這個例子說明了爲什麼我使用/需要遞歸。 – Senno