在 T-SQL 中查找开始和结束日期(基于集合)
Posted
技术标签:
【中文标题】在 T-SQL 中查找开始和结束日期(基于集合)【英文标题】:Find the start and end date (set based) in T-SQL 【发布时间】:2011-01-10 04:11:26 【问题描述】:我有以下。
Name Date
A 2011-01-01 01:00:00.000
A 2011-02-01 02:00:00.000
A 2011-03-01 03:00:00.000
B 2011-04-01 04:00:00.000
A 2011-05-01 07:00:00.000
想要的输出是
Name StartDate EndDate
-------------------------------------------------------------------
A 2011-01-01 01:00:00.000 2011-04-01 04:00:00.000
B 2011-04-01 04:00:00.000 2011-05-01 07:00:00.000
A 2011-05-01 07:00:00.000 NULL
如何在基于集合的方法中使用 TSQL 实现相同的目标。
DDL 如下
DECLARE @t TABLE(PersonName VARCHAR(32), [Date] DATETIME)
INSERT INTO @t VALUES('A', '2011-01-01 01:00:00')
INSERT INTO @t VALUES('A', '2011-01-02 02:00:00')
INSERT INTO @t VALUES('A', '2011-01-03 03:00:00')
INSERT INTO @t VALUES('B', '2011-01-04 04:00:00')
INSERT INTO @t VALUES('A', '2011-01-05 07:00:00')
Select * from @t
【问题讨论】:
我不明白您如何计算所需的输出。你如何决定结束日期?例如,在所需的输出中,您有一条记录: name start date end date A 2011-01-01 01:00:00.000 2011-04-01 04:00:00.000 但输入的日期为 2011-04-01 04 :00:00.000 与名称 B 关联。我们如何确定记录的结束日期。记录的定义是什么? 一条记录的结束日期是另一条记录的开始日期。所以 A 的开始日期是 2011-01-01 01:00:00.000,但 B 的开始日期是 2011-04-01 04:00:00.000。所以 A 的结束日期是 2011-04-01 04:00:00.000。同样,B 之后的 A 的开始日期为 2011-05-01 07:00:00.000,这是 B 的结束日期。 但是你怎么知道选择哪条记录作为特定记录的结束日期? 在名称中找到的第一个差异。即 A 在开始时出现 3 次,然后 B 出现在第 4 行。所以 1 笔交易结束。在第 5 排,A 又来了。所以从 4 号到 5 号有一个新的交易 不幸的是,没有某种逻辑来确定您如何选择结束日期并将数据合并到输出中,我认为没有人可以帮助编写 SQL 来获取输出你的愿望。从 A(输入)到 B(输出)必须遵循某种逻辑。 【参考方案1】:;WITH cte1
AS (SELECT *,
ROW_NUMBER() OVER (ORDER BY Date) -
ROW_NUMBER() OVER (PARTITION BY PersonName
ORDER BY Date) AS G
FROM @t),
cte2
AS (SELECT PersonName,
MIN([Date]) StartDate,
ROW_NUMBER() OVER (ORDER BY MIN([Date])) AS rn
FROM cte1
GROUP BY PersonName,
G)
SELECT a.PersonName,
a.StartDate,
b.StartDate AS EndDate
FROM cte2 a
LEFT JOIN cte2 b
ON a.rn + 1 = b.rn
因为 CTE 的结果通常不会实现 如果您实现 自己的中间结果如下。
DECLARE @t2 TABLE (
rn INT IDENTITY(1, 1) PRIMARY KEY,
PersonName VARCHAR(32),
StartDate DATETIME );
INSERT INTO @t2
SELECT PersonName,
MIN([Date]) StartDate
FROM (SELECT *,
ROW_NUMBER() OVER (ORDER BY Date) -
ROW_NUMBER() OVER (PARTITION BY PersonName
ORDER BY Date) AS G
FROM @t) t
GROUP BY PersonName,
G
ORDER BY StartDate
SELECT a.PersonName,
a.StartDate,
b.StartDate AS EndDate
FROM @t2 a
LEFT JOIN @t2 b
ON a.rn + 1 = b.rn
【讨论】:
【参考方案2】:SELECT
PersonName,
StartDate = MIN(Date),
EndDate
FROM (
SELECT
PersonName,
Date,
EndDate = (
/* get the earliest date after current date
associated with a different person */
SELECT MIN(t1.Date)
FROM @t AS t1
WHERE t1.Date > t.Date
AND t1.PersonName <> t.PersonName
)
FROM @t AS t
) s
GROUP BY PersonName, EndDate
ORDER BY 2
基本上,对于每个Date
,我们都会在它之后找到与不同PersonName
关联的最近日期。这给了我们EndDate
,它现在可以为我们区分同一个人的连续日期组。
现在我们只需要将数据按PersonName
和EndDate
进行分组,并得到每个组中最小的Date
为StartDate
。是的,当然,按StartDate
对数据进行排序。
【讨论】:
【参考方案3】:获取行号,以便您知道上一条记录在哪里。然后,取一个记录和它之后的下一个记录。当状态改变时,我们有一个候选行。
select
state,
min(start_timestamp),
max(end_timestamp)
from
(
select
first.state,
first.timestamp_ as start_timestamp,
second.timestamp_ as end_timestamp
from
(
select
*, row_number() over (order by timestamp_) as id
from test
) as first
left outer join
(
select
*, row_number() over (order by timestamp_) as id
from test
) as second
on
first.id = second.id - 1
and first.state != second.state
) as agg
group by state
having max(end_timestamp) is not null
union
-- last row wont have a ending row
--(select state, timestamp_, null from test order by timestamp_ desc limit 1)
-- I think it something like this for sql server
(select top state, timestamp_, null from test order by timestamp_ desc)
order by 2
;
已使用 PostgreSQL 测试,但也应与 SQL Server 一起使用
【讨论】:
【参考方案4】:cte 的另一个答案是一个很好的答案。另一种选择是在任何情况下都遍历集合。它不是基于集合的,但它是另一种实现方式。
您将需要迭代到 A. 为与其事务对应的每条记录分配一个唯一的 id,或者 B. 以实际获取您的输出。
TSQL 不适合迭代记录,特别是如果您有很多记录,因此我会推荐一些其他方法,一个小的 .net 程序或更擅长迭代的东西。
【讨论】:
【参考方案5】:有一个非常快速的方法可以使用一些间隙和岛屿理论来做到这一点:
WITH CTE as (SELECT PersonName, [Date]
, Row_Number() over (ORDER BY [Date])
- Row_Number() over (ORDER BY PersonName, [Date]) as Island
FROM @t)
Select PersonName, Min([Date]), Max([Date])
from CTE
GROUP BY Island, PersonName
ORDER BY Min([Date])
【讨论】:
以上是关于在 T-SQL 中查找开始和结束日期(基于集合)的主要内容,如果未能解决你的问题,请参考以下文章