在NULL和非NULL值之间分区数据

时间:2018-12-13 16:23:40

标签: sql-server tsql gaps-and-islands

我正在轮询资源的当前状态,并将此信息记录到表中。有时资源不可用,因此状态将为NULL。

我正在尝试将数据划分为连续的行块,其中状态为NOT NULL,然后再将行块划分为status为NULL。我想从这些分区中获取更多信息,例如这些块的最早和最新时间​​戳,该块的行数等等。

示例数据可能如下所示

DECLARE @data TABLE
(
 ID INT IDENTITY,
 STATE NVARCHAR(10) NULL,
 TS DATETIME2(0) NOT NULL
);

INSERT INTO @data
(
 STATE,
 TS
)
VALUES
(N'A', DATEADD(SECOND, 0, GETDATE())),
(N'B', DATEADD(SECOND, 1, GETDATE())),
(NULL, DATEADD(SECOND, 2, GETDATE())),
(NULL, DATEADD(SECOND, 3, GETDATE())),
(NULL, DATEADD(SECOND, 4, GETDATE())),
(N'A', DATEADD(SECOND, 5, GETDATE())),
(N'C', DATEADD(SECOND, 6, GETDATE())),
(N'D', DATEADD(SECOND, 7, GETDATE())),
(N'B', DATEADD(SECOND, 8, GETDATE())),
(NULL, DATEADD(SECOND, 9, GETDATE())),
(NULL, DATEADD(SECOND, 10, GETDATE()))

ID  STATE   TS
1   A       2018-12-13 17:01:38
2   B       2018-12-13 17:01:39
3   NULL    2018-12-13 17:01:40
4   NULL    2018-12-13 17:01:41
5   NULL    2018-12-13 17:01:42
6   A       2018-12-13 17:01:43
7   C       2018-12-13 17:01:44
8   D       2018-12-13 17:01:45
9   B       2018-12-13 17:01:46
10  NULL    2018-12-13 17:01:47
11  NULL    2018-12-13 17:01:48

请注意,这是简化的方法,因为时间戳可能不定期(并非总是相差1秒),并且减少为一种资源,因此省略了名称(在实际数据中,某些资源带有“名称”列)

对于我想要得到的数据,这些数据是四个分区,分别由ID(1,2)[一个非空值的块],然后(2,4,5)[NULL值],然后(6)组成,7、8、9)(再次为非空),最后是(10、11)

这些分区的最小TS和计数应为

17:01:38    2    non-NULL
17:01:40    3    NULL
17:01:43    4    non-NULL
17:01:47    2    NULL

我尝试了分组和窗口函数,但是两者都处理所有不同的值。 有人对此有解决方案吗?

SQL-SERVER 2014

1 个答案:

答案 0 :(得分:2)

这是一个空白和孤岛的问题

您可以尝试使用ROW_NUMBER窗口函数来获取间隙编号并按其分组。

SELECT min(TS) ts,count(*) cnt,val
FROM (
SELECT *,
 ROW_NUMBER() OVER(ORDER BY ID) - 
 ROW_NUMBER() OVER(PARTITION BY (CASE WHEN STATE IS NOT NULL THEN 1 ELSE 0 END) ORDER BY ID) grp,
 (CASE WHEN STATE IS NOT NULL THEN 'non-NULL' END) val
FROM @data
) t1
GROUP BY grp,val
ORDER BY min(TS)

sqlfiddle