类别值被替换/更新的滚动总和

Posted

技术标签:

【中文标题】类别值被替换/更新的滚动总和【英文标题】:Rolling sum where category values are replaced/updated 【发布时间】:2021-12-28 15:54:36 【问题描述】:

我正在尝试让滚动总和在任何给定日期发生变化的类别/组金额变化 - 当发生变化时 类别的新值成为滚动总和的一部分,但之前的值然后忽略该类别的;所以这是一个滚动总和,但仅基于每个类别的最新(在那个时间点)。

示例数据(SumAmount 是试图解决的问题)

txn_id | cust_id | trans_date |  Category | amount | SumAmount
-----------------------------------------------------------------
     1 |       1 |  2020-01-01|  Ball     |      5 |   5     --first tran so sum is 5
     2 |       1 |  2020-01-02|  Cup      |      5 |   10    --sum is 10 (ball=5,Cup=5)
     3 |       1 |  2020-01-03|  Ball     |      2 |   7     --sum is 7 (ball=2,Cup=5) 
     4 |       1 |  2020-02-04|  Ball     |      4 |   9     --sum is 9 (ball=4,Cup=5)
     5 |       1 |  2020-02-05|  Ball     |      1 |   6     --sum is 6 (ball=1,Cup=5)
     6 |       1 |  2020-02-06|  Cup      |      10|   11    --sum is 11(ball=1,Cup=10)
     7 |       1 |  2020-02-07|  Phone    |      5 |   16    --sum is 16(ball=1,Cup=10,Phone=5)
     8 |       1 |  2020-02-08|  Cup      |      5 |   11    --sum is 11(ball=1,Cup=5,Phone=5)
     9 |       1 |  2020-02-09|  Ball     |      5 |   15    --sum is 15(ball=5,Cup=5,Phone=5)

我已经在游标中进行了这项工作,但想知道是否可以使用基于 SET 的方法

光标如下:

CREATE PROCEDURE [dbo].[PriceHistory](@CustId int, @MaxPriceHistory decimal(16,2) Output)
create table #PriceHistory ( CategoryID uniqueidentifier, Amount decimal(16,2))

    declare pricehistory_cursor CURSOR FOR
    select CategoryID, Amount
    from mytable
    where CustId =@CustId
    order by trans_date;

    declare @CategoryID uniqueidentifier
    declare @Amount decimal(16,2)
    declare @CurrentTotal decimal(16,2)

    set @MaxPriceHistory = 0

    open pricehistory_cursor
    fetch next from pricehistory_cursor into @CategoryID, @Amount

    WHILE @@FETCH_STATUS = 0  
    BEGIN 
           if (exists(select * from #PriceHistory where CategoryID = @CategoryID))
                  update #PriceHistory set Amount = @actualAmount where CategoryID = @CategoryID 
           else
                  insert into #PriceHistory(CategoryID,Amount) values (@CategoryID, @Amount)

           select @CurrentTotal = sum(Amount) from #PriceHistory

           if (@CurrentTotal > @MaxPriceHistory)
                  set @MaxPriceHistory = @CurrentTotal

           fetch next from pricehistory_cursor into @CategoryID, @Amount                 
    END

    close pricehistory_cursor
    deallocate pricehistory_cursor;

最终,我正在寻找整个交易生命周期中的 Max SumAmount(在提供的示例中为 SumAmount 列),在本示例中为 16。

我知道光标在做什么,我知道为什么它会这样工作(如果已经存在,则替换该特定类别的金额(这是我对基于 SET 的方法感到困惑的一点,我将如何获得 Cup 金额5,当 txn_id = 5 发生时?),并将其与当时所有其他最新类别金额相加),如果可能与某种递归有关,我只是无法理解CTE 或 ROW_NUMBER。

【问题讨论】:

..recursion 和 json ..dbfiddle.uk/… @lptr 这很时髦,以前从未使用过 JSON 函数。这是我不说明数据类型的坏处,但是 JSON 函数是否管理 MONEY 或 DECIMALS?我的金额列是钱而不是 INT 只是强制转换(.. 作为您的数据类型)而不是强制转换(作为 int)。 @lptr Ooo 看起来像 json_modify 函数不执行货币数据类型“参数数据类型货币对 json_modify 函数的参数 3 无效。”我会在谷歌周围挖掘一下! :D 将钱转换为 json_modify 的小数…dbfiddle.uk/… 【参考方案1】:

由于数据在一个全新的临时表中,这也意味着主键没有间隙。

这对于递归 CTE 来说是一个很好的情况。

下面的查询保留了球、杯子和手机的最新金额。 那么总和的计算就完全取决于类别了。

WITH RCTE_BALL_CUP_PHONE AS
(
   SELECT txn_id, cust_id, trans_date, category, amount
   , CASE WHEN category = 'Ball'  THEN amount ELSE 0 END AS NearestBallAmount
   , CASE WHEN category = 'Cup'   THEN amount ELSE 0 END AS NearestCupAmount
   , CASE WHEN category = 'Phone' THEN amount ELSE 0 END AS NearestPhoneAmount
   , amount AS SumAmount
   FROM  #PriceHistory AS tmp
   WHERE txn_id = 1

   UNION ALL

   SELECT tmp.txn_id, tmp.cust_id, tmp.trans_date, tmp.category, tmp.amount
   , CASE WHEN tmp.category = 'Ball'  THEN tmp.amount ELSE c.NearestBallAmount END
   , CASE WHEN tmp.category = 'Cup'   THEN tmp.amount ELSE c.NearestCupAmount END
   , CASE WHEN tmp.category = 'Phone' THEN tmp.amount ELSE c.NearestPhoneAmount END
   , CASE 
     WHEN tmp.category = 'Ball'  THEN (tmp.amount + c.NearestCupAmount  + c.NearestPhoneAmount)
     WHEN tmp.category = 'Cup'   THEN (tmp.amount + c.NearestBallAmount + c.NearestPhoneAmount)
     WHEN tmp.category = 'Phone' THEN (tmp.amount + c.NearestCupAmount  + c.NearestBallAmount)
     ELSE tmp.Amount
     END
   FROM RCTE_BALL_CUP_PHONE c
   JOIN #PriceHistory AS tmp 
     ON tmp.txn_id = c.txn_id + 1
)
SELECT txn_id, cust_id, trans_date, category, amount
, SumAmount
FROM RCTE_BALL_CUP_PHONE
ORDER BY txn_id;
txn_id | cust_id |交易日期 |类别 |金额 |金额 -----: | ------: | :--------- | :------- | -----: | --------: 1 | 1 | 2020-01-01 |球 | 5 | 5 2 | 1 | 2020-01-02 |杯子 | 5 | 10 3 | 1 | 2020-01-03 |球 | 2 | 7 4 | 1 | 2020-02-04 |球 | 4 | 9 5 | 1 | 2020-02-05 |球 | 1 | 6 6 | 1 | 2020-02-06 |杯子 | 10 | 11 7 | 1 | 2020-02-07 |电话 | 5 | 16 8 | 1 | 2020-02-08 |杯子 | 5 | 11 9 | 1 | 2020-02-09 |球 | 5 | 15

db小提琴here

为了将来参考,这里是对 lptr 很棒的 JSON 方法的改编。 它适用于超过 3 个类别,而无需进行任何更改。

with RCTE as
(
  select *, cast(concat('"', category, '":', amount, '') as varchar(max)) as j
  from #PriceHistory t
  where txn_id=1

  union all

  select t.*, cast(json_modify(cte.j, concat('$.', t.category), t.amount) as varchar(max))
  from RCTE cte
  join #PriceHistory t on t.txn_id = cte.txn_id+1
)
select txn_id, cust_id, trans_date, category, amount
, (select sum(cast(value as int)) from openjson(j)) as SumAmount
, j
from RCTE
order by txn_id
txn_id | cust_id |交易日期 |类别 |金额 |金额 | j -----: | ------: | :--------- | :------- | -----: | --------: | :---------------------------- 1 | 1 | 2020-01-01 |球 | 5 | 5 | “球”:5 2 | 1 | 2020-01-02 |杯子 | 5 | 10 | “球”:5,“杯”:5 3 | 1 | 2020-01-03 |球 | 2 | 7 | “球”:2,“杯”:5 4 | 1 | 2020-02-04 |球 | 4 | 9 | “球”:4,“杯”:5 5 | 1 | 2020-02-05 |球 | 1 | 6 | “球”:1,“杯”:5 6 | 1 | 2020-02-06 |杯子 | 10 | 11 | “球”:1,“杯”:10 7 | 1 | 2020-02-07 |电话 | 5 | 16 | “球”:1,“杯”:10,“电话”:5 8 | 1 | 2020-02-08 |杯子 | 5 | 11 | “球”:1,“杯”:5,“电话”:5 9 | 1 | 2020-02-09 |球 | 5 | 15 | “球”:5,“杯”:5,“电话”:5

【讨论】:

谢谢!看起来是我所追求的,类别列表可能会进入 100 个,所以我猜我必须用预先列出的那些构建一些动态表,但它是我可以使用的东西 :)跨度> @MrC 谢谢,很高兴它有帮助。好吧,这适用于 3 个类别。但是如果你有很多类别,那么你真的应该考虑使用 lptr 在 cmets 中添加的很棒的 JSON 技巧。它适用于任意数量的类别。我试过this fiddle

以上是关于类别值被替换/更新的滚动总和的主要内容,如果未能解决你的问题,请参考以下文章

将搜索和替换限制为类别

SQLITE:显示每个类别的总数(总和)

数组中每个类别的总和

在 MySQL 中运行多个类别的总和

php sql中带有日期、变量类别和总和值的数据透视表

计算具有相似类别的每个 td 的总和