2017-09-24 148 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