Как получить средние значения для временных интервалов в Postgres - PullRequest
0 голосов
/ 09 мая 2018

Я использую PostgreSQL 9.6. У меня есть такая таблица:

mac   sn         loc   time     date      vin1    vin2    vin3
1a34 4as11111111 aaaa  7:06:18  1/1/2018  447.42  472.32  682.59
1a34 4as11111111 aaaa  7:06:43  1/1/2018  455.97  476.25  682.59
1a34 4as11111111 aaaa  7:07:35  1/1/2018  470.88  484.2   682.5

Мне нужно вычислить среднее значение vin1, vin2, vin3 за промежутки времени 300 секунд (5 минут). Например, начиная с первого раза (7:06:18 - 7:11:18), для дат в диапазоне. Я могу выбрать нужные мне данные с помощью этого запроса:

select * from table
where sn='4as11111111' and date between '2018-01-01' and '2018-01-02';

Но я не знаю, как сгруппировать его по временным интервалам в 300 секунд и вычислить среднее для столбцов vin1, vin2, vin3 для этих 5-минутных интервалов, чтобы получить что-то вроде этого:

mac  sn          loc     time     date      vin1_av  vin2_av  vin3_av
1a34 4as11111111 aaaa   7:06:18  1/1/2018  450.0    480.32   600.59
1a34 4as11111111 aaaa   7:11:18  1/1/2018  460.0    490.25   782.59
1a34 4as11111111 aaaa   7:16:18  1/1/2018  470.88   500.2    600.5

Любая помощь будет принята с благодарностью.

1 Ответ

0 голосов
/ 10 мая 2018

БД дизайн

Несмотря на то, что вы можете работать с отдельными столбцами date и time, на самом деле нет никакого преимущества перед одним столбцом timestamp. Я бы адаптировался:

ALTER TABLE tbl ADD column ts timestamp;
UPDATE tbl SET ts = date + time;  -- assuming actual date and time types
ALTER TABLE tbl DROP column date, DROP column time;

Если дата и время не являются действительными типами данных date и time, используйте to_timestamp(). Связанный:

Запрос

Тогда запрос немного проще:

SELECT *
FROM  (
   SELECT sn, generate_series(min(ts), max(ts), interval '5 min') AS ts
   FROM   tbl
   WHERE  sn = '4as11111111'
   AND    ts >= '2018-01-01'
   AND    ts <  '2018-01-02'
   GROUP  BY 1
   ) grid
CROSS  JOIN LATERAL (
   SELECT round(avg(vin1), 2) AS vin1_av
        , round(avg(vin2), 2) AS vin2_av
        , round(avg(vin3), 2) AS vin3_av
   FROM   tbl
   WHERE  sn =  grid.sn
   AND    ts >= grid.ts
   AND    ts <  grid.ts + interval '5 min'
   ) avg;

дБ <> скрипка здесь

Генерирует сетку времени начала в первом подзапросе grid, начиная с первой до последней квалифицирующей строки в заданном временном интервале.

Присоединение к строкам, попадающим в каждый раздел, с помощью LATERAL объединения и немедленное агрегирование средних значений в подзапросе avg. Из-за агрегатов он всегда возвращает строку, даже если записи не найдены. Среднее значение по умолчанию NULL в этом случае.

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

Индекс

По крайней мере, иметь этот индекс из нескольких столбцов:

CRATE INDEX foo_idx ON tbl (sn, ts);

Или на (sn, ts, vin1, vin2, vin3), чтобы разрешить сканирование только по индексу - если выполнены некоторые предварительные условия, особенно если строки таблицы намного шире, чем в демонстрационной версии.

Близко связаны:

На основании вашей исходной таблицы

В соответствии с просьбой и разъяснением в комментарии , а затем снова обновляется в вопросе, чтобы включить столбцы mac и loc. Я предполагаю, что вы хотите отдельные средние значения для (mac, loc).

date и time по-прежнему являются отдельными столбцами, столбцы vin * имеют тип float и исключают временные интервалы без строк:

Обновленный запрос также перемещает функцию возврата набора generate_series() в список FROM, который является более чистым, чем Postgres 10:

SELECT t.mac, sn.sn, t.loc, ts.ts::time AS time, ts.ts::date AS date
     , t.vin1_av, t.vin2_av, t.vin3_av
FROM  (SELECT text '4as11111111') sn(sn)  -- provide sn here once
CROSS  JOIN LATERAL (
   SELECT min(date+time) AS min_ts, max(date+time) AS max_ts
   FROM   tbl
   WHERE  sn = sn.sn
   AND    date+time >= '2018-01-01 0:0'   -- provide time frame here
   AND    date+time <  '2018-01-02 0:0'
   ) grid
CROSS  JOIN LATERAL generate_series(min_ts, max_ts, interval '5 min') ts(ts)
CROSS  JOIN LATERAL (
   SELECT mac, loc
        , round(avg(vin1)::numeric, 2) AS vin1_av  -- cast to numeric for round()
        , round(avg(vin2)::numeric, 2) AS vin2_av  -- but rounding is optional
        , round(avg(vin3)::numeric, 2) AS vin3_av
   FROM   tbl
   WHERE  sn = sn.sn
   AND    date+time >= ts.ts
   AND    date+time <  ts.ts + interval '5 min'
   GROUP  BY mac, loc
   HAVING count(*) > 0  -- exclude empty slots
   ) t;

Создайте индекс выражения из нескольких столбцов для поддержки этого:

CRATE INDEX bar_idx ON tbl (sn, (date+time));

дБ <> скрипка здесь

Но я бы предпочел все время использовать timestamp.

...