2016-11-16 1 views
2

Inspiriert von diesem great answer schrieb ich die folgende Abfrage, die die AVG berechnet nach 5-Minuten-Intervallen für das letzte Jahr zurückgibt.wie man nicht gruppierte Daten einfügt

Was Ich mag würde haben, ist alle 5-Minuten-Intervallen und im Fall, auf null, wenn keine Zeilen in einem bestimmten Zeitraum passen.

with intervals as (select 
        (select min("timestamp") from public.hst_energy_d) + n AS start_timestamp, 
        (select min("timestamp") from public.hst_energy_d) + n + 299 AS end_timestamp 
        from generate_series(extract(epoch from now())::BIGINT - 10596096000, extract(epoch from now())::BIGINT, 300) n) 
(SELECT AVG(meas."Al1") as "avg", islots.start_timestamp AS "timestamp" 
FROM public.hst_energy_d meas 
    RIGHT OUTER JOIN intervals islots 
    on meas.timestamp >= islots.start_timestamp and meas.timestamp <= islots.end_timestamp 
WHERE 
    meas.idinstrum = 4 
    AND 
    meas.id_device = 122 
    AND 
    meas.timestamp > extract(epoch from now()) - 10596096000 
GROUP BY islots.start_timestamp, islots.end_timestamp 
ORDER BY timestamp); 

Antwort

1

Ich glaube, ich sehe, was Sie zu tun versuchen, und ich frage mich, ob interval '5 minutes' mit would't zügig besser und einfachen Ansatz folgen:

with times as ( -- find the first date in the dataset, up to today 
    select 
    date_trunc ('minutes', min("timestamp")) - 
    mod (extract ('minutes' from min("timestamp"))::int, 5) * interval '1 minute' as bt, 
    date_trunc ('minutes', current_timestamp) - 
    mod (extract ('minutes' from current_timestamp)::int, 5) * interval '1 minute' as et 
    from hst_energy_d 
    where 
    idinstrum = 4 and 
    id_device = 122 
), -- generate every possible range between these dates 
ranges as (
    select 
    generate_series(bt, et, interval '5 minutes') as range_start 
    from times 
), -- normalize your data to which 5-minut interval it belongs to 
rounded_hst as (
    select 
    date_trunc ('minutes', "timestamp") - 
    mod (extract ('minutes' from "timestamp")::int, 5) * interval '1 minute' as round_time, 
    * 
    from hst_energy_d 
    where 
    idinstrum = 4 and 
    id_device = 122 
) 
select 
    r.range_start, r.range_start + interval '5 minutes' as range_end, 
    avg (hd."Al1") 
from 
    ranges r 
    left join rounded_hst hd on 
    r.range_start = hd.round_time 
group by 
    r.range_start 
order by 
    r.range_start 

By the way, das anspruchsvolle Auge kann frage mich warum mit dem CTE rounded_hst stören und warum nicht einfach ein "between" in der Join verwenden. Nach allem, was ich getestet und beobachtet habe, explodiert die Datenbank alle Möglichkeiten und testet dann die Zwischenbedingung in einer where-Klausel - einem gefilterten Kartesischen. Für diese vielen Intervalle ist das garantiert ein Mörder.

Das Abschneiden jeder Daten auf die nächsten fünf Minuten ermöglicht eine Standard-SQL-Verknüpfung. Ich ermutige Sie beide zu testen, und ich denke, Sie werden sehen, was ich meine.

- EDIT 2016.11.17 -

Lösung von OP, die die Zeiten sind Zahlen berücksichtigt, nicht die Daten:

with times as ( -- find the first date in the dataset, up to today 
    select 
     date_trunc('minutes', to_timestamp(min("timestamp"))::timestamp) - 
     mod(extract ('minutes' from to_timestamp(min("timestamp"))::timestamp)::int, 5) * interval '1 minute' as bt, 
     date_trunc('minutes', current_timestamp::timestamp) - 
     mod(extract ('minutes' from (current_timestamp)::timestamp)::int, 5) * interval '1 minute' as et 
    from hst_energy_d 
    where 
     idinstrum = 4 and 
     id_device = 122 
), -- generate every possible range between these dates 
    ranges as (
     select 
     generate_series(bt, et, interval '5 minutes') as range_start 
     from times 
), -- normalize your data to which 5-minute interval it belongs to 
    rounded_hst as (
     select 
     date_trunc ('minutes', to_timestamp("timestamp")::timestamp)::timestamp - 
     mod (extract ('minutes' from (to_timestamp("timestamp")::timestamp))::int, 5) * interval '1 minute' as round_time, 
     * 
     from hst_energy_d 
     where 
     idinstrum = 4 and 
     id_device = 122 
) 
select 
    extract('epoch' from r.range_start)::bigint, extract('epoch' from r.range_start + interval '5 minutes')::bigint as range_end, 
    avg (hd."Al1") 
from 
    ranges r 
    left join rounded_hst hd on 
          r.range_start = hd.round_time 
group by 
    r.range_start 
order by 
    r.range_start; 
+0

was für eine saubere Abfrage! Leider ist "timestamp" ein 'BIGINT' anstelle eines echten' timestamp', daher meine Mathe mit Zahlen anstelle von Zeitstempeln. – Bertuz

+0

Ich habe Ihren Code angepasst, um mit bigint zu arbeiten, aber ich bin mir nicht sicher, ob das noch funktionieren könnte Nutzen Sie einen leichteren SQL. [gist auf die neue SQL] (https://gist.github.com/bertuz/5544663474b8a0850dcede03d1903a02) können Sie bitte einen Blick und geben Sie mir ein Feedback? Plus: Sie haben darüber gesprochen, dass Postgres besser mit Ihrer Lösung funktioniert: Wie haben Sie das analysiert? Mit 'EXPLAIN'? Hier ist der [Abfrageplan] (https://gist.github.com/bertuz/5f06ab81cde3e78231c94c3a76ad20f8) meine Abfrage führt. Führt es tatsächlich zwei Scans auf hst_energy_d durch? Das ist eine Menge*! Danke – Bertuz

+0

Gut, 'EXPLAIN' spricht für sich selbst: Ihre Lösungen kosten' 48.47', im Gegensatz zu meinen Kosten '247.17' – Bertuz

Verwandte Themen