【问题标题】:Get Start and End date from multiple rows of dates, excluding weekends从多行日期中获取开始和结束日期,不包括周末
【发布时间】:2021-12-17 01:31:09
【问题描述】:

我正在尝试根据下表中的数据找出如何返回开始日期和结束日期:

Name Date From Date To
A 2022-01-03 2022-01-03
A 2021-12-29 2021-12-31
A 2021-12-28 2021-12-28
A 2021-12-27 2021-12-27
A 2021-12-23 2021-12-24
A 2021-11-08 2021-11-09

我追求的结果会是这样的:

Name Date From Date To
A 2021-12-23 2022-01-03
A 2021-11-08 2021-11-09

第一个表中的日期有时会超过周末,日期从和日期到,但如果该行在星期五结束,下一行从下一个星期一开始,则需要将其归类为相同的“块” ,如第二个表所示。根据How do I exclude Weekend days in a SQL Server query?,我希望使用DATEFIRST 设置来满足周末的需求,以避免使用日历表,但如果日历表最终成为最简单的方法,我很乐意考虑创建一个。

在上面的示例中,我只有 1 个名称,但表格将有多个名称,并且需要按此分组。

我看到的唯一示例是仅使用 1 个日期列进行记录,我努力更改他们的代码以适应我的示例。我发现的最接近的例子对我不起作用,因为它基于日期时间字段和时差 - find start and stop date for contiguous dates in multiple rows

【问题讨论】:

  • 我不明白结果。为什么 2021-12-242021-12-27 相隔 3 天时“连续”?
  • @Larnu 道歉,我忘了说它需要忽略周末。
  • 似乎是时候“投资”日历表了。
  • @Larnu 在 SQL Server 上设置 DATEFIRST 是不可能的吗?我试图避免使用日历表。
  • “我试图避免使用日历表。” 为什么?

标签: sql sql-server ssms


【解决方案1】:
with extended as (
    select name,
        date_from,
        case when datepart(weekday, date_to) = 6
            then dateadd(day, 2, date_to) else date_to end as date_to
    from t
), adjacent as (
    select *,
        case when dateadd(day, 1,
            lag(date_to) over (partition by name order by date_from)) = date_from
                then 0 else 1 end as brk
    from extended
), blocked as (
    select *, sum(brk) over (partition by name order by date_from) as grp
    from adjacent
)
select name, min(date_from), max(date_to) from blocked
group by name, grp;

我假设范围没有重叠,并且所有输入日期都在工作日。在我的手机上敲定这个时,我最初犯了两个错误。出于某种原因,我在脑海中颠倒了日期,然后我认为星期五是 5(如@@datefirst)而不是 6。(当然,无论如何,这可能会因地区设置而异。)使用表表达式是将某些细节模块化并隐藏在较低级别的逻辑中。在这种情况下,如果其中一些假设被证明是错误的,那么调整日期将非常容易。

https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=42e0c452d57d474232bcf991d6d3c43c

【讨论】:

    【解决方案2】:

    这是一个间隙和岛屿问题,您需要考虑周末的连续性。

    你可以这样做:

    select max(name) as name, min(date_from) as date_from, max(date_to) as date_to
    from (
      select *, sum(inc) over(order by date_to) as grp
      from (
        select *,
          case when lag(ext_to) over(order by date_to) = date_from
               then 0 else 1 end as inc
        from (
          select *,
            case when (datepart(weekday, date_to) = 6) 
                 then dateadd(day, 3, date_to)
                 else dateadd(day, 1, date_to) end as ext_to
          from t
        ) x
      ) y
    ) z
    group by grp
    

    结果:

    name  date_from   date_to
    ----  ----------  ----------
    A     2021-11-08  2021-11-09
    A     2021-12-23  2022-01-03
    

    请参阅db<>fiddle #1 的运行示例。

    注意:您的问题没有提及,但您可能希望按人细分。我没有这样做。

    编辑:按名称添加分区

    实际上,按名称进行分区非常简单。以下查询执行此操作:

    select name, min(date_from) as date_from, max(date_to) as date_to
    from (
      select *, sum(inc) over(partition by name order by date_to) as grp
      from (
        select *,
          case when lag(ext_to) over(partition by name order by date_to) = date_from
               then 0 else 1 end as inc
        from (
          select *,
            case when (datepart(weekday, date_to) = 6) 
                 then dateadd(day, 3, date_to)
                 else dateadd(day, 1, date_to) end as ext_to
          from t
        ) x
      ) y
    ) z
    group by name, grp
    order by name, grp
    

    db<>fiddle #2查看运行查询。

    【讨论】:

    • 是的,抱歉......我最终根据它下面的第一个 cmets 重写了这个问题并丢失了那个细节......就像按名称分组的结果一样。我会看看我是否可以解决你的问题。
    • @Pawel 添加了第二个解决方案。
    • 效果很好!谢谢
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-10
    • 1970-01-01
    相关资源
    最近更新 更多