【问题标题】:Lookup previous values on condition in SQL Server 2017 using a table as a queue使用表作为队列在 SQL Server 2017 中查找以前的条件值
【发布时间】:2020-03-02 06:09:01
【问题描述】:

在名为 reason 的表中查找一行时:

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 值都将变为“移动”,而 009876 NULL 值将都变为“停放”。

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

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

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

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

我尝试在此处理解有关使用表作为队列的帖子:http://rusanu.com/2010/03/26/using-tables-as-queues,但这超出了我的技能水平。

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

【问题讨论】:

  • 在发布此内容后,我有了一个想法,即通过加入日期 ID 并将比较表分解成更小的块,并且只回顾前一天。这提高了更接近 nlogn 的性能,但使用优先级队列仍然会好很多。

标签: sql sql-server tsql sql-server-2017


【解决方案1】:

类似这样的:

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 并将返回 NOT NULL 值。

【讨论】:

  • 这将 300,000 行的性能从 15 分钟提高到 6 秒!复杂度似乎从 n^2 变为非常接近 nlogn。非常感谢,我将在我的数据集上使用这种技术数百次。
【解决方案2】:

你可以这样做: 它将为您提供每个 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)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-08-16
    • 1970-01-01
    • 1970-01-01
    • 2015-08-04
    • 1970-01-01
    • 2018-09-23
    • 2017-12-31
    • 1970-01-01
    相关资源
    最近更新 更多