在 NULL 和非 NULL 值之间对数据进行分区

Partitioning data between NULL and non-NULL values

我正在轮询资源的当前状态并将此信息记录到 table。有时资源不可用,所以状态会是NULL。

我正在尝试将数据划分为连续的行块,其中状态为 NOT NULL,后跟状态为 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) [空值],然后(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-服务器 2014

这是一个缺口和孤岛问题

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

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