Как найти пик и долину в последовательности выполнения в sql - PullRequest
0 голосов
/ 11 октября 2018

Итак, у меня есть набор данных в Афине, поэтому для целей этого вы, вероятно, можете рассматривать его как базу данных postgres.Образец данных можно увидеть в этой sql fiddle .

Вот пример:

  create table vals (
  timestamp int,
  type varchar(25),
  val int
  );

  insert into vals(timestamp,type, val) 
  values      (10, null, 1),
              (20, null, 2),
              (39, null, 1),
              (40,'p',1),
              (50,'p',2),
              (60,'p',1),
              (70,'v',5),
              (80,'v',6),
              (90,'v',6),
              (100,'v',3),
              (110,null,3),
              (120,'v',6),
              (130,null,3),
              (140,'p',10),
              (150,'p',8),
              (160,null,3),
              (170,'p',1),
              (180,'p',2),
              (190,'p',2),
              (200,'p',1),
              (210,null,3),
              (220,'v',1),
              (230,'v',1),
              (240,'v',3),
              (250,'v',41)               

Я хотел бы получить набор данных, которыйвключает в себя все значения, но выделяет самое высокое значение 'p' и самое низкое значение смежных 'v.

, поэтому в конечном итоге я получу:

   timestamp, type, value, is_peak
    (10, null, 1, null),
    (20, null, 2, null),
    (39, null, 1, null),
    (40,'p',1, null),
    (50,'p',2, 1),
    (60,'p',1, null),
    (70,'v',5, null),
    (80,'v',6, null),
    (90,'v',6, null),
    (100,'v',3, 1),
    (110,null,3, null),
    (120,'v',6, 1),
    (130,null,3, null),
    (140,'p',10, 1),
    (150,'p',8, null),
    (160,null,3, null),
    (170,'p',1, null),
    (180,'p',2, 1),
    (190,'p',2, null), -- either this record or 180 would be fine
    (200,'p',1, null),
    (210,null,3, null),
    (220,'v',1, 1), -- again either this or 230
    (230,'v',1, null),
    (240,'v',3, null),
    (250,'v',41, null) 

- пик имеетмного вариантов для типа, если бы это был какой-то дипарандж или инкрементное число, которое было бы хорошо.Точно так же я могу быть уверен, что в смежном наборе «помеченный» является самым высоким или самым низким значением.

Удачи, оцените ассист

Примечание: пик максимума пиковили мин долины могут быть где угодно в смежном множестве, но как только тип меняется, мы начинаем заново.

Ответы [ 3 ]

0 голосов
/ 13 октября 2018

Вы можете использовать Функции окон LEAD / LAG :

SELECT *,
  CASE WHEN type = 'p' AND val>LAG(val) OVER(PARTITION BY type ORDER BY timestamp)
        AND val > LEAD(val) OVER(PARTITION BY type ORDER BY timestamp) THEN 1 
       WHEN type = 'v' AND val<LAG(val) OVER(PARTITION BY type ORDER BY timestamp)
       AND val < LEAD(val) OVER(PARTITION BY type ORDER BY timestamp) THEN 1 
  END AS is_peak
FROM vals
ORDER BY timestamp;

db <> Fiddle Demo

Вывод:

┌───────────┬───────┬──────┬─────────┐
│ timestamp │ type  │ val  │ is_peak │
├───────────┼───────┼──────┼─────────┤
│       10  │       │   1  │         │
│       20  │       │   2  │         │
│       39  │       │   1  │         │
│       40  │ p     │   1  │         │
│       50  │ p     │   2  │       1 │
│       60  │ p     │   1  │         │
│       70  │ v     │   5  │         │
│       80  │ v     │   6  │         │
│       90  │ v     │   6  │         │
│      100  │ v     │   3  │       1 │
│      110  │       │   3  │         │
│      120  │ v     │   6  │         │
│      130  │       │   3  │         │
│      140  │ p     │  10  │       1 │
│      150  │ p     │   8  │         │
└───────────┴───────┴──────┴─────────┘

Версия с предложением окна:

SELECT *, CASE WHEN type = 'p' AND val > LAG(val) OVER s
                AND val > LEAD(val) OVER s THEN 1 
               WHEN type = 'v' AND val < LAG(val) OVER s
                AND val < LEAD(val) OVER s THEN 1 
          END AS is_peak
FROM vals
WINDOW s AS (PARTITION BY type ORDER BY timestamp)
ORDER BY timestamp;

db <> fiddle demo2

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

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

SELECT *,CASE
  WHEN type IN ('p','v') AND val > LAG(val,1,0) OVER(PARTITION BY type ORDER BY timestamp)
  AND val > LEAD(val,1,0) OVER(PARTITION BY type ORDER BY timestamp) THEN 1 
  WHEN type IN ('v') AND val < LAG(val,1,0) OVER(PARTITION BY type ORDER BY timestamp)
  AND val < LEAD(val,1,0) OVER(PARTITION BY type ORDER BY timestamp) THEN 1 
 END AS is_peak
FROM vals
ORDER BY timestamp;

db <> fiddle demo3


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

Окончательное решение с gaps-and-islands обнаружение (плато обработки):

WITH cte AS (
  SELECT *, LEAD(val,1,0) OVER(PARTITION BY type ORDER BY timestamp) AS l
  FROM vals
), cte2 AS (
  SELECT *, SUM(CASE WHEN val = l THEN 1 ELSE 0 END) OVER(PARTITION BY type ORDER BY timestamp) AS dr
  FROM cte
), cte3 AS (
  SELECT *, CASE WHEN type IN ('p') AND val > LAG(val,1) OVER(PARTITION BY type ORDER BY timestamp)
                AND val >= LEAD(val,1) OVER(PARTITION BY type ORDER BY timestamp) THEN 1 
               WHEN type IN ('v') AND val < LAG(val,1) OVER(PARTITION BY type ORDER BY timestamp)
                AND val <= LEAD(val,1) OVER(PARTITION BY type ORDER BY timestamp) THEN 1 
          END AS is_peak
  FROM cte2
)
SELECT timestamp, type, val,
     CASE WHEN is_peak = 1 THEN 1 
          WHEN EXISTS (SELECT 1 FROM cte3 cx
                       WHERE cx.is_peak = 1
                         AND cx.val = cte3.val
                         AND cx.type = cte3.type
                         AND cx.dr = cte3.dr)
              THEN 1
     END is_peak
FROM cte3
ORDER BY timestamp;

db <> финал демонстрации скрипки

Выход:

┌────────────┬───────┬──────┬─────────┐
│ timestamp  │ type  │ val  │ is_peak │
├────────────┼───────┼──────┼─────────┤
│        10  │       │   1  │         │
│        20  │       │   2  │         │
│        39  │       │   1  │         │
│        40  │ p     │   1  │         │
│        50  │ p     │   2  │       1 │
│        60  │ p     │   1  │         │
│        70  │ v     │   5  │         │
│        80  │ v     │   6  │         │
│        90  │ v     │   6  │         │
│       100  │ v     │   3  │       1 │
│       110  │       │   3  │         │
│       120  │ v     │   6  │         │
│       130  │       │   3  │         │
│       140  │ p     │  10  │       1 │
│       150  │ p     │   8  │         │
│       160  │       │   3  │         │
│       170  │ p     │   1  │         │
│       180  │ p     │   2  │       1 │
│       190  │ p     │   2  │       1 │
│       200  │ p     │   1  │         │
│       210  │       │   3  │         │
│       220  │ v     │   1  │       1 │
│       230  │ v     │   1  │       1 │
│       240  │ v     │   3  │         │
│       250  │ v     │  41  │         │
└────────────┴───────┴──────┴─────────┘

Дополнительное примечание:

ISO SQL: 2016 добавляет сопоставление с шаблоном MATCH_RECOGNIZE для этого вида сценариев, где вы определяете регулярное выражение для пика, например PATTERN (STRT UP+ FLAT* DOWN+), но в настоящее время оно поддерживается толькоOracle.

Статья по теме: Современный SQL - match_recognize регулярных выражений по строкам

0 голосов
/ 13 октября 2018

Есть небольшая хитрость, которую можно использовать для такой проблемы с пропусками и островками, как эта.

Вычитая row_number из row_number над значением, вы получаете некоторый рейтинг.

У этого метода есть определенные недостатки для определенных целей.
Но он работает для этого случая.

После того, как это ранжирование вычислено, оно может использоваться другими оконными функциями во внешнем запросе.
И мы снова можем использовать row_number для этого.Но в зависимости от требования вы можете использовать вместо этого DENSE_RANK или оконную функцию MIN & MAX.

Затем мы просто оборачиваем их в CASE для различной логики в зависимости от типа.

select timestamp, type, val, 
(case 
 when type = 'v' and row_number() over (partition by (rn1-rn2), type order by val, rn1) = 1 then 1
 when type = 'p' and row_number() over (partition by (rn1-rn2), type order by val desc, rn1) = 1 then 1
 end) is_peak
-- , rn1, rn2, (rn1-rn2) as rnk
from
(
  select timestamp, type, val,
   row_number() over (order by timestamp) as rn1,
   row_number() over (partition by type order by timestamp) as rn2
  from vals
) q
order by timestamp;

Вы можете проверить скрипту SQL здесь

Возвращает:

timestamp   type    val     is_peak
---------   ----    ----    -------
10          null    1       null
20          null    2       null
39          null    1       null
40          p       1       null
50          p       2       1
60          p       1       null
70          v       5       null
80          v       6       null
90          v       6       null
100         v       3       1
110         null    3       null
120         v       6       1
130         null    3       null
140         p       10      1
150         p       8       null
160         null    3       null
170         p       1       null
180         p       2       1
190         p       2       null
200         p       1       null
210         null    3       null
220         v       1       1
230         v       1       null
240         v       3       null
250         v       41      null
0 голосов
/ 11 октября 2018

Для достижения этого можно использовать подзапросы в выражении case:

create table #vals 
(
    [timestamp] int,
    [type] varchar(25),
    val int
);

insert into #vals ([timestamp], [type], val) 
values  (10, null, 1),
        (20, null, 2),
        (30, null, 1),
        (40,'p',1),
        (50,'p',2),
        (60,'p',1),
        (70,'v',5),
        (80,'v',6),
        (90,'v',6),
        (100,'v',3),
        (110,null,3)

select 
    r.*,
    case 
        when r.[type] = 'p' and not exists (select * from #vals c where c.[type] = r.[type] and c.val > r.val) then 1
        when r.[type] = 'v' and not exists (select * from #vals c where c.[type] = r.[type] and c.val < r.val) then 1
        else null
    end as is_peak
from #vals r

drop table #vals

Результаты:

/----------------------------------\
| timestamp | type | val | is_peak |
|-----------|------|-----|---------|
| 10        | NULL | 1   | NULL    |
| 20        | NULL | 2   | NULL    |
| 30        | NULL | 1   | NULL    |
| 40        | p    | 1   | NULL    |
| 50        | p    | 2   | 1       |
| 60        | p    | 1   | NULL    |
| 70        | v    | 5   | NULL    |
| 80        | v    | 6   | NULL    |
| 90        | v    | 6   | NULL    |
| 100       | v    | 3   | 1       |
| 110       | NULL | 3   | NULL    |
\----------------------------------/

Примечание. Если существует несколько записей с одинаковым значением (пик)val, каждый из них будет помечен 1 в столбце is_peak.

...