SQL Server - 递归引用(循环,连接,插入?)

时间:2017-03-28 11:44:38

标签: sql-server loops tsql left-join

如果您能给我任何有关以下SQL Server挑战的最快解决方案的提示,我将不胜感激:

我们假设我的表格中包含DATECLIENT以及其他列中的几个特征。我需要计算COLUMN_1COLUMN_2但是:

  • COLUMN_1使用客户端的当前DATE特征以及之前DATE的{​​{1}}和COLUMN_1值(递归)参考)
  • DATE从当前日期开始另外使用COLUMN_2值(因此我想引用其最终值,而不是实现列逻辑的特定情况)'

如何在SQL Server中最有效地复制此逻辑?

我正在考虑循环遍历DATA和每个DATA,加入以前的DATA,首先计算COLUMN_1,然后计算COLUMN_2(但是如何确保COLUMN_1中的值可以访问COLUMN_1?)

此致 巴特

2 个答案:

答案 0 :(得分:2)

如果没有具体的例子,我们将无法告诉您哪种解决方案最有效,特别是当您正在寻找一种您描述为递归的解决方案时。如果可以使用窗口函数,则可能不需要完整的递归解决方案。

在sql server 2012+中,您可以访问lead()lag(),您可以使用它来根据分区和顺序获取列的上一个和下一个值。

select 
    client
  , date 
  , nextdate = lead(date) over (partition by client order by date)
  , prevdate = lag(date)  over (partition by client order by date)
  , column1 = 'do stuff with lead/lag'
  , column2 = 'do stuff with lead/lag'
from t

rextester示例:http://rextester.com/FFHU71709

返回:

+--------+------------+------------+------------+------------------------+------------------------+
| client |    date    |  nextdate  |  prevdate  |        column1         |        column2         |
+--------+------------+------------+------------+------------------------+------------------------+
|      1 | 2017-01-01 | 2017-01-02 | NULL       | do stuff with lead/lag | do stuff with lead/lag |
|      1 | 2017-01-02 | 2017-01-03 | 2017-01-01 | do stuff with lead/lag | do stuff with lead/lag |
|      1 | 2017-01-03 | NULL       | 2017-01-02 | do stuff with lead/lag | do stuff with lead/lag |
|      2 | 2017-01-02 | 2017-01-04 | NULL       | do stuff with lead/lag | do stuff with lead/lag |
|      2 | 2017-01-04 | 2017-01-06 | 2017-01-02 | do stuff with lead/lag | do stuff with lead/lag |
|      2 | 2017-01-06 | NULL       | 2017-01-04 | do stuff with lead/lag | do stuff with lead/lag |
+--------+------------+------------+------------+------------------------+------------------------+

在SQL Server 2012之前模拟超前/滞后的一种方法是使用outer apply()

select 
    client
  , date
  , nextdate
  , prevdate
  , column1 = 'do stuff with lead/lag'
  , column2 = 'do stuff with lead/lag'
from t
  outer apply (
    select top 1 nextdate = i.date
    from t i
    where i.client = t.client
      and i.date > t.date
    order by i.date asc
  ) n
  outer apply (
    select top 1 prevdate = i.date
    from t i
    where i.client = t.client
      and i.date < t.date
    order by i.date desc
  ) p

rextester演示:http://rextester.com/GGS1299

返回:

+--------+------------+------------+------------+---------------------------------+---------------------------------+
| client |    date    |  nextdate  |  prevdate  |             column1             |             column2             |
+--------+------------+------------+------------+---------------------------------+---------------------------------+
|      1 | 2017-01-01 | 2017-01-02 | NULL       | do stuff with nextdate/prevdate | do stuff with nextdate/prevdate |
|      1 | 2017-01-02 | 2017-01-03 | 2017-01-01 | do stuff with nextdate/prevdate | do stuff with nextdate/prevdate |
|      1 | 2017-01-03 | NULL       | 2017-01-02 | do stuff with nextdate/prevdate | do stuff with nextdate/prevdate |
|      2 | 2017-01-02 | 2017-01-04 | NULL       | do stuff with nextdate/prevdate | do stuff with nextdate/prevdate |
|      2 | 2017-01-04 | 2017-01-06 | 2017-01-02 | do stuff with nextdate/prevdate | do stuff with nextdate/prevdate |
|      2 | 2017-01-06 | NULL       | 2017-01-04 | do stuff with nextdate/prevdate | do stuff with nextdate/prevdate |
+--------+------------+------------+------------+---------------------------------+---------------------------------+

对于绝对需要递归的解决方案,那么您可能需要使用递归cte。

;with cte as (
  -- non recursive cte to add `nextdate` for recursive join
  select 
      t.client
    , t.date
    , nextdate = x.date
  from t
    outer apply (
      select top 1 i.date
      from t i
      where i.client = t.client
        and i.date > t.date
      order by i.date asc
    ) x
)
, r_cte as (
  --anchor rows / starting rows
  select 
      client
    , date
    , nextdate
    , prevDate = convert(date, null)
    , column1  = convert(varchar(64),null)
    , column2  = convert(varchar(64),null)
  from cte t
  where not exists (
    select 1
    from cte as i
    where i.client = t.client
      and i.date   < t.date
    )

  union all 
  --recursion starts here
  select 
      c.client
    , c.date
    , c.nextdate
    , prevDate = p.date
    , column1 = convert(varchar(64),'do recursive stuff with p.column1')
    , column2 = convert(varchar(64),'do recursive stuff with p.column2')
  from cte c
    inner join r_cte p
      on c.client = p.client
     and c.date   = p.nextdate
)
select *
from r_cte

rextester演示:http://rextester.com/LKH38243

返回:

+--------+------------+------------+------------+-----------------------------------+-----------------------------------+
| client |    date    |  nextdate  |  prevdate  |              column1              |              column2              |
+--------+------------+------------+------------+-----------------------------------+-----------------------------------+
|      1 | 2017-01-01 | 2017-01-02 | NULL       | NULL                              | NULL                              |
|      2 | 2017-01-02 | 2017-01-04 | NULL       | NULL                              | NULL                              |
|      2 | 2017-01-04 | 2017-01-06 | 2017-01-02 | do recursive stuff with p.column1 | do recursive stuff with p.column2 |
|      2 | 2017-01-06 | NULL       | 2017-01-04 | do recursive stuff with p.column1 | do recursive stuff with p.column2 |
|      1 | 2017-01-02 | 2017-01-03 | 2017-01-01 | do recursive stuff with p.column1 | do recursive stuff with p.column2 |
|      1 | 2017-01-03 | NULL       | 2017-01-02 | do recursive stuff with p.column1 | do recursive stuff with p.column2 |
+--------+------------+------------+------------+-----------------------------------+-----------------------------------+

参考

答案 1 :(得分:1)

如果使用SQL2012或更高版本,请查看功能LAG&amp; LEAD

例如,如果你想将前一行的值与此行的值一起使用 - 像这样的LAG:

DECLARE @T TABLE (DateCol DATETIME, StringCol VARCHAR(10))
INSERT INTO @T (DateCol, StringCol) VALUES ('2017-01-01','A'), ('2017-01-02','B'), ('2017-01-03','C'), ('2017-01-04','D'), ('2017-01-05','E')
SELECT DateCol, StringCol, PreviousRowStringcol = LAG(StringCol,1,NULL) OVER (ORDER BY DateCol) FROM @T