当不存在当前日期值时,SQL Server 查询以结转前一天的值

Posted

技术标签:

【中文标题】当不存在当前日期值时,SQL Server 查询以结转前一天的值【英文标题】:SQL Server Query to carry over values from previous day when no current day values exists 【发布时间】:2020-05-14 00:14:03 【问题描述】:

我有一个查询跟踪交易者在每个交易时段的风险资本。当某一天没有交易时,我需要将最后一个活跃日的日终值结转到下一个活跃日。这是我目前所拥有的:

DECLARE @Start DATETIME = '2019-08-20'
DECLARE @End   DATETIME = '2019-08-27'

DECLARE @history TABLE( Id INT, AccountId INT, AllocatedCapital MONEY, RunningAllocatedCapital MONEY,RN INT,SessionDate DATETIME) 

INSERT INTO @history(Id, AccountId, AllocatedCapital, RunningAllocatedCapital,RN,SessionDate) 
     VALUES (362082,    1182,   -170150.0000,   -170150.0000,   1,  '2019-08-20'),
            (362090,    1182,   -4167.9600,     -199466.4600,   1,  '2019-08-21'),
            (362088,    1182,   -10330.0000,    -195298.5000,   2,  '2019-08-21'),
            (362086,    1182,   -9454.5000,     -184968.5000,   3,  '2019-08-21'),
            (362084,    1182,   -5364.0000,     -175514.0000,   4,  '2019-08-21'),
            (362094,    1182,   -4140.0000,     -203606.4600,   1,  '2019-08-22'),
            (362092,    1182,   -4140.0000,     -207746.4600,   2,  '2019-08-22'),
            (362105,    1182,    4140.0000,     -187052.4800,   1,  '2019-08-27')


;WITH tradingdays  as (

SELECT TOP (DATEDIFF(DAY, @Start, @End) + 1)
       DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1,@Start) SessionDate 
  FROM sys.all_objects a
 CROSS JOIN sys.all_objects b

)

SELECT -MIN(RunningAllocatedCapital) MaxCapitalAtRisk,
       -MAX(CASE H.RN WHEN 1 THEN H.RunningAllocatedCapital END)EodCapitalAtRisk,
        C.SessionDate
  FROM tradingdays C
  LEFT JOIN @history H ON H.SessionDate = C.SessionDate
 WHERE DATENAME(dw,C.SessionDate) NOT IN ('Saturday','Sunday')
 GROUP BY C.SessionDate, H.SessionDate
 ORDER BY C.SessionDate

而不是这个:

MaxCapitalAtRisk    EodCapitalAtRisk    SessionDate
170150.00           170150.00           2019-08-20 00:00:00.000
199466.46           199466.46           2019-08-21 00:00:00.000
207746.46           203606.4600         2019-08-22 00:00:00.000
NULL                NULL                2019-08-23 00:00:00.000
NULL                NULL                2019-08-26 00:00:00.000
187052.48           187052.48           2019-08-27 00:00:00.000

我的结果集应该如下所示:

MaxCapitalAtRisk    EodCapitalAtRisk    SessionDate
170150.00           170150.00           2019-08-20 00:00:00.000
199466.46           199466.46           2019-08-21 00:00:00.000
207746.46           203606.46           2019-08-22 00:00:00.000
203606.46           203606.46           2019-08-23 00:00:00.000
203606.46           203606.46           2019-08-26 00:00:00.000
187052.48           187052.48           2019-08-27 00:00:00.000 

我知道在 SQL Server 中有一些简洁的方法可以在不使用子查询或游标的情况下执行此操作,但我不记得如何执行此操作。

【问题讨论】:

【参考方案1】:

基本上,您正在寻找带有ignore nulls 选项的lag()。 SQL Server 不支持这一点,但我们可以使用 gaps-and-island 技术来模拟它。

这个想法是使用条件总和或计数来构建由一个“常规”记录(岛)和 0 到 N 个“缺失”记录(间隙)组成的记录组。然后,我们可以使用first_value() 用岛的值来填补空白:

with tradingdays as (
    select @start SessionDate 
    union all select dateadd(day, 1, SessionDate) from tradingdays where SessionDate < @end
)
select
    SessionDate,
    first_value(MaxCapitalAtRisk) over(partition by grp order by SessionDate) MaxCapitalAtRisk,
    first_value(EodCapitalAtRisk) over(partition by grp order by SessionDate) EodCapitalAtRisk
from (
    select 
        td.SessionDate, 
        - min(RunningAllocatedCapital) MaxCapitalAtRisk,
        - max(case h.rn when 1 then h.runningallocatedcapital end) EodCapitalAtRisk,
        count(h.SessionDate) over(order by td.SessionDate) grp
    from tradingdays td 
    left join @history h on h.SessionDate = td.SessionDate 
    where datename(dw, td.SessionDate) not in ('Saturday', 'Sunday')
    group by td.SessionDate, h.SessionDate
) t
order by SessionDate

我更改了生成日期的公用表表达式以使用递归,因为我发现它更容易遵循 - 但这不会改变逻辑,如果你更喜欢它,你可以切换回原来的 cte。如果您坚持我的 cte,并且您的日期分布在 100 天以上,那么您需要在查询的最后添加 option(maxrecursion 0)

Demo on DB Fiddle

会议日期 |最大资本风险 | EodCapitalAtRisk :------------------------ | :--------------- | :--------------- 2019-08-20 00:00:00.000 | 170150.0000 | 170150.0000 2019-08-21 00:00:00.000 | 199466.4600 | 199466.4600 2019-08-22 00:00:00.000 | 207746.4600 | 207746.4600 2019-08-23 00:00:00.000 | 207746.4600 | 207746.4600 2019-08-26 00:00:00.000 | 207746.4600 | 207746.4600 2019-08-27 00:00:00.000 | 187052.4800 | 187052.4800

【讨论】:

我没有提供足够全面的示例数据集。当有差距时,我需要在两个字段中插入以前的 EodCapitalAtRisk。当某一天没有交易时,MaxCapitalAtRisk 是之前的 EodCapitalAtRisk。希望它很容易修改您提供的查询。

以上是关于当不存在当前日期值时,SQL Server 查询以结转前一天的值的主要内容,如果未能解决你的问题,请参考以下文章

sql server 日期 查询技巧

选择查询以连续查找最大日期

sql server 日期范围查询

SQL查询以获取特定年份的当前日期(考虑月份和日期)的数据

sqlserver如何根据当前日期获得上月某一天的日期

SQL查询以获取未来的日期记录(ALL)以及当前的日期记录