从 SQL 到 SPL:补齐某个平均值保证总和不变
SQL Server 数据库有的发票表里每个项目会有一笔金额,项目表里每个项目有多个账户,两者以 ProjectID 关联。
# Invoices
InvoiceID |
Amount |
ProjectID |
1 |
100.0000 |
1 |
2 |
100.0000 |
2 |
3 |
100.0000 |
3 |
4 |
100.0000 |
4 |
#Projects
ProjectID |
AccountCode |
1 |
12345 |
2 |
12345 |
2 |
7890 |
3 |
800 |
3 |
234 |
3 |
987 |
4 |
800 |
4 |
234 |
4 |
987 |
4 |
2579 |
现在要将两表关联起来,新增分账字段,按项目里的账户数大致平均分钱,比如 100 分成 3 份,其中 N-1 个账户的金额要按 1/N 保留 2 位小数,即 33.33,第 N 个账户要补齐平均值,保证总额不变,即 100-33.33*2=33.34。
InvoiceID |
Amount |
ProjectID |
AccountCode |
SplitAmount |
1 |
100.0 |
1 |
12345 |
100.0 |
2 |
100.0 |
2 |
12345 |
50.0 |
2 |
100.0 |
2 |
7890 |
50.0 |
3 |
100.0 |
3 |
800 |
33.34 |
3 |
100.0 |
3 |
234 |
33.33 |
3 |
100.0 |
3 |
987 |
33.33 |
4 |
100.0 |
4 |
800 |
25.0 |
4 |
100.0 |
4 |
234 |
25.0 |
4 |
100.0 |
4 |
987 |
25.0 |
4 |
100.0 |
4 |
2579 |
25.0 |
SQL:
select *,
SplitAmount
+ case when rn = 1
then i.Amount - sum (i.SplitAmount)
over (partition by i.ProjectID)
else 0
end as AdjustedSplitAmount
from(
select
I.*, P.AccountCode,
round(I.Amount / count(I.InvoiceID) over (partition by P.ProjectID), 2) as SplitAmount,
row_number() over (partition by P.ProjectID order by p.AccountCode) as rn
from
#Invoices I Inner Join #Projects P on I.ProjectID = P.ProjectID
) i
SQL 分组后必须立刻汇总,不能保持分组子集,在子集上直接按规则新增分账字段,要用嵌套子查询 + 窗口函数间接实现,序号也用窗口函数额外生成,整体代码繁琐。
有分组子集时,SPL 代码可以更自然:
A |
|
1 |
=mssql.query("select I.*,P.AccountCode, from #Invoices I,#Projects P where I.ProjectID = P.ProjectID") |
2 |
=A1.group(ProjectID) |
3 |
=A2.(cnt=~.count(),avg=round(Amount/cnt,2), ~. derive(avg+if(#==1, Amount-avg*cnt): SplitAmount)) |
4 |
=A3.conj() |
A1:简单关联,加载数据。
A2:分组,但不汇总。
A3:处理每组数据,按规则直接新增分账字段。# 是天然序号,不必额外计算。
A4:合并各组。
英文版 https://c.scudata.com/article/1737603567463