2012-05-16 8 views
6

Angenommen, ich habe diese Tabelle:Fensterfunktionen und „local“ Aggregation

select * from window_test; 

k | v 
---+--- 
a | 1 
a | 2 
b | 3 
a | 4 

Letztlich möchte ich erhalten:

k | min_v | max_v 
---+-------+------- 
a | 1  | 2 
b | 3  | 3 
a | 4  | 4 

Aber ich würde genauso glücklich sein, dies zu bekommen (da ich kann es leicht filtern mit distinct):

k | min_v | max_v 
---+-------+------- 
a | 1  | 2 
a | 1  | 2 
b | 3  | 3 
a | 4  | 4 

Ist es möglich, 9.1+ Fenster func dies mit PostgreSQL zu erreichen tionen? Ich versuche zu verstehen, ob ich es schaffen kann, in diesem Beispiel eine separate Partition für das erste und letzte Vorkommen von k=a zu verwenden (geordnet nach v).

+0

Erstens: Möchten Sie komplette Bereiche "zusammenbrechen"? I.e. Was würden Sie erwarten, wenn Sie '(a, 5), (a, 6), (a, 7)' zu Ihrem Datensatz hinzufügen? Zweitens: Ist 'v' immer perfekt um 1 zu erhöhen oder können Lücken auftreten? –

+0

@ A.H. 'v' ist nicht perfekt ansteigend (tatsächlich ist es ein Zeitstempel). Ja, ich möchte komplette Bereiche ausblenden und würde '(a, 4, 5)' als letztes Tupel nach dem Einfügen von '(a, 5)' erwarten. –

+0

@KonradGarus, funktioniert eine der bereitgestellten Lösungen für Sie? – vyegorov

Antwort

7

Dies gibt das gewünschte Ergebnis mit den Beispieldaten. Nicht sicher, ob es für die Daten der realen Welt funktioniert:

select k, 
     min(v) over (partition by group_nr) as min_v, 
     max(v) over (partition by group_nr) as max_v 
from (
    select *, 
      sum(group_flag) over (order by v,k) as group_nr 
    from (
    select *, 
      case 
       when lag(k) over (order by v) = k then null 
       else 1 
      end as group_flag 
    from window_test 
    ) t1 
) t2 
order by min_v; 

ich aber die DISTINCT weggelassen.

+0

Sehr nette Annäherung! – vyegorov

+0

Danke Jungs. Ich mag beide Lösungen, finde diese aber eleganter. –

+0

Gute Arbeit dort, aber es funktionierte nicht für mich (postgresql 9.3), bis ich den Satz 'group by k, group_nr' vor der' order by min_v' hinzugefügt und die 'min (v) over ...' und 'max (v) over ...' Fenster Funktionen in der Hauptauswahl für einfache Aggregat 'min (v)' und 'max (v)' Funktionen. Danke, das hat mir wirklich geholfen :) – Aleix

1

EDIT: Ich habe mit der folgenden Abfrage kam - ohne Fensterfunktionen überhaupt:

WITH RECURSIVE tree AS (
    SELECT k, v, ''::text as next_k, 0 as next_v, 0 AS level FROM window_test 
    UNION ALL 
    SELECT c.k, c.v, t.k, t.v + level, t.level + 1 
    FROM tree t JOIN window_test c ON c.k = t.k AND c.v + 1 = t.v), 
partitions AS (
    SELECT t.k, t.v, t.next_k, 
     coalesce(nullif(t.next_v, 0), t.v) AS next_v, t.level 
    FROM tree t 
    WHERE NOT EXISTS (SELECT 1 FROM tree WHERE next_k = t.k AND next_v = t.v)) 
SELECT min(k) AS k, v AS min_v, max(next_v) AS max_v 
    FROM partitions p 
GROUP BY v 
ORDER BY 2; 

ich jetzt 2 Arbeitsanfragen zur Verfügung gestellt haben, ich hoffe, einer von ihnen wird Ihr Hotel.

SQL Fiddle für diese Variante.


Eine andere Möglichkeit, dies zu erreichen, ist die Verwendung einer Unterstützungssequenz.

  1. eine Unterstützung Sequenz erstellen:

    CREATE SEQUENCE wt_rank START WITH 1; 
    
  2. Die Abfrage:

    WITH source AS (
        SELECT k, v, 
         coalesce(lag(k) OVER (ORDER BY v), k) AS prev_k 
        FROM window_test 
        CROSS JOIN (SELECT setval('wt_rank', 1)) AS ri), 
    ranking AS (
        SELECT k, v, prev_k, 
         CASE WHEN k = prev_k THEN currval('wt_rank') 
           ELSE nextval('wt_rank') END AS rank 
        FROM source) 
    SELECT r.k, min(s.v) AS min_v, max(s.v) AS max_v 
        FROM ranking r 
        JOIN source s ON r.v = s.v 
        GROUP BY r.rank, r.k 
        ORDER BY 2; 
    
+0

Danke. Eigentlich ist "v" die Sortierspalte. Diese Lösung bricht, wenn ich am Ende ein (a, 5) Tupel hinzufüge (mit anderen Worten, wenn es einige Cluster mit mehr als einer Reihe gibt). –

0

Würde dies nicht die Arbeit für Sie tun, ohne die Notwendigkeit für Windows, Partitionen oder Koaleszenz. Es verwendet nur einen traditionellen SQL-Trick für nächste Tupel über eine selbst finden join, und eine min auf dem Unterschied:

SELECT k, min(v), max(v) FROM (
    SELECT k, v, v + min(d) lim FROM (
     SELECT x.*, y.k n, y.v - x.v d FROM window_test x 
     LEFT JOIN window_test y ON x.k <> y.k AND y.v - x.v > 0) 
    z GROUP BY k, v, n) 
w GROUP BY k, lim ORDER BY 2; 

Ich denke, das ist wahrscheinlich eine ‚relationale‘ Lösung, aber ich bin mir nicht sicher über seine Effizienz.

+0

Die Lösung mit einer Fensterfunktion ist in der Regel schneller. Ihre Abfrage muss zweimal durch die Tabelle "window_test" gehen (wegen des Self-Joins). Ein Join mit '<' or '>' skaliert normalerweise auch nicht. Plus: Sie gruppieren das Ergebnis des Joins, was bedeutet, dass mehr Zeilen von der Gruppe verarbeitet werden müssen, und zwar mit der Fensterfunktion (die nur einmal mit den Zeilen in der Tabelle umgehen muss). Aber was ist mit Fensterfunktionen falsch? Jedes moderne DBMS unterstützt sie. –

+0

Einverstanden. Und sicherlich nichts gegen Fensterfunktionen - ich biete diese Lösung dem OP an, da es interessant sein könnte, dass es nur Operatoren der relationalen Algebra verwendet und daher als "reiner" betrachtet werden könnte (ich persönlich denke, dass es auch konzeptionell klarer ist). Allerdings würde ich es nicht auf eine Milliarden Zeilen Relation verwenden ... –

Verwandte Themen