【问题标题】:find start and end dates over a non-contiguous range在非连续范围内查找开始日期和结束日期
【发布时间】:2026-01-18 00:20:04
【问题描述】:

我需要找到定义为范围的开始和结束日期:开始日期是第一个日期,结束日期是第一个日期,后续日期是结束日期后两个月或更长时间。可以有多个可能的范围

我有一个类似的表结构:

ID        int  identity(1,1),
fk_ID     char(9),
dateField datetime

数据如下:

1     a     2012-01-01
2     a     2012-01-05
3     a     2012-01-12
4     b     2012-02-01
5     a     2012-04-01
6     b     2012-05-01
7     a     2012-05-30

预期的输出如下所示:

fk_id    startdate    enddate
a        2012-01-01   2012-01-12
a        2012-04-01   2012-05-30
b        2012-02-01   2012-02-01
b        2012-05-01   null

编辑: 通过执行以下操作:

CREATE TABLE #temp
(
    autonum     int     identity(1,1),
    id          char(9),
    sd          datetime
)

insert into #temp (id, sd) values ('a', '2012-01-01')
insert into #temp (id, sd) values ('a', '2012-01-05')
insert into #temp (id, sd) values ('a', '2012-01-12')
insert into #temp (id, sd) values ('a', '2012-03-01')
insert into #temp (id, sd) values ('a', '2012-04-03')
insert into #temp (id, sd) values ('a', '2012-06-06')
insert into #temp (id, sd) values ('b', '2012-02-12')
insert into #temp (id, sd) values ('b', '2012-02-15')
insert into #temp (id, sd) values ('b', '2012-03-01')
insert into #temp (id, sd) values ('b', '2012-04-03')
insert into #temp (id, sd) values ('b', '2012-06-01')

select t1.id, null as previousend, min(t1.sd) as nextstart
from #temp t1
group by t1.id
union
select t1.id, t1.sd as enddate, (select min(t2.sd) from #temp t2 where t1.id=t2.id and    t2.sd>t1.sd) as nextstart
from #temp t1
where (select min(t2.sd) from #temp t2 where t1.id=t2.id and t2.sd>t1.sd) >= dateadd(month, 2, t1.sd)
union
select t1.id, max(t1.sd), null
from #temp t1
group by t1.id

drop table #temp

我可以得到这样的输出:

id        previousend             nextstart
--------- ----------------------- -----------------------
a         NULL                    2012-01-01 00:00:00.000
a         2012-04-03 00:00:00.000 2012-06-06 00:00:00.000
a         2012-06-06 00:00:00.000 NULL
b         NULL                    2012-02-12 00:00:00.000
b         2012-06-01 00:00:00.000 NULL

这非常接近,但理想情况下,该范围的开始日期和结束日期将在行上。

【问题讨论】:

  • 我想你的意思是...结束日期是第一个日期,随后的日期在开始日期之后两个月或更长时间对吧?
  • @alfonso 不幸的是,描述是准确的,虽然有点令人困惑。数据示例有望阐明情况。
  • 您已经在第二组不同的样本数据中展示了“非常接近”的内容。在这种情况下,你能否展示你真正想要的结果,而不是用另一个令人困惑的单词问题模糊地描述它?
  • 在您的第一个示例中,您似乎让 b 的 enddate 为空,因为该 ID 在第二个范围内只有一个日期。但是在第二个示例中,您没有显示您想要的实际结果,但是从您现在得到的外观来看,您似乎不希望 a 的最后一个范围的结束日期为 null,它只有只有日期。这变得越来越令人困惑,并且很难通过单个查询来解决这两个示例。能否请您提交一个新问题,其中包含一个单一的、有凝聚力的问题陈述和一组样本数据和期望的结果?
  • 您的样本数据应该涵盖所有边缘情况,这样人们就不会浪费时间来制定有效的解决方案,直到您在其中添加额外的一行...

标签: sql tsql sql-server-2005


【解决方案1】:

考虑到问题的所有变化,这是我的最佳猜测。我仍然觉得这个问题非常混乱、分裂,而且这两种情况的预期结果似乎不匹配。使用此查询:

;WITH x AS 
(
  SELECT a.id, sd = a.sd, ed = b.sd, rn1 = ROW_NUMBER() OVER 
    (PARTITION BY a.id, a.sd ORDER BY a.sd)
  FROM #temp AS a
  LEFT OUTER JOIN #temp AS b
  ON a.id = b.id
  AND b.sd >= a.sd
  AND b.sd <= DATEADD(MONTH, 2, a.sd)
),
y AS 
(SELECT id, sd, 
  ed = (SELECT MAX(ed) FROM x AS x2 
    WHERE x.id = x2.id AND x2.sd <= DATEADD(MONTH, 2, x.sd)
  )
FROM x
WHERE rn1 = 1
),
z AS 
(
  SELECT id, sd = MIN(sd), ed
  FROM y GROUP BY id, ed
)
SELECT id, sd, ed /* = CASE 
  WHEN ed > sd OR (sd = ed AND NOT EXISTS 
  (SELECT 1 FROM z AS z2 WHERE z2.id = z.id AND z.sd > z2.sd)) THEN ed END 
*/
FROM z
ORDER BY id, sd;

您的第一组数据的结果:

INSERT #temp (id, sd) VALUES
('a','2012-01-01'),
('a','2012-01-05'),
('a','2012-01-12'),
('b','2012-02-01'),
('a','2012-04-01'),
('b','2012-05-01'),
('a','2012-05-30');

如下:

id  sd          ed
a   2012-01-01  2012-01-12
a   2012-04-01  2012-05-30
b   2012-02-01  2012-02-01
b   2012-05-01  2012-05-01

对于第二组:

insert into #temp (id, sd) values ('a', '2012-01-01')
insert into #temp (id, sd) values ('a', '2012-01-05')
insert into #temp (id, sd) values ('a', '2012-01-12')
insert into #temp (id, sd) values ('a', '2012-03-01')
insert into #temp (id, sd) values ('a', '2012-04-03')
insert into #temp (id, sd) values ('a', '2012-06-06')
insert into #temp (id, sd) values ('b', '2012-02-12')
insert into #temp (id, sd) values ('b', '2012-02-15')
insert into #temp (id, sd) values ('b', '2012-03-01')
insert into #temp (id, sd) values ('b', '2012-04-03')
insert into #temp (id, sd) values ('b', '2012-06-01')

如下:

id  sd          ed
a   2012-01-01  2012-04-03
a   2012-06-06  2012-06-06
b   2012-02-12  2012-06-01

如果取消注释 CASE 块,您将在开始日期和结束日期相同的情况下获得结束日期的 NULL。正如我多次建议的那样,你的问题是分裂的,你想要的结果似乎不匹配,所以我不确定正确的答案是什么。

【讨论】:

  • 这完全符合我的需要。你能推荐一个关于 CTE 的资源来帮助我理解这里发生了什么吗?再次感谢
  • CTE 只不过是一个派生表,但语法允许您在后续查询中多次引用它。我想说herehere 是好的开始。
  • 我选择这个答案还为时过早。我添加了 1 行:ID=7 FK_ID=a dateField='2012-02-01'。添加另一个月份的日期会抛出所有输出。
  • 当然,您似乎也将实际问题更改为包含previousend 并添加了其他变量。很难击中移动的目标。 :-(
  • 上一个结束和下一个开始是我能够获得该范围的开始和结束日期的唯一方法。
【解决方案2】:

Fiddle 上的第二次尝试远非优雅,但除了最终记录在结束日期不为 NULL 之外似乎可以工作:

CREATE TABLE temp
(
    id          char(9),
    d          datetime
);

insert into temp (id, d) values ('a', '2012-01-01');
insert into temp (id, d) values ('a', '2012-01-05');
insert into temp (id, d) values ('a', '2012-01-12');
insert into temp (id, d) values ('a', '2012-04-01');
insert into temp (id, d) values ('a', '2012-05-30');
insert into temp (id, d) values ('b', '2012-02-01');
insert into temp (id, d) values ('b', '2012-05-01');


SELECT 
   x.id ,
   min(x.sd) sd ,
   x.ed
FROM
  (SELECT 
      a.id ,
      a.sd ,
      max(a.ed) ed
   FROM
      ( 
      SELECT 
          j.id ,
          j.d sd ,
          q.D ed
       FROM temp j
          JOIN temp q 
             ON 
             j.id = q.id
             AND j.d <= q.d
       GROUP BY j.id ,
           j.d ,
           q.d 
      ) a
   WHERE datediff(m,a.sd,a.ed)<=2
   GROUP BY a.id ,
        a.sd
         )x
GROUP BY x.id ,
         x.ed
ORDER BY x.id ,
         min(x.sd) ,
         x.ed

【讨论】:

  • 示例小提琴第一个结果显示开始日期 2012 年 1 月 1 日,结束日期 2012 年 9 月 1 日。它应该是开始日期 2012 年 1 月 1 日,结束日期 2012 年 1 月 2 日。开始日期 2012 年 9 月 1 日,结束日期 null .
  • 是的 - 很抱歉我误读了这个问题;不知道怎么回答这个问题,期待看到最终答案
  • 继续摆弄,但这个问题真的把我的周日下午搞砸了:sqlfiddle.com/#!3/b3f67/15
  • 我已经用一个新的小提琴链接重新设计了答案:sqlfiddle.com/#!3/ec93c/2
最近更新 更多