【问题标题】:SQL - Combine two rows if difference is below threshholdSQL - 如果差异低于阈值,则合并两行
【发布时间】:2021-05-05 05:56:41
【问题描述】:

我在 SQL Server 中有这样的表:

id  start_time  end_time
1   10:00:00    10:34:00
2   10:38:00    10:52:00
3   10:53:00    11:23:00
4   11:24:00    11:56:00
5   14:20:00    14:40:00
6   14:41:00    14:59:00
7   15:30:00    15:40:00

我想要的是一个根据两个连续记录之间的时间差输出合并记录的查询(行 n 的 end_time 和行 n+1 的 start_time ) .时间差小于2分钟的记录合并为一个时间条目,并保留第一条记录的ID。如果多个连续记录的时间差小于 2 分钟,这也应该合并两个以上的记录。

这将是预期的输出:

id  start_time  end_time
1   10:00:00    10:34:00
2   10:38:00    11:56:00
5   14:20:00    14:59:00
7   15:30:00    15:40:00

提前感谢有关如何构建查询的任何提示。

编辑: 我从以下代码开始计算lead_time和时间差,但不知道如何分组和合并。

WITH rows AS
        (
        SELECT  *, ROW_NUMBER() OVER (ORDER BY Id) AS rn
        FROM #temp
        )
SELECT  mc.id, mc.start_time, mc.end_time, mp.start_time lead_time, DATEDIFF(MINUTE, mc.[end_time], mp.[start_time]) as DiffToNewSession
FROM    rows mc
LEFT JOIN    rows mp
ON      mc.rn = mp.rn - 1

【问题讨论】:

  • 你可以看看this answer。它使用Group by,它可以工作。
  • 根据问题指南,请展示您的尝试并告诉我们您发现了什么(在本网站或其他地方)以及为什么它不能满足您的需求。
  • 基于两个连续记录之间的时间差 - 你错过了时间差的定义,因为有两个时间列。
  • @astentx,OP表示一行的“end_time”和下一行的“start_time”之间的差异......这就是他如何获得ID = 2,start_time的行= 10:30, end_time = 11:56 .... ID 为 3 的行在 ID 为 2 的行结束后 1 分钟开始,第 4 行在第 3 行结束后 1 分钟开始,但第 5 行在第 4 行结束后 24 分钟开始,因此(在“预期输出”中)第 5 行得到自己的行的原因

标签: sql sql-server tsql


【解决方案1】:

t-sql中的window函数可以实现很多数据统计,比如

create table #temp(id int identity(1,1), start_time time, end_time time)
insert into #temp(start_time, end_time)
values  ('10:00:00', '10:34:00')
      , ('10:38:00', '10:52:00')
      , ('10:53:00', '11:23:00')
      , ('11:24:00', '11:56:00')
      , ('14:20:00', '14:40:00')
      , ('14:41:00', '14:59:00')
      , ('15:30:00', '15:40:00')

;with c0 as(
select *, LAG(end_time,1,'00:00:00') over (order by id) as lag_time
from #temp
), c1 as(
select *, case when DATEDIFF(MI, lag_time, start_time) <= 2 then 1 else -0 end as gflag
from c0
), c2 as(
select *, SUM(case when gflag=0 then 1 else 0 end) over(order by id) as gid
from c1
)
select MIN(id) as id, MIN(start_time) as start_time, MAX(end_time) as end_time
from c2
group by gid

为了更好的描述数据构建的过程,我简单的用c0,c1,c2...来表示层级,可以合并一些层级进行优化。 如果不能使用 id 作为排序条件,则需要更改上述语句中的排序部分。

【讨论】:

  • 完美运行。不过,对我来说,递归解决方案更容易理解。不过谢谢!
  • 不客气,个人强烈推荐使用“集合”的思想解决SQL问题,递归、循环(游标)这些在程序中使用比较好。
  • @Raffael 。 . .作为 OP,您当然可以选择任何您想要的答案。为了清晰和性能,我投给了这个版本。
  • 经过一些额外的工作并添加了排序标准(除了 ID 之外,我还有一些其他列)我从递归解决方案更改为这个解决方案,因为您可以轻松地逐步按照数据构造进行操作。跨度>
【解决方案2】:

您可以使用recursive cte 来获得您想要的结果。这种方法只是简单地将当前end_time 与下一个start_time 进行比较。如果它小于2 mintues 阈值,则使用与grp_start 相同的start_time。最后,简单地在grp_start 上做一个GROUP BY

with rcte as
(
    -- anchor member
    select  *, grp_start = start_time
    from    tbl
    where   id  = 1

    union all

    -- recursive member
    select  t.id, t.start_time, t.end_time, 
            grp_start = case when datediff(second, r.end_time, t.start_time) <= 120
                             then r.grp_start
                             else t.start_time
                             end
    from    tbl t
            inner join rcte r   on  t.id    = r.id + 1
)
select  id = min(id), grp_start as start_time, max(end_time) as end_time
from    rcte 
group by grp_start

demo

【讨论】:

  • 如果不是,则使用row_number() 生成新序列
  • @Raffael 。 . .注意:如果行没有按时间排序,这将不起作用。您的问题并没有明确说明情况总是如此。
【解决方案3】:

我想这应该可以在没有递归的情况下解决问题。我再次使用了几个 ctes 以使解决方案更易于阅读。估计可以减少一点……

INSERT INTO T1 VALUES
(1,'10:00:00','10:34:00')
,(2,'10:38:00','10:52:00')
,(3,'10:53:00','11:23:00')
,(4,'11:24:00','11:56:00')
,(5,'14:20:00','14:40:00')
,(6,'14:41:00','14:59:00')
,(7,'15:30:00','15:40:00')
GO

WITH cte AS(
SELECT *
      ,ROW_NUMBER() OVER (ORDER BY id) AS rn
      ,DATEDIFF(MINUTE, ISNULL(LAG(endtime) OVER (ORDER BY id), starttime), starttime) AS diffMin
      ,COUNT(*) OVER (PARTITION BY (SELECT 1)) as maxRn
  FROM T1
),
cteFirst AS(
SELECT *
  FROM cte
  WHERE rn = 1 OR diffMin > 2
),
cteGrp AS(
SELECT *
      ,ISNULL(LEAD(rn) OVER (ORDER BY id), maxRn+1) AS nextRn
  FROM cteFirst
)
SELECT f.id, f.starttime, MAX(ISNULL(n.endtime, f.endtime)) AS endtime
  FROM cteGrp f
  LEFT JOIN cte n ON n.rn >= f.rn AND n.rn < f.nextRn
  GROUP BY f.id, f.starttime

【讨论】:

  • LEAD 函数接受 3 个参数:值、偏移量和默认值,当函数超出窗口边界时使用。所以IFNULL(LEAD(expr1) ..., expr2) 可以只用LEAD 重写,这样更具可读性:LEAD(expr1, 1, expr2) ...`
  • 仅当最后两行不应合并时才有效。如果最后一行的时间差低于阈值,则不视为倒数第二行的 end_time,也不单独包含在结果中。
猜你喜欢
  • 2021-05-22
  • 2020-11-23
  • 1970-01-01
  • 2017-12-06
  • 1970-01-01
  • 2018-08-09
  • 2021-11-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多