Как различать журналы, у которых нет идентификатора, чтобы агрегировать их отдельно на SQL? - PullRequest
2 голосов
/ 09 мая 2020

У меня есть (гипотетическое) устройство, которое регистрирует события потребления своих батарей. Он имеет несколько слотов и ежедневно регистрирует процент потребления батареи в каждом слоте. Вот как выглядят данные:

CREATE TABLE batteries
(slot integer, day date, percentage integer);

INSERT INTO batteries
(slot, day, percentage)
VALUES
(0, '2020-05-08', 96),
(0, '2020-05-09', 96),
(0, '2020-05-10', 97),
(0, '2020-05-11', 97),
(0, '2020-05-12', 97),
(0, '2020-05-13', null),
(0, '2020-05-14', 95),
(0, '2020-05-15', 96),
(0, '2020-05-16', null),
(0, '2020-05-17', 1),
(0, '2020-05-18', 2),
(1, '2020-05-08', 10),
(1, '2020-05-09', 10),
(1, '2020-05-10', 10);

Журнал показывает, что в слоте 0 почти полностью разряженная батарея была заменена 13 мая на другую использованную батарею, которая затем была заменена на 16 мая по новой. Батарея в слоте 1 всегда сообщала об использовании 10%.

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

slot  min_date      max_date      percentage  sequence
------------------------------------------------------
0     '2020-05-10'  '2020-05-12'  97          0
0     '2020-05-15'  '2020-05-15'  96          1
0     '2020-05-18'  '2020-05-18'  2           2
1     '2020-05-08'  '2020-05-10'  10          0

Последнее известное значение батареи 0 в слоте 0 было 97, о котором сообщалось с 10 по 12 мая;

Последнее известное значение батареи 1 в слоте 0 было 96, о котором сообщалось только 15 мая;

Последнее известное значение батареи 2 (текущее) в слоте 0 было 2, о чем сообщалось только 18 мая;

Последнее известное значение батареи 0 (текущее) в слоте 1 было 10, о чем сообщалось с 8 мая по 10 мая.

Моя основная проблема заключается в том, как получить минимальную и максимальную дату для каждой батареи, не имея идентификатора батареи. В этом примере, если я группирую по слоту и проценту для получения дат, я получу неправильную минимальную дату на батарее 1 слота 0, потому что раньше в этом слоте была другая батарея с таким же процентом.

Можно ли получить этот результат по запросу SQL без постобработки?

Ответы [ 3 ]

1 голос
/ 09 мая 2020

Если я правильно понимаю данные, вы знаете, что есть новая батарея, когда значение NULL. Если это указание, то вы можете рассчитать последовательность, подсчитав количество NULL значений до каждой строки (с использованием кумулятивной суммы).

У вас есть один дополнительный шаг для получения последнего значения, а затем агрегат:

select slot,
       min(day) filter (where percentage = last_percentage),
       max(day), last_percentage,
       sequence
from (select b.*,
             first_value(percentage) over (partition by slot, sequence order by day desc) as last_percentage
      from (select b.*,
                   count(*) filter (where percentage is null) over (partition by slot order by day) as sequence
            from batteries b
           ) b
       where percentage is not null
     ) b
group by slot, sequence, last_percentage
order by slot, sequence;

Здесь - скрипт db <>.

В Redshift вы просто используете case выражения или логические значения:

select slot,
       min(case when percentage = last_percentage then day end),
       max(day), last_percentage,
       sequence
from (select b.*,
             first_value(percentage) over (partition by slot, sequence order by day desc) as last_percentage
      from (select b.*,
                   sum( (percentage is null)::int ) over (partition by slot order by day) as sequence
            from batteries b
           ) b
       where percentage is not null
     ) b
group by slot, sequence, last_percentage
order by slot, sequence;
1 голос
/ 09 мая 2020

Я поместил несколько logi c, как показано ниже, которые могут (или не могут) помочь вам получить дату min и max. проверьте демо здесь.

with cte as
(
  select
    *,
    dense_rank() over (partition by percentage order by rnk desc) as nrnk
  from
  (
    select
          *,
          (day - '2000-01-01'::date 
               - row_number() over (partition by percentage order by day)) as rnk
      from batteries
      where percentage is not null  
  ) t   
) 

select
  slot,
  min(day) as min_day,
  max(day) as max_day,
  percentage
from cte
where nrnk = 1    
group by
  slot, 
  percentage, 
  nrnk

Вывод:

| slot | min_day    | max_day    | percentage |
| ---- | ---------- | ---------- | ---------- |
| 0    | 2020-05-17 | 2020-05-17 | 1          |
| 0    | 2020-05-18 | 2020-05-18 | 2          |
| 0    | 2020-05-14 | 2020-05-14 | 95         |
| 0    | 2020-05-15 | 2020-05-15 | 96         |
| 0    | 2020-05-10 | 2020-05-12 | 97         |
| 1    | 2020-05-08 | 2020-05-10 | 10         |
0 голосов
/ 09 мая 2020

Это итеративный процесс. Если вы хотите сделать это в SQL, используйте рекурсивный запрос. Для этого сначала пронумеруйте свои строки на слот, чтобы упростить переход от одной строки к следующей

В рекурсивной части:

  1. Сравните процент строк с предыдущий и запомните, так как этот процент является текущим.
  2. Каждый раз, когда вы достигаете нулевого процента, вы получаете новую батарею (увеличивайте количество батарей). строка с наивысшей датой для каждого слота и батареи.
    with recursive numbered as
    (
      select
        slot, day, percentage,
        row_number() over (partition by slot order by day) as rn
      from batteries
    )
    , cte(slot, battery, min_date, max_date, percentage, rn) as
    (
      select slot, 1, day, day, percentage, rn
      from numbered
      where rn = 1
       union all
      select
        n.slot,
        cte.battery + case when n.percentage is null then 1 else 0 end,
        case when n.percentage = cte.percentage then cte.min_date else n.day end,
        n.day,
        n.percentage,
        n.rn
      from cte
      join numbered n on n.slot = cte.slot and n.rn = cte.rn + 1
    )
    select distinct on (slot, battery)
      slot, min_date, max_date, percentage, battery - 1 as sequence
    from cte
    order by slot, battery, max_date desc;
    

    Результат:

    +------+------------+------------+------------+----------+
    | slot |  min_date  |  max_date  | percentage | sequence |
    +------+------------+------------+------------+----------+
    |    0 | 2020-05-10 | 2020-05-12 |         97 |        0 |
    |    0 | 2020-05-15 | 2020-05-15 |         96 |        1 |
    |    0 | 2020-05-18 | 2020-05-18 |          2 |        2 |
    |    1 | 2020-05-08 | 2020-05-10 |         10 |        0 |
    +------+------------+------------+------------+----------+
    

    Демо: https://dbfiddle.uk/?rdbms=postgres_12&fiddle=1ee3278d6f020f2d1a61bc93276965f5

...