查找连续的服务天数,中间没有休息日

Posted

技术标签:

【中文标题】查找连续的服务天数,中间没有休息日【英文标题】:Find consecutive days of service without a day break in between 【发布时间】:2014-10-15 21:39:31 【问题描述】:

服务表:

claimid, customerid, serv-start-date, service-end-date, charge
1, A1, 1-1-14 , 1-5-14 , $200
2, A1, 1-6-14 , 1-8-14 , $300
3, A1, 2-1-14 , 2-1-14 , $100
4, A2, 2-1-14 , 2-1-14 , $100
5, A2, 2-3-14 , 2-5-14 , $100
6, A2, 2-6-14 , 2-8-14 , $100

问题: 基本上看最大总连续天数 服务开始日期和结束日期。 客户 A1 为 8 天(1-5 加 6-8),客户 A2 为 5 6 天(3-5 加 6-8)...(claimid 是唯一的 PK )。

日期采用 m-d-yy 表示法。

【问题讨论】:

如果它是每个客户的单一值,那么您可能意味着找到最大连续服务天数。而对于客户 2,它实际上应该是 6。 您能否有一个客户的服务记录具有重叠的日期范围。为便于讨论,数据库/表记录客户 A3 在 2014-01-01 至 2014-01-05 和 2014-01-03 至 2014-01-08 有服务是否合法? 在其他相关问题中,请查看Date difference between consecutive rows、Aggregate adjacent only records with T-SQL 和Analytical Query。 【参考方案1】:

这有点混乱,因为您可能有没有多条记录的客户。这使用公共表表达式以及 max 聚合和 union all 来确定您的结果:

with cte as (
  select s.customerid, 
    s.servicestartdate,
    s2.serviceenddate,
    datediff(day,s.servicestartdate,s2.serviceenddate)+1 daysdiff
  from service s
    join service s2 on s.customerid = s2.customerid
      and s2.servicestartdate in (s.serviceenddate, dateadd(day,1,s.serviceenddate))
)
select customerid, max(daysdiff) daysdiff
from cte
group by customerid
union all
select customerid, max(datediff(day, servicestartdate, serviceenddate))
from service s
where not exists (
  select 1
  from cte
  where s.customerid = cte.customerid
  )
group by customerid
SQL Fiddle Demo

union 语句中的第二个查询是确定那些连续几天没有多条记录的服务记录。

【讨论】:

这是否处理看似合理但未说明的情况,即单个客户连续 3 次(或更多)提供服务:例如,2014-01-01 到 2014-01-03 和 2014-01- 04 到 2014-01-06 和 2014-01-07 到 2014-01-07(应该产生 7 天的答案)? @JonathanLeffler -- 我同意,这会变得更加混乱 :) 在我的脑海中,也许是一个带有交叉连接的日期表以获得所需的结果。递归 CTE 可能——但无论如何它都不是微不足道的...... @sgeddes ya 如果记录都按照样本的方式排序,这很好,但很可能不是。所以它不会捕捉到我们需要的东西。【参考方案2】:

来吧,我认为这是最简单的方法:

SELECT customerid, sum(datediff([serv-end-date],[serv-start-date]))
  FROM [service]
 GROUP BY customerid

您必须决定同一天的开始/结束记录是否计为 1。如果是,则在 datediff 函数中添加 1,例如sum(datediff([serv-end-date],[serv-start-date]) + 1)

如果您不想计算同一天的服务,但希望在汇总时包括开始/结束日期,则需要添加一个仅在开始日期和结束日期不同时执行 +1 的函数.如果你想知道如何做到这一点,请告诉我。

【讨论】:

我看不出这是如何对连续天数求和的——它只是对每个客户的所有日期差异求和。 每条记录存储一组从开始日期到结束日期的连续天数。您所要做的就是按客户总结。【参考方案3】:

我认为解决 Jonathan Leffler 描述的问题的唯一方法(在对另一个答案的评论中)是使用临时表来合并连续的日期范围。这最好在 SP 中完成 - 但如果以下批次无法产生您正在寻找的输出:-

select *, datediff(day,servicestartdate,serviceenddate)+1 as numberofdays
into #t
from service

while @@rowcount>0 begin

  update t1 set 
      t1.serviceenddate=t2.serviceenddate,
      t1.numberofdays=datediff(day,t1.servicestartdate,t2.serviceenddate)+1
  from #t t1
  join #t t2 on t2.customerid=t1.customerid
    and t2.servicestartdate=dateadd(day,1,t1.serviceenddate)

end

select 
  customerid,
  max(numberofdays) as maxconsecutivedays
from #t
group by customerid

临时表的更新需要循环进行,因为日期范围(我假设)可以分布在任意数量的记录(1->n)上。有趣的问题。


我已对代码进行了更新,以便临时表以一个额外的列结束,该列包含每条记录的日期范围内的天数。这允许以下操作:-

select x.customerid, x.maxconsecutivedays, max(x.serviceenddate) as serviceenddate
from (
    select t1.customerid, t1.maxconsecutivedays, t2.serviceenddate
    from (
        select 
          customerid,
          max(numberofdays) as maxconsecutivedays
        from #t
        group by customerid
    ) t1
    join #t t2 on t2.customerid=t1.customerid and t2.numberofdays=t1.maxconsecutivedays
) x
group by x.customerid, x.maxconsecutivedays

识别每个客户的最长连续天数(或最新/最长,如果有平局)。这将允许您随后潜入临时表以提取与该块相关的行 - 通过搜索 customeridserviceenddate不是 maxconsecutivedays)。不确定这是否适合您的用例 - 但它可能会有所帮助。

【讨论】:

很好的是有办法标记每条记录 1,2,3 以便我们知道哪些记录相互连接......所以对于 Cust A1 选择了哪些 Claim id 并枚举它们。 连续日期范围块中的索赔记录的serviceenddate 都将是相同的(在#t 中),因此应该可以设计一个select .... over(partition by ...) 来创建您的标签正在描述。当客户有两个或多个连续天数相同的日期范围块时,就会出现问题。您要选择哪个区块? 如果客户的连续天数相同,则以较大者为准【参考方案4】:
WITH chain_builder AS
(
SELECT ROW_NUMBER() OVER(ORDER BY s.customerid, s.CLAIMID) as chain_ID,
  s.customerid,
  s.serv-start-date, s.service-end-date, s.CLAIMID, 1 as chain_count
FROM services s
WHERE s.serv-start-date <> ALL 
  (
  SELECT DATEADD(d, 1, s2.service-end-date)
  FROM services s2
  )
UNION ALL
SELECT chain_ID, s.customerid, s.serv-start-date, s.service-end-date,
  s.CLAIMID, chain_count + 1
  FROM services s
JOIN chain_builder as c
  ON s.customerid = c.customerid AND
  s.serv-start-date = DATEADD(d, 1, c.service-end-date)
),
chains AS
(
SELECT chain_ID, customerid, serv-start-date, service-end-date,
  CLAIMID, chain_count
FROM chain_builder
),
diff AS
(
SELECT c.chain_ID, c.customerid, c.serv-start-date, c.service-end-date,
  c.CLAIMID, c.chain_count,
  datediff(day,c.serv-start-date,c.service-end-date)+1 daysdiff
FROM chains c
),
diff_sum AS
(
SELECT chain_ID, customerid, serv-start-date, service-end-date,
  CLAIMID, chain_count,
  SUM(daysdiff) OVER (PARTITION BY chain_ID) as total_diff
FROM diff
),
diff_comp AS
(
SELECT chain_ID, customerid,
  MAX(total_diff) OVER (PARTITION BY customerid) as total_diff
FROM diff_sum
)
SELECT DISTINCT ds.CLAIMID, ds.customerid, ds.serv-start-date,
  ds.service-end-date, ds.total_diff as total_days, ds.chain_count
FROM diff_sum ds
JOIN diff_comp dc
ON ds.chain_ID = dc.chain_ID AND ds.customerid = dc.customerid
  AND ds.total_diff = dc.total_diff
ORDER BY customerid, chain_count
OPTION (maxrecursion 0)

【讨论】:

以上是关于查找连续的服务天数,中间没有休息日的主要内容,如果未能解决你的问题,请参考以下文章

查找超过指定天数的任何人的最大连续缺勤天数

PostgreSQL:查找到现在为止的连续天数

SQL 来确定不同的连续访问天数?

获取连续登陆天数,连续签到天数 ,方法优化

Codeforces 698A - Vacations - [简单DP]

SQL 来确定访问的最小连续天数?