我有下表:

ID Transaction Amount Inventory Price
1 NULL NULL 11 NULL
2 Sale -1 10 100
3 Purchase 2 12 102
4 Sale -2 10 103

第一行是起始金额,下面三行显示更改库存的交易记录.我需要计算一个新的运行平均价格的基础上购买和复制当前的平均价格的情况下销售基于以下公式:

    If Transaction = NULL (i.e. starting line) then Average = 90;

    If Transaction = 'Sale' then Average = lag(Average) (i.e. whatever is the latest calculated average);

    If transaction = 'Purchase' then ((Inventory - Amount) * lag(Average) 
                                 + Amount * Price)
                                 / Inventory

排序顺序为ID列升序.

这个问题是由滞后(平均值)引起的,因 for each 计算步骤都要求前一行是更新的值,即计算必须逐行运行和更新.

结果表应如下所示:

ID Transaction Amount Inventory Price Average
1 NULL NULL 11 NULL 90
2 Sale -1 10 100 90
3 Purchase 2 12 102 92
4 Sale -2 10 103 92

计算如下:

ID 1-->90(起始值)

ID 2-->90(复制以前的平均值)

ID3-->92=((12-2)*90+(2*102))/12

ID 4-->92(复制以前的平均值)

我try 了以下几种方法:

  1. 使用起始值为90的列(Average),并在另一列(Average_F)中运行计算.
    Select *, 
       case when [transaction] is null then Average
            when [transaction]  = 'Sale' then lag(Average) over (order by ID)
            when [transaction] = 'Purchase' 
                 then (((Inventory - Amount) * lag(Average) over (order by ID))
                      + (Amount * Price)) / Inventory
        end as Average_f
from table

没有成功:

ID Transaction Amount Inventory Price Average Average_f
1 NULL NULL 11 NULL 90 90
2 Sale -1 10 100 NULL 90
3 Purchase 2 12 102 NULL NULL
4 Sale -2 10 103 NULL NULL
  1. 我也试过更新声明:
    update table
    set average = case when [transaction] is null then Average
             when [transaction] = 'Purchase' 
                 then (((Inventory - Amount) * (select lag(Average) over (order by ID)
                                                from table t 
                                                where t.ID = table.ID))
                      + (Amount * Price)) / Inventory
             when [transaction]  = 'Sale' then (select lag(Average) over (order by ID)
                                                from table t 
                                                where t.ID = table.ID)
             end

也没有奏效:

ID Transaction Amount Inventory Price Average
1 NULL NULL 11 NULL 90
2 Sale -1 10 100 NULL
3 Purchase 2 12 102 NULL
4 Sale -2 10 103 NULL

在SQL中,有没有一种方法可以单独计算每一行,或者有任何其他方法可以使用以前的平均值来计算平均值?

推荐答案

一种基于集合的解决方案,它考虑了非顺序标识符并取消了对递归级别的限制.

with
  t1 as (
    select *
    from (
      values
        (1, NULL, NULL, 11, NULL),
        (2, 'Sale', -1, 10, 100),
        (3, 'Purchase', 2, 12, 102),
        (4, 'Sale', -2, 10, 103)
    ) as t(id, "Transaction", amount, inventory, price)
  ),
  t2 as (
    select
      *,
      lead(id) over(order by id) as next_id
    from t1
  ),
  r as (
    select *, 90 as average
    from t2
    where "Transaction" is null
    union all
    select
      t2.*,
      case t2."Transaction"
        when 'Sale' then r.average
        else (r.Average * (t2.Inventory - t2.Amount) +
              t2.Amount * t2.Price) / t2.Inventory
      end
    from r
    join t2
    on t2.id = r.next_id
  )
select
  id, "Transaction", amount, inventory, price, average
from r
OPTION (MAXRECURSION 0)
;

db<>fiddle

用于与基于集合的解决方案进行比较的基于指针的解决方案.

drop table if exists #data;

create table #data (
  id int,
  "Transaction" nvarchar(10),
  amount int,
  inventory int,
  price int
);

insert into #data(id, "Transaction", amount, inventory, price)
  values
    (1, NULL, NULL, 11, NULL),
    (2, 'Sale', -1, 10, 100),
    (3, 'Purchase', 2, 12, 102),
    (4, 'Sale', -2, 10, 103);

drop table if exists #averages;

create table #averages (
  id int,
  "Transaction" nvarchar(10),
  amount int,
  inventory int,
  price int,
  average float
);

declare
  @id int,
  @tran nvarchar(10),
  @amt int,
  @inv int,
  @price int,
  @avg float;

DECLARE C CURSOR FORWARD_ONLY STATIC READ_ONLY FOR
  select *
  from #data
  order by id;

open c;

FETCH NEXT FROM C INTO @id, @tran, @amt, @inv, @price;

WHILE @@FETCH_STATUS = 0 BEGIN
  set @avg = case
               when @tran is null then 90
               else
                 case @tran
                   when 'Sale' then @avg
                   else (@avg * (@inv - @amt) +
                         @amt * @price) / @inv
                 end
             end;
  insert into #averages(id, "Transaction", amount, inventory, price, average)
    values(@id, @tran, @amt, @inv, @price, @avg);
  FETCH NEXT FROM C INTO @id, @tran, @amt, @inv, @price;
END;

CLOSE C;
DEALLOCATE C;

select * from #averages;

https://dbfiddle.uk/m3nePu26

这两种解决方案的结果:

+----+-------------+--------+-----------+-------+---------+
| id | Transaction | amount | inventory | price | average |
+----+-------------+--------+-----------+-------+---------+
|  1 | null        | null   |        11 | null  |      90 |
|  2 | Sale        | -1     |        10 | 100   |      90 |
|  3 | Purchase    | 2      |        12 | 102   |      92 |
|  4 | Sale        | -2     |        10 | 103   |      92 |
+----+-------------+--------+-----------+-------+---------+

Sql相关问答推荐

当编号和版本的唯一状态更改时报告

为什么Postgrs Planner会在输出部分中显示我在查询中不使用的列?'""

在多个联合中使用相同的SELECT SQL查询

通过之前的连接-这是Oracle的错误吗?

找到最新的连线

如何找到一个组合的两个列,这是不是在其他表在ORACLE SQL?

SQL递归.硬币兑换问题.-try 使用递归解决硬币找零问题

两个月之间的WHERE CASE WHEN-ORA-00905:缺少关键字

违反了完整性约束-值存在时找不到父键

根据日期 Select ID 的上一条记录

具有多个表 JOINS 的 STRING_AGG 的替代方法 (SQL Server 2016)

错误:postgresql 中缺少表评级的 FROM 子句条目

Postgresql:在链接表中判断相关表中插入值的条件

用户定义的标量值函数是否仍然会阻止并行性?

Clickhouse:左连接表到外部数组

按所选的值将记录分组到不同的列中

Postgres如何在一个日历周中前进和回填值

REGEXP_SUBSTR使用方法

如何获取每个组中最近的n条记录并将它们聚合成数组

Clob 问题 - 将 clob 列拆分为多行