Поиск предыдущих значений при условии в SQL Server 2017 с использованием таблицы в качестве очереди - PullRequest
1 голос
/ 02 марта 2020

При поиске строки в таблице с именем 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 мне нужно получить самое последнее значение, которое не было 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, но это было за пределами моего понимания. уровень квалификации.

Поскольку это очень распространенное требование для моих наборов данных, я надеялся, что есть элегантное решение, которое можно применить?

Ответы [ 2 ]

2 голосов
/ 02 марта 2020

Примерно так:

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];

enter image description here

Идея состоит в том, чтобы использовать 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;

enter image description here

Затем мы можем просто получить значение MAX/MIN для такой группы, как эти агрегаты игнорировать NULL с и вернет значение NOT NULL.

0 голосов
/ 02 марта 2020

Вы можете сделать это следующим образом: он даст вам значения для NULL для каждого machine_id

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)
...