通过 LEAD/LAG 或递归 CTE 更新?
Posted
技术标签:
【中文标题】通过 LEAD/LAG 或递归 CTE 更新?【英文标题】:Update by LEAD/LAG or Recursive CTE? 【发布时间】:2019-09-26 03:53:23 【问题描述】:有一个表格持有日期和账户余额。 但是,余额在某些日期不可用。 假设当日期不可用时余额不会改变。 如何更新所有日期的余额信息?
这是一个例子: 表 D 包含所有有效日期。
2000-01-01
2000-01-02
2000-01-03
2000-01-04
2000-01-05
2000-01-06
2000-01-07
2000-01-08
2000-01-09
表 A 包含日期和帐户余额。
2000-01-02 $100
2000-01-05 $200
2000-01-09 $700
最终,我想生成一个这样的表:
2000-01-01 null
2000-01-02 $100
2000-01-03 $100
2000-01-04 $100
2000-01-05 $200
2000-01-06 $200
2000-01-07 $200
2000-01-08 $200
2000-01-09 $700
我想到了以下几点:
领先和落后, 递归 CTE但是,它们似乎都不适合这种情况。
【问题讨论】:
mysql 还是 SQL Server?它们是完全不同的 RDBMS? 我更喜欢使用 SQL Server,但 MySQL 也可以,如果它不涉及使用 MySQL-only 函数。 如果您向我们展示了您的尝试,请方便。 【参考方案1】:实现此目的的一种方法是使用第一个值并创建一些排名函数。我正在使用 SQL 服务器
with cte as
(
select '2000-01-01' as Datenew union all
select '2000-01-02' as Datenew union all
select '2000-01-03' as Datenew union all
select '2000-01-04' as Datenew union all
select '2000-01-05' as Datenew union all
select '2000-01-06' as Datenew union all
select '2000-01-07' as Datenew union all
select '2000-01-08' as Datenew union all
select '2000-01-09' as Datenew ), cte2 as (
select '2000-01-02' as DateSal, '100' as Salary union all
select '2000-01-05' as DateSal, '200' as Salary union all
select '2000-01-09' as DateSal, '700' as Salary )
select datenew, Salary = FIRST_VALUE(salary) over (partition by ranking order by datenew) from (
select datenew, salary ,
sum(case when DateSal is not null then 1 end) over (order by datenew) ranking
from cte c
left join cte2 c2 on c.Datenew = c2.DateSal ) tst
order by datenew
--Sum 创建运行总计以创建分组,第一个值确保我们为给定组获得相同的值。
这是输出
【讨论】:
【参考方案2】:ANSI SQL。
Table_D
-------
dd(field name)
-------
2000-01-01
2000-01-02
2000-01-03
2000-01-04
2000-01-05
2000-01-06
2000-01-07
2000-01-08
2000-01-09
Table_A
-------
dd(field name) cost(field name)
-------
2000-01-02 $100
2000-01-05 $200
2000-01-09 $700
select a.dd
, (
case when a.cost is null then min(a.cost) OVER (partition by a.cost_group ORDER BY a.dd) else a.cost end
) as cost
from (
select a.dd, b.cost
, count(b.cost) over (order by a.dd) as cost_group
from Table_D a
left join Table_A b on (b.dd = a.dd)
) a
【讨论】:
【参考方案3】:我们可以使用count() over ()
窗口函数创建不同的partitions
,然后使用min() over ()
函数将最小值传播到该分区。
首先,我创建了临时变量表来保存 OP 数据 -
declare @xyz table (dt date, amount int)
insert into @xyz values
('2000-01-02','100'),
('2000-01-05','200'),
('2000-01-09','700');
其次,我将从上表中获取最大日期。
declare @maxDT date = (select cast(max(dt) as date) from @xyz);
最后,首先CTE
是recursive CTE
以创建从2000-01-01
到max date
的数据存储在上面的变量中。第二个CTE
是创建分区。
;with cte as (
select cast('2000-01-01' as date) as dt
union all
select dateadd(day,1,cte.dt) from cte where cte.dt < @maxDT
), cte2 as (
select cte.dt, x.amount, x.dt as dt2, count(x.dt) over (order by cte.dt) as ranking
from cte
left join @xyz x on x.dt = cte.dt
)
select dt, min(amount) over (partition by ranking)
from cte2;
【讨论】:
【参考方案4】:SQL Server 不支持LAG()
或LAST_VALUE()
的IGNORE NULLS
选项。这其实是最简单的方法。
相反,您可以使用APPLY
:
select d.*, a.balance
from dates d outer apply
(select top (1) a.*
from a
where a.date <= d.date
order by a.date desc
) a;
或使用相关子查询的等价物:
select d.*,
(select top (1) a.*
from a
where a.date <= d.date
order by a.date desc
fetch first 1 row only
)
from dates d;
这将适用于 MySQL 和 SQL Server,但需要注意的是,您需要在 MySQL 中使用LIMIT
。
也就是说,如果您有大量数据(在“日期”粒度上不太可能),那么两步窗口函数可能是更好的解决方案:
select ad.date,
max(ad.balance) over (partition by grp) as balance
from (select d.date, a.balance,
count(a.date) over (order by d.date) as grp
from dates d left join
a
on d.date = a.date
) ad;
子查询为每个余额值和以下日期分配一个“组”。这个“组”然后用于在外部查询中分配余额。
此版本可在 MySQL 或 SQL Server 中运行。
【讨论】:
以上是关于通过 LEAD/LAG 或递归 CTE 更新?的主要内容,如果未能解决你的问题,请参考以下文章