【问题标题】:Look back 7 days from a specif date and retrieve a value only if the day exists从特定日期回溯 7 天并仅在该日期存在时检索值
【发布时间】:2019-09-13 16:10:45
【问题描述】:

我有一个表格,它代表特定日期的工作小时数。从该表中,我希望能够向代表前 7 天工作时间的每一行再添加 7 列。

我一直在使用 LAG() 来完成此操作,并且它工作正常,直到我发现一个问题,即我在几天之间存在差距,现在我选择前 7 行而不是选择前 7 天。

这个脚本演示了我想要完成的事情。

DECLARE @data TABLE ( 
    [user_id] int,
    [date] DATETIME, 
    [day_hours_worked] VARCHAR(15)
);

INSERT INTO @data
VALUES 
  ( '1', '2019-09-07 00:00:00.000', '07_8' )
, ( '1', '2019-09-08 00:00:00.000', '08_4' )
, ( '1', '2019-09-09 00:00:00.000', '09_6' )
, ( '1', '2019-09-10 00:00:00.000', '10_8' )
, ( '1', '2019-09-11 00:00:00.000', '11_4' )
, ( '1', '2019-09-12 00:00:00.000', '12_6' )
, ( '1', '2019-09-13 00:00:00.000', '13_8' )
, ( '1', '2019-09-14 00:00:00.000', '14_4' )
, ( '1', '2019-09-20 00:00:00.000', '20_8' );

select 
    [user_id],
    [date],
    [day_hours_worked],
    LAG([day_hours_worked], 1) OVER (PARTITION BY [user_id] ORDER BY  [date]) [Hours-1],
    LAG([day_hours_worked], 2) OVER (PARTITION BY [user_id] ORDER BY  [date]) [Hours-2],
    LAG([day_hours_worked], 3) OVER (PARTITION BY [user_id] ORDER BY  [date]) [Hours-3],
    LAG([day_hours_worked], 4) OVER (PARTITION BY [user_id] ORDER BY  [date]) [Hours-4],
    LAG([day_hours_worked], 5) OVER (PARTITION BY [user_id] ORDER BY  [date]) [Hours-5],
    LAG([day_hours_worked], 6) OVER (PARTITION BY [user_id] ORDER BY  [date]) [Hours-6],
    LAG([day_hours_worked], 7) OVER (PARTITION BY [user_id] ORDER BY  [date]) [Hours-7] 
from @data

这就是我得到的。

user_id date    day_hours_worked    Hours-1 Hours-2 Hours-3 Hours-4 Hours-5 Hours-6 Hours-7
1   2019-09-07 00:00:00.000 07_8    NULL    NULL    NULL    NULL    NULL    NULL    NULL
1   2019-09-08 00:00:00.000 08_4    07_8    NULL    NULL    NULL    NULL    NULL    NULL
1   2019-09-09 00:00:00.000 09_6    08_4    07_8    NULL    NULL    NULL    NULL    NULL
1   2019-09-10 00:00:00.000 10_8    09_6    08_4    07_8    NULL    NULL    NULL    NULL
1   2019-09-11 00:00:00.000 11_4    10_8    09_6    08_4    07_8    NULL    NULL    NULL
1   2019-09-12 00:00:00.000 12_6    11_4    10_8    09_6    08_4    07_8    NULL    NULL
1   2019-09-13 00:00:00.000 13_8    12_6    11_4    10_8    09_6    08_4    07_8    NULL
1   2019-09-14 00:00:00.000 14_4    13_8    12_6    11_4    10_8    09_6    08_4    07_8
1   2019-09-20 00:00:00.000 20_8    14_4    13_8    12_6    11_4    10_8    09_6    08_4

最后一行是唯一不正确的行,因为它查看的是前 7 行而不是前 7 天。在我的场景中,预期的结果应该是:

user_id date    day_hours_worked    Hours-1 Hours-2 Hours-3 Hours-4 Hours-5 Hours-6 Hours-7
1   2019-09-07 00:00:00.000 07_8    NULL    NULL    NULL    NULL    NULL    NULL    NULL
1   2019-09-08 00:00:00.000 08_4    07_8    NULL    NULL    NULL    NULL    NULL    NULL
1   2019-09-09 00:00:00.000 09_6    08_4    07_8    NULL    NULL    NULL    NULL    NULL
1   2019-09-10 00:00:00.000 10_8    09_6    08_4    07_8    NULL    NULL    NULL    NULL
1   2019-09-11 00:00:00.000 11_4    10_8    09_6    08_4    07_8    NULL    NULL    NULL
1   2019-09-12 00:00:00.000 12_6    11_4    10_8    09_6    08_4    07_8    NULL    NULL
1   2019-09-13 00:00:00.000 13_8    12_6    11_4    10_8    09_6    08_4    07_8    NULL
1   2019-09-14 00:00:00.000 14_4    13_8    12_6    11_4    10_8    09_6    08_4    07_8
1   2019-09-20 00:00:00.000 20_8    NULL    NULL    NULL    NULL    NULL    14_4    13_8

这甚至可以使用 LAG() 吗?有没有更好的方法来完成我想要的?

提前致谢。

【问题讨论】:

  • 一个更相关的问题是,为什么您首先需要这样做?你想完成什么?
  • 外部联接到保证每个日期有一行的日历表,然后像以前一样继续使用LAG
  • @iamdave 我需要这样做,因为我可以访问前 7 天每天在一行中工作的时间。这是因为我正在使用这些数据来提供一个报告,该报告对上一行或下一行一无所知,需要它进行一些计算。
  • @MartinSmith 你能提供一些代码吗?
  • 没时间,但概念很简单。在可预见的将来,为感兴趣的范围内的每个日期创建一个日历表,其中每个日期都有一行。选择感兴趣期间的 where 子句。这为您提供每个日期一行,现在外部连接到您的原始表,每个日期您仍然有一行,因此您不会遇到缺少行的问题,并且您的 LAG 方法计数行仍然有效

标签: sql sql-server tsql datetime


【解决方案1】:

我认为在 SQL Server 中没有一种优雅的方法可以做到这一点。最简单的可能是case 表达式:

(case when lag(date, 1) over (partition by user_id order by date) = dateadd(day, 1, date)
      then lag(day_hours_worked, 1) over (partition by [user_id] order by [date]) 
 end) as [Hours-1],

一些数据库支持range 有时间间隔,让这更简单。

编辑:

你可以用apply做你想做的事:

select d.*, d2.*, d2.*
from data d outer apply
     (select max(case when dd.diff = 1 then d2.day_hours_worked end) as w1,
             max(case when dd.diff = 2 then d2.day_hours_worked end) as w2,
             max(case when dd.diff = 3 then d2.day_hours_worked end) as w3,
             max(case when dd.diff = 4 then d2.day_hours_worked end) as w4,
             max(case when dd.diff = 5 then d2.day_hours_worked end) as w5,
             max(case when dd.diff = 6 then d2.day_hours_worked end) as w6,
             max(case when dd.diff = 7 then d2.day_hours_worked end) as w7
      from data d2 cross apply
           (values (datediff(day, d2.date, d.date) )) dd(diff)
      where d2.date < d.date and d2.date >= dateadd(day, -7, d.date)
     ) d2;

Here 是一个 dbfiddle。

【讨论】:

  • 你的意思是 dateadd(day, -1, date) 对吧?不幸的是,我已经尝试过 CASE 方法,但它仍然查看前一行。所以在我的场景中,一旦我到达 -6 天(即第 14 天),lag(date, 6) 已经在查看前第 6 行,即第 9 天。
  • 这不会按预期工作,如果在最近一天的 7 天中有一个缺失的一天,那么效果将级联下降,所有日期条件都将评估为 false,因此所有列将为空,而不仅仅是缺少的一天
  • @MartinSmith。 . .你说的对。我提供了一个有效的答案。
  • @GordonLinoff 在我看来这很丑(不喜欢交叉应用)。就像您说在 SQL 中没有优雅的方法可以做到这一点。但是效果很好!!!即使是多个用户。非常感谢!!!!!!
【解决方案2】:

您的逻辑看起来是正确的,只是因为缺少日期而失败。所以我想如果我们添加缺失的日期,你的逻辑就会起作用。

请按照我的代码。您可能需要更改其中的某些部分,但它会为您提供单个用户想要的结果。

;with wrk as (
    select 
        [user_id],
        [date],
        [day_hours_worked]
    from @data
)
-- generate list of dates, starting from min date in your table
, allDates as (
    select Dt = dateadd(day, row_number() over (order by object_id) - 1, (select min([date]) from wrk))
    from sys.objects
)
-- select only dates missing from your table
, missingDates as (
    select Dt
    from allDates
    where Dt < (select max([date]) from wrk)
    and not exists (select [date] from wrk where wrk.[date] = Dt)
)
, fullData as (
    select [user_id],
            [date],
            [day_hours_worked]
    from wrk
    union
    -- we need [user_id] here, so I get it from your table
    -- this is not perfect and works only if you work on one single user
    select (select top 1 [user_id] from wrk), Dt, null
    from missingDates
)
, final as (
    select 
        [user_id],
        [date],
        [day_hours_worked],
        LAG([day_hours_worked], 1) OVER (PARTITION BY [user_id] ORDER BY  [date]) [Hours-1],
        LAG([day_hours_worked], 2) OVER (PARTITION BY [user_id] ORDER BY  [date]) [Hours-2],
        LAG([day_hours_worked], 3) OVER (PARTITION BY [user_id] ORDER BY  [date]) [Hours-3],
        LAG([day_hours_worked], 4) OVER (PARTITION BY [user_id] ORDER BY  [date]) [Hours-4],
        LAG([day_hours_worked], 5) OVER (PARTITION BY [user_id] ORDER BY  [date]) [Hours-5],
        LAG([day_hours_worked], 6) OVER (PARTITION BY [user_id] ORDER BY  [date]) [Hours-6],
        LAG([day_hours_worked], 7) OVER (PARTITION BY [user_id] ORDER BY  [date]) [Hours-7] 
    from fullData
)
select *
from final
where day_hours_worked is not null
order by [date]

【讨论】:

  • 感谢您花时间构建此答案,它对您所说的单个用户非常有效!不幸的是,我有不止一个用户,但我感谢您的回答。
猜你喜欢
  • 2020-06-02
  • 2016-06-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多