PostgreSQL-Abfrage mit maximalem und minimalem Datum plus zugehöriger ID pro Zeile

7

Ich habe folgende Tabelle:

CREATE TABLE trans (
    id SERIAL PRIMARY KEY,
    trans_date date,
    trans_time time        
);

Ich möchte die folgende Ansicht haben

CREATE OR REPLACE VIEW daily_trans AS
SELECT trans_date,
    max(trans_time) as first, 
    min(trans_time) as last,
    calculate_status(min(trans_time), max(trans_time)) as status 
GROUP BY trans_date 

mit Spalten, die die IDs der maximalen und minimalen Trans_time angeben.

Wie mache ich das?

Zufälliger Joe
quelle

Antworten:

10

Sie können dies in einem Schritt mit Fensterfunktionen berechnen :

CREATE OR REPLACE VIEW daily_trans AS
SELECT DISTINCT
       trans_date
     , first_value(trans_time) OVER w AS first_time
     , first_value(id)         OVER w AS first_id
     , last_value(trans_time)  OVER w AS last_time
     , last_value(id)          OVER w AS last_id
     , calculate_status(min(trans_time) OVER w
                      , max(trans_time) OVER w) AS status 
FROM   trans
WINDOW w AS (PARTITION BY trans_date ORDER BY trans_time, id
             ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
ORDER  BY 1;

Ich habe idals sekundäre ORDERSpalte in die Fensterklausel eingefügt , um die Sortierreihenfolge bei identischen Zeiten pro Tag stabil zu machen.

Wenn Sie mit Fensterfunktionen nicht vertraut sind, lesen Sie unbedingt dieses Kapitel des Handbuchs .

ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWINGist eine nicht so häufig verwendete Klausel zum Anpassen des Fensterrahmens, da last_value()immer der letzte Wert der gesamten Partition (das Datum) und nicht der letzte Wert bis zum aktuellen Wert (einschließlich Peers) gemäß der Sortierreihenfolge zurückgegeben werden soll.

So kombinieren Sie DISTINCTmit Fensterfunktionen:

Erwin Brandstetter
quelle
Vielen Dank @Erwin Brandstetter, es ist eine ausgezeichnete Antwort.
Zufälliger Joe
4

Sie wollen ungefähr so ​​etwas

select min_trans_time, min_trans.id as min_trans_time_id,
       max_trans_time, max_trans.id as max_trans_time_id
from (
    select   trans_date,
             max(trans_time) as max_trans_time, 
             min(trans_time) as min_trans_time,
    from     trans        
    group by trans_date) min_max_trans,
       trans as min_trans,
       trans as max_trans
where min_trans.trans_time = min_max_trans.min_trans_time
and   max_trans.trans_time = min_max_trans.max_trans_time

Was möglicherweise nicht stabil ist, wenn mehrere transes dasselbe teilen trans_time( dh, wenn die Abfrage für denselben Datensatz ausgeführt wird, kann dies zu unterschiedlichen Ergebnissen führen. Eine einfache Möglichkeit, dies zu lösen, besteht darin, die maximale oder minimale ID auszuwählen. Zum Beispiel könnte dies natürlich die Ergebnisse beeinflussen :).

Sie können auch Fensterfunktionen verwenden, wenn Sie PostgreSQL 8.4 oder höher verwenden. Sie bieten eine klarere Abfrage (klar, wenn Sie mit Fensterfunktionen vertraut sind :), und sie ermöglichen es Ihnen, Dinge zu tun, die mit der obigen Strategie ziemlich schwierig zu tun sind (z. B. den 2. höheren Wert anstelle des Maximums zu erhalten). Nach meiner kurzen Erfahrung schnitten sie jedoch schlechter ab als der hier vorgeschlagene Self-Join-Ansatz. Erwins Antwort enthält eine Version der Abfrage mit Fensterfunktionen. Es wäre interessant festzustellen, welche Abfrage besser ausgeführt wird und / oder besser optimiert werden kann (durch Hinzufügen von Indizes usw.).

Alex
quelle
+1 für die Berücksichtigung nicht eindeutiger Zeiten pro Tag (obwohl Sie das nicht gelöst haben) und die Erwähnung von Fensterfunktionen (obwohl Sie sie nicht verwendet haben).
Erwin Brandstetter
Ich habe deine positiv bewertet. Ich schlage diese Lösung vor und behalte sie hier, da Fensterfunktionen nicht in allen RDBMS verfügbar oder standardisiert sind und möglicherweise weniger leistungsfähig sind. Allerdings werde ich meine Antwort ein wenig bearbeiten ...
Alex
2
Tatsächlich sind Fensterfunktionen im SQL: 2003-Standard enthalten und werden von allen wichtigen RDBMS (Oracle, PostgreSQL, SQL Server, ...) unterstützt. Die einzige bemerkenswerte Ausnahme ist MySQL. Andererseits unterstützt MySQL nicht einmal CTEs (ein weiterer wichtiger SQL-Standard).
Erwin Brandstetter
Sind sie? Ich dachte, jedes RDBMS verwendet eine andere Syntax. Wenn ja, verwirrt es mich, dass Limit / Offset nicht standardisiert ist (was nicht das letzte Mal war, als ich IIRC überprüft habe - obwohl das schon lange her ist).
Alex
2
RDBMS verwenden für fast jedes Detail eine etwas andere Syntax. Das standardkonformste RDBMS ist wahrscheinlich PostgreSQL. Das ändert nichts an der Tatsache, dass SQL-Standards existieren und dass die Hauptakteure sie größtenteils implementieren.
Erwin Brandstetter