извлекать данные с минимальной разницей во времени между каждыми 2 выборками - PullRequest
2 голосов
/ 27 октября 2019

У меня есть таблица с отметкой времени сэмплов:

create table #OREN_TEMP(TS datetime)

insert into #OREN_TEMP(TS)
    values 
    ('2019-10-25 06:20:07.000'),
    ('2019-10-25 06:20:15.000'),
    ('2019-10-25 06:20:19.000'),
    ('2019-10-25 06:20:26.000'),
    ('2019-10-25 06:20:26.000'),
    ('2019-10-25 06:20:34.000'),
    ('2019-10-25 06:20:42.000'),
    ('2019-10-25 06:20:51.000'),
    ('2019-10-25 06:20:59.000'),
    ('2019-10-25 06:21:07.000'),
    ('2019-10-25 06:21:15.000'),
    ('2019-10-25 06:21:19.000'),
    ('2019-10-25 06:21:26.000')

    select * from #OREN_TEMP

Меня попросили извлечь данные из таблицы, когда минимальное время между сэмплами составляет 20 секунд. Это означает, что мне нужно извлечь следующее:

('2019-10-25 06:20:07.000')
('2019-10-25 06:20:34.000')
('2019-10-25 06:20:59.000')
('2019-10-25 06:21:19.000')

Я попытался сделать следующее:

declare @MIN_DATE datetime

select top 1 @MIN_DATE =TS from #OREN_TEMP
order by TS
select TS
from (select TS, row_number() over (partition by datediff(second,@MIN_DATE,TS) / 20 order by TS) as RowNum
      from #OREN_TEMP
    ) TMP
where TMP.ROWNUM=1

и получил:

TS
2019-10-25 06:20:07.000
2019-10-25 06:20:34.000
2019-10-25 06:20:51.000 -- Not good - only 17 seconds from previous
2019-10-25 06:21:07.000 -- Not good - only 16 seconds from previous

, но это не такЭто не помогло, поскольку я разделил временные рамки на основе первого TS, а не последнего.

Я хочу избегать циклов и курсора. Внутреннее соединение с самой таблицей в порядке.

Как мне это сделать? Я использую стандартную версию SQL Server 2012.

Ответы [ 2 ]

1 голос
/ 27 октября 2019

Мой ответ похож на @Gordon, но сделан независимо ...

Снова я использовал cte

;with cte as ( 
-- got first row , mark it RowNumber = 1
select top 1 ts, RN = 1 
from #OREN_TEMP

union all
-- in recursive part set number for all rows
select  T.ts, convert(int,row_number() over ( order by T.TS)) 
from #OREN_TEMP T 
-- join only to rows which are after 20+ secs
join cte T1 on datediff(SECOND,T1.TS, T.TS) >= 20
-- and we have to take RowNumber = 1 from cte
and T1.RN = 1
)
-- and result goes.......
select TS from cte
where RN = 1

Результат

ts
-----------------------
2019-10-25 06:20:07.000
2019-10-25 06:20:34.000
2019-10-25 06:20:59.000
2019-10-25 06:21:19.000

РЕДАКТИРОВАТЬ

Для таких стран, как моя (Россия), чтобы запустить ваши вставки, мы должны добавить код с командой

SET DATEFORMAT mdy
1 голос
/ 27 октября 2019

Для этого вы можете использовать рекурсивный CTE. Но это не эффективно в SQL Server. SQL Server имеет ряд ограничений на рекурсивные CTE, включая отсутствие агрегации и рекурсивных ссылок в подзапросах.

Итак, вот одна из версий:

with first_row as (
      select top (1) ot.*
      from oren_temp ot
      order by ts asc
     ),
     cte as (
      select ts, 1 as lev
      from first_row
      union all
      select ot.ts, lev + 1
      from oren_temp ot join
           cte
           on ot.ts >= dateadd(second, 20, cte.ts)
     )
select lev, min(ts)
from cte
group by lev;

и дБ <> fiddle .

Я бы предостерег вас от запуска этого набора данных со слишком большим количеством строк.

EDIT:

На самом деле, существует более эффективный метод,На каждом рекурсивном шаге мы можем сохранить предыдущий первый найденный ts (что является минимумом), используя row_number():

with first_row as (
      select top (1) ot.*
      from oren_temp ot
      order by ts asc
     ),
     cte as (
      select ts, 1 as lev, convert(int, 1) as seqnum
      from first_row
      union all
      select ot.ts, lev + 1, convert(int, row_number() over (partition by lev order by ot.ts)) as seqnum
      from oren_temp ot join
           cte
           on ot.ts >= dateadd(second, 20, cte.ts)
       where seqnum = 1
     )
select lev, ts, seqnum
from cte
where seqnum = 1;

Это должно быть более эффективным для больших данных. И это тоже круто.

...