Sql 连接日期序列并计算连续天数

Sql 连接日期序列并计算连续天数,sql,postgresql,Sql,Postgresql,假设我有一张如下的桌子 date add_days 2015-01-01 5 2015-01-04 2 2015-01-11 7 2015-01-20 10 2015-01-30 1 我想做的是检查天数余额,即如果日期大于或小于前一个日期+N天(添加天数),如果是连续序列,则取累计天数计数 所以算法的工作原理应该是 for i in 2:N_rows { days_balance[i] := date[i-1] + add_days[i-1] - date[i]

假设我有一张如下的桌子

date        add_days
2015-01-01  5
2015-01-04  2
2015-01-11  7
2015-01-20  10
2015-01-30  1
我想做的是检查
天数余额
,即如果
日期
大于或小于前一个日期+N天(
添加天数
),如果是连续序列,则取累计天数计数

所以算法的工作原理应该是

for i in 2:N_rows {
   days_balance[i] := date[i-1] + add_days[i-1] - date[i]
   if days_balance[i] >= 0 then
      date[i] := date[i] + days_balance[i]
}
预期结果如下

date        days_balance
2015-01-01  0
2015-01-04  2
2015-01-11  -3
2015-01-20  -2
2015-01-30  0

在纯SQL中可能吗?我想它应该与一些条件连接一起使用,但看不出它是如何实现的。

好吧,我想我有一个递归CTE(对不起,我现在只有Microsoft SQL Server可用,所以它可能不符合PostgreSQL)

此外,我认为你的预期结果是错的(见上面的评论)。如果没有,这可能会被修改,以符合您的数学

drop table junk
create table junk(date DATETIME, add_days int)

insert into junk values
('2015-01-01',5  ),       
('2015-01-04',2  ),      
('2015-01-11',7  ),      
('2015-01-20',10 ),      
('2015-01-30',1  )    

;WITH cte as
(
    select ROW_NUMBER() OVER (ORDER BY date) i, date, add_days, ISNULL(DATEDIFF(DAY, LAG(date) OVER (ORDER BY date), date), 0) days_since_prev
    FROM Junk
)
,recursiveCte (i, date, add_days, days_since_prev, days_balance, math)  as
(
    select top 1 
        i, 
        date, 
        add_days, 
        days_since_prev, 
        0 [days_balance], 
        CAST('no math for initial one, just has zero balance' as varchar(max)) [math]
    from cte where i = 1
    UNION ALL --recursive step now
    select 
        curr.i, 
        curr.date, 
        curr.add_days, 
        curr.days_since_prev, 
        prev.days_balance - curr.days_since_prev + prev.add_days [days_balance],
        CAST(prev.days_balance as varchar(max)) + ' - ' + CAST(curr.days_since_prev as varchar(max)) + ' + ' + CAST(prev.add_days as varchar(max)) [math]
    from cte curr
    JOIN recursiveCte prev ON curr.i = prev.i + 1       
)
select i,  DATEPART(day,date) [day], add_days, days_since_prev, days_balance, math
from recursiveCTE
order by date
结果如下:

+---+-----+----------+-----------------+--------------+------------------------------------------------+
| i | day | add_days | days_since_prev | days_balance | math                                           |
+---+-----+----------+-----------------+--------------+------------------------------------------------+
| 1 | 1   | 5        | 0               | 0            | no math for initial one, just has zero balance |
| 2 | 4   | 2        | 3               | 2            | 0 - 3 + 5                                      |
| 3 | 11  | 7        | 7               | -3           | 2 - 7 + 2                                      |
| 4 | 20  | 10       | 9               | -5           | -3 - 9 + 7                                     |
| 5 | 30  | 1        | 10              | -5           | -5 - 10 + 10                                   |
+---+-----+----------+-----------------+--------------+------------------------------------------------+

我发布了另一个答案,因为比较它们可能很好,因为它们使用不同的方法(这一个只是进行n^2样式的连接,另一个使用递归CTE)。这一项利用了这样一个事实,即在计算某一行之前,您不必计算前一行的天数余额,您只需将前几天的数据相加即可

drop table junk
create table junk(date DATETIME, add_days int)

insert into junk values
('2015-01-01',5  ),       
('2015-01-04',2  ),      
('2015-01-11',7  ),      
('2015-01-20',10 ),      
('2015-01-30',1  )    

;WITH cte as
(
    select ROW_NUMBER() OVER (ORDER BY date) i, date, add_days, ISNULL(DATEDIFF(DAY, LAG(date) OVER (ORDER BY date), date), 0) days_since_prev
    FROM Junk
)
, combinedWithAllPreviousDaysCte as
(
    select i [curr_i], date [curr_date], add_days [curr_add_days], days_since_prev [curr_days_since_prev], 0 [prev_add_days], 0 [prev_days_since_prev] from cte where i = 1 --get first row explicitly since it has no preceding rows
    UNION ALL
    select curr.i [curr_i], curr.date [curr_date], curr.add_days [curr_add_days], curr.days_since_prev [curr_days_since_prev], prev.add_days [prev_add_days], prev.days_since_prev [prev_days_since_prev]
    from cte curr 
    join cte prev on curr.i > prev.i --join to all previous days
)
select curr_i, curr_date, SUM(prev_add_days) - curr_days_since_prev - SUM(prev_days_since_prev) [days_balance]
from combinedWithAllPreviousDaysCte
group by curr_i, curr_date, curr_days_since_prev
order by curr_i
产出:

+--------+-------------------------+--------------+
| curr_i |        curr_date        | days_balance |
+--------+-------------------------+--------------+
|      1 | 2015-01-01 00:00:00.000 |            0 |
|      2 | 2015-01-04 00:00:00.000 |            2 |
|      3 | 2015-01-11 00:00:00.000 |           -3 |
|      4 | 2015-01-20 00:00:00.000 |           -5 |
|      5 | 2015-01-30 00:00:00.000 |           -5 |
+--------+-------------------------+--------------+

我不太明白你的算法是如何返回预期结果的?但让我分享一个我想出的可能有用的技巧

仅当数据的最终结果要导出到Excel时,此操作才有效,即使如此,它也不会在所有情况下都有效,具体取决于导出数据集的格式,但在这里

如果您熟悉Excel公式,我发现,如果您在SQL中以另一个字段的形式编写Excel公式,它将在导出到Excel后立即为您执行该公式(对我来说,最好的方法就是将其复制并粘贴到Excel中,这样它就不会将其格式化为文本)

对于你的例子,这里是你可以做的(再次注意,我不理解你的算法,所以这可能是错误的,但这只是给你一个概念)

这项工作的关键部分是使用
INDEX
formula函数根据当前单元格的位置选择单元格。因此,
ROW()-1
告诉它获取上一条记录的结果,而
COLUMN()-2
意味着从当前记录左侧的两列中获取值。因为不能使用像
A2+B2-A3
这样的单元格引用,因为行号在导出时不会更改,并且它假定列的位置

我将SQL字符串连接与
|
一起使用,这样在屏幕上更容易阅读


我在excel中试过这个;它与您预期的结果不匹配。但是,如果这项技术对您有效,那么只需修改excel公式即可。

如果您使用的是PostgreSQL 9.1.13或更高版本,则需要查看PostgreSQL中的
lag
lead
窗口函数。另外,你如何计算第一条记录的上一个日期和上一个添加日期?@FutbolFan按当前日期和上一个日期,我指的是后面的行-为了清晰起见,我将进行编辑。是的,我得到了这部分。但是,对于date=
2015-01-01
的行,我要问的是该记录的上一个日期和上一个添加日期是多少?预期结果正确吗?第四排不是应该是-5吗?这是我的数学:从第01天到第04天=0余额减去3天的变化+5天加起来=2;第04天至第11天=2个余额减去7天变化+2个加上天数=-3;第11天到第20天=-3天余额减去9天变化+7天加上天数=-5;第20天至第30天=-5天余额减去10天变化+10天相加=-5天@FutbolFan我更新了我的问题和伪代码-现在清楚了吗?而且,我觉得这不是一个很好的解决方案,因为递归级别随着每一行的增加而增加,所以只使用一些东西来迭代处理结果可能会更好/更有效(但最初的问题并不是这样要求的)。
SELECT
    date
  , add_days
  , '=INDEX($1:$65536,ROW()-1,COLUMN()-2)'
  ||'+INDEX($1:$65536,ROW()-1,COLUMN()-1)'
  ||'-INDEX($1:$65536,ROW(),COLUMN()-2)'
    AS "days_balance[i]"
  ,'=IF(INDEX($1:$65536,ROW(),COLUMN()-1)>=0'
  ||',INDEX($1:$65536,ROW(),COLUMN()-3)'
  ||'+INDEX($1:$65536,ROW(),COLUMN()-1))'
    AS "date[i]"
FROM
  myTable
ORDER BY /*Ensure to order by whatever you need for your formula to work*/