通过 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);

最后,首先CTErecursive CTE 以创建从2000-01-01max 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 更新?的主要内容,如果未能解决你的问题,请参考以下文章

递归 CTE 如何逐行运行?

使用实体框架流利语法或内联语法编写递归 CTE

SQL Server CTE 递归查询全解

通过递归 CTE 分解 BOM

递归 CTE 性能不佳

将 CTE 应用于递归查询