使用 table 作为队列在 SQL Server 2017 中查找以前的条件值

Lookup previous values on condition in SQL Server 2017 using a table as a queue

在 table 上查找一行时称为原因:

machine_id  reason   start_time
001234      moving   10:00:00
001234      parked   10:10:00
001234      moving   10:15:00
001234      NULL     10:20:00
001234      NULL     10:25:00
001234      dumping  10:30:00

009876      parked   10:00:00
009876      NULL     10:10:00
009876      NULL     10:15:00
009876      moving   10:20:00
009876      dumping  10:25:00

无论出于何种原因,我都需要获取非 NULL 的最新值,因此 001234 NULL 值都将变为 'moving',而 009876 NULL 值将变为 'parked'.

我通常会通过交叉应用来解决这个问题,例如:

SELECT 
    r1.machine_id    
    ,ISNULL(r1.reason,r2.reason) AS reason
    ,r1.start_time
FROM #reason r1
CROSS APPLY(
    SELECT TOP 1
            r2.reason
    FROM #reason r2
    WHERE r2.machine_oid = r1.machine_oid 
    AND r2.start_time < r1.start_time
    AND r2.reason IS NOT NULL
    ORDER BY start_time DESC
) r2

但是我查询的这个table是几十万行(无法修改源数据库)而且查询的复杂度似乎接近n^2。

在 C++ 中,我会通过使用优先级队列并丢弃对象列表中的项目来检查不符合条件的项目来解决,因此复杂度接近 nlogn。

我尝试在此处了解 post 关于使用 table 作为队列的内容:http://rusanu.com/2010/03/26/using-tables-as-queues 但它超出了我的技能水平。

因为这是对我的数据集的一个非常普遍的要求,所以我希望有一个可以应用的优雅解决方案?

你可以这样做: 它会给你每个 machine_id

的 NULL 值
select a.* from reason a
inner join reason b on a.machine_id = b.machine_id 
and a.reason is not null and b.reason is null and a.start_time < b.start_time
where 
not exists(select 1 from reason c where a.machine_id = c.machine_id 
and a.start_time < c.start_time and c.start_time < b.start_time)

像这样:

DECLARE @DataSource TABLE
(
    [machine_id] VARCHAR(6)
   ,[reason] VARCHAR(12)
   ,[start_time] TIME
);

INSERT INTO @DataSource([machine_id], [reason], [start_time])
VALUES ('001234', 'moving', '10:00:00')
      ,('001234', 'parked', '10:10:00')
      ,('001234', 'moving', '10:15:00')
      ,('001234', NULL, '10:20:00')
      ,('001234', NULL, '10:25:00')
      ,('001234', 'dumping', '10:30:00')
      ,('009876', 'parked', '10:00:00')
      ,('009876', NULL, '10:10:00')
      ,('009876', NULL, '10:15:00')
      ,('009876', 'moving', '10:20:00')
      ,('009876', 'dumping',  '10:25:00');


SELECT [machine_id]
      ,[reason] AS [reason_old]
      ,ISNULL([reason], MAX([Reason]) OVER (PARTITION BY [machine_id], [RowID])) AS [reason]
      ,[start_time]
FROM 
(
    SELECT *
          ,SUM(IIF([reason] IS NULL, 0, 1)) OVER (PARTITION BY [machine_id] ORDER BY [start_time] ASC) AS [RowID]
    FROM @DataSource 
) DS
ORDER BY [machine_id]
        ,[start_time];

想法是使用 SUM 将具有 NULL 值的记录与第一条具有 NOT NULL 值的记录分组。

SELECT *
      ,SUM(IIF([reason] IS NULL, 0, 1)) OVER (PARTITION BY [machine_id] ORDER BY [start_time] ASC) AS [RowID]
FROM @DataSource;

然后,我们可以简单地获取此类组的 MAX/MIN 值,因为这些聚合忽略 NULLs 并将 return NOT NULL 值。