分组导致递归查询(SQL Server)

时间:2017-09-24 19:28:13

标签: sql-server tsql common-table-expression recursive-query

我有一个递归查询,用于计算库存计算的加权平均成本。我的问题是我需要从不同列分组的同一查询中获得多个加权平均值。我知道我可以通过多次计算来解决问题,每个键列一个。但是由于查询性能方面的考虑,我希望它遍历一次。有时我有1M +行。

我简化了数据并将加权平均值替换为一个简单的总和,以使我的问题更容易理解。

如何使用递归cte获得下面的结果?请记住,我必须使用递归查询来计算加权平均成本。我在sql server 2016上。

示例数据(Id也是排序顺序.Id和Key一起是唯一的。)

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

2 个答案:

答案 0 :(得分:0)

以下是如何在SQL Server 2012&后来...

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

答案 1 :(得分:0)

  

为什么要计算1M +行?

其次我认为你的数据库设计错了? key1 ,key2,key3应该是unpivoted,一列名为Keys,另外一列代表每个密钥组。

在下面的例子中你会清楚。

如果我能够优化我的查询,那么我可以考虑计算许多行,我试着限制行数。

如果可能的话,你可以考虑保持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+),非常感谢Jason,

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