从多行日期中获取开始和结束日期,不包括周末

Get Start and End date from multiple rows of dates, excluding weekends

我正在尝试根据下面的数据 return 开始日期和结束日期 table:

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

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

在上面的示例中,我只有 1 个名称,但是 table 将有多个名称,需要按名称分组。

我看到的唯一例子是只使用 1 个日期列作为记录,我努力改变他们的代码来满足我的例子。我找到的最接近的示例对我不起作用,因为它基于日期时间字段和时差 - find start and stop date for contiguous dates in multiple rows

这是一个 Gaps & Island 问题,需要考虑周末的连续性。

你可以这样做:

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 处的 运行 查询。

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 日。(当然,无论如何这可能会因区域设置而异。)一个使用 table 表达式的优点是模块化并将某些细节隐藏在较低级别的逻辑中。在这种情况下,如果其中一些假设被证明是错误的,调整日期将非常容易。

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