Gruppierung oder Fenster

13

Ich habe eine Situation, die meiner Meinung nach mit der Fensterfunktion gelöst werden kann, bin mir aber nicht sicher.

Stellen Sie sich die folgende Tabelle vor

CREATE TABLE tmp
  ( date timestamp,        
    id_type integer
  ) ;

INSERT INTO tmp 
    ( date, id_type )
VALUES
    ( '2017-01-10 07:19:21.0', 3 ),
    ( '2017-01-10 07:19:22.0', 3 ),
    ( '2017-01-10 07:19:23.1', 3 ),
    ( '2017-01-10 07:19:24.1', 3 ),
    ( '2017-01-10 07:19:25.0', 3 ),
    ( '2017-01-10 07:19:26.0', 5 ),
    ( '2017-01-10 07:19:27.1', 3 ),
    ( '2017-01-10 07:19:28.0', 5 ),
    ( '2017-01-10 07:19:29.0', 5 ),
    ( '2017-01-10 07:19:30.1', 3 ),
    ( '2017-01-10 07:19:31.0', 5 ),
    ( '2017-01-10 07:19:32.0', 3 ),
    ( '2017-01-10 07:19:33.1', 5 ),
    ( '2017-01-10 07:19:35.0', 5 ),
    ( '2017-01-10 07:19:36.1', 5 ),
    ( '2017-01-10 07:19:37.1', 5 )
  ;

Ich möchte bei jeder Änderung in der Spalte id_type eine neue Gruppe haben. ZB 1. Gruppe von 7:19:21 bis 7:19:25 Uhr, 2. Start und Ziel um 7:19:26 Uhr und so weiter.
Nachdem es funktioniert, möchte ich weitere Kriterien einschließen, um Gruppen zu definieren.

In diesem Moment mit der Abfrage unten ...

SELECT distinct 
    min(min(date)) over w as begin, 
    max(max(date)) over w as end,   
    id_type
from tmp
GROUP BY id_type
WINDOW w as (PARTITION BY id_type)
order by  begin;

Ich erhalte folgendes Ergebnis:

begin                   end                     id_type
2017-01-10 07:19:21.0   2017-01-10 07:19:32.0   3
2017-01-10 07:19:26.0   2017-01-10 07:19:37.1   5

Während ich möchte:

begin                   end                     id_type
2017-01-10 07:19:21.0   2017-01-10 07:19:25.0   3
2017-01-10 07:19:26.0   2017-01-10 07:19:26.0   5
2017-01-10 07:19:27.1   2017-01-10 07:19:27.1   3
2017-01-10 07:19:28.0   2017-01-10 07:19:29.0   5
2017-01-10 07:19:30.1   2017-01-10 07:19:30.1   3
2017-01-10 07:19:31.0   2017-01-10 07:19:31.0   5
2017-01-10 07:19:32.0   2017-01-10 07:19:32.0   3
2017-01-10 07:19:33.1   2017-01-10 07:19:37.1   5

Nachdem ich diesen ersten Schritt gelöst habe, füge ich weitere Spalten hinzu, die als Regeln zum Aufteilen von Gruppen verwendet werden sollen. Diese anderen Spalten können nicht mehr verwendet werden.

Postgres-Version: 8.4 (Wir haben Postgres mit Postgis, daher ist ein Upgrade nicht einfach. Die Postgis-Funktionen ändern ihre Namen und es gibt andere Probleme. Wir hoffen jedoch, dass wir bereits alles neu schreiben und die neue Version eine neuere Version 9.X mit verwenden wird postgis 2.x)

Lelo
quelle
2
Allgemeine Lösung: dba.stackexchange.com/questions/35380/…
Erwin Brandstetter

Antworten:

4

Für ein paar Punkte,

  • Rufen Sie keine nicht temporäre Tabelle auf tmp, die nur verwirrend wird.
  • Verwenden Sie keinen Text für Zeitstempel (das machen Sie in Ihrem Beispiel, weil der Zeitstempel nicht abgeschnitten wurde und hat .0)
  • Nennen Sie kein Feld, in dem Zeit ist date. Wenn es Datum und Uhrzeit hat, ist es ein Zeitstempel (und speichern Sie es als einen)

Besser eine Fensterfunktion verwenden ..

SELECT id_type, grp, min(date), max(date)
FROM (
  SELECT date, id_type, count(is_reset) OVER (ORDER BY date) AS grp
  FROM (
    SELECT date, id_type, CASE WHEN lag(id_type) OVER (ORDER BY date) <> id_type THEN 1 END AS is_reset
    FROM tmp
  ) AS t
) AS g
GROUP BY id_type, grp
ORDER BY min(date);

Ausgänge

 id_type | grp |          min          |          max          
---------+-----+-----------------------+-----------------------
       3 |   0 | 2017-01-10 07:19:21.0 | 2017-01-10 07:19:25.0
       5 |   1 | 2017-01-10 07:19:26.0 | 2017-01-10 07:19:26.0
       3 |   2 | 2017-01-10 07:19:27.1 | 2017-01-10 07:19:27.1
       5 |   3 | 2017-01-10 07:19:28.0 | 2017-01-10 07:19:29.0
       3 |   4 | 2017-01-10 07:19:30.1 | 2017-01-10 07:19:30.1
       5 |   5 | 2017-01-10 07:19:31.0 | 2017-01-10 07:19:31.0
       3 |   6 | 2017-01-10 07:19:32.0 | 2017-01-10 07:19:32.0
       5 |   7 | 2017-01-10 07:19:33.1 | 2017-01-10 07:19:37.1
(8 rows)

Erklärung

Zuerst brauchen wir Resets. Wir generieren sie mit lag()

SELECT date, id_type, CASE WHEN lag(id_type) OVER (ORDER BY date) <> id_type THEN 1 END AS is_reset
FROM tmp
ORDER BY date;

         date          | id_type | is_reset 
-----------------------+---------+----------
 2017-01-10 07:19:21.0 |       3 |         
 2017-01-10 07:19:22.0 |       3 |         
 2017-01-10 07:19:23.1 |       3 |         
 2017-01-10 07:19:24.1 |       3 |         
 2017-01-10 07:19:25.0 |       3 |         
 2017-01-10 07:19:26.0 |       5 |        1
 2017-01-10 07:19:27.1 |       3 |        1
 2017-01-10 07:19:28.0 |       5 |        1
 2017-01-10 07:19:29.0 |       5 |         
 2017-01-10 07:19:30.1 |       3 |        1
 2017-01-10 07:19:31.0 |       5 |        1
 2017-01-10 07:19:32.0 |       3 |        1
 2017-01-10 07:19:33.1 |       5 |        1
 2017-01-10 07:19:35.0 |       5 |         
 2017-01-10 07:19:36.1 |       5 |         
 2017-01-10 07:19:37.1 |       5 |         
(16 rows)

Dann zählen wir, um Gruppen zu bekommen.

SELECT date, id_type, count(is_reset) OVER (ORDER BY date) AS grp
FROM (
  SELECT date, id_type, CASE WHEN lag(id_type) OVER (ORDER BY date) <> id_type THEN 1 END AS is_reset
  FROM tmp
  ORDER BY date
) AS t
ORDER BY date

         date          | id_type | grp 
-----------------------+---------+-----
 2017-01-10 07:19:21.0 |       3 |   0
 2017-01-10 07:19:22.0 |       3 |   0
 2017-01-10 07:19:23.1 |       3 |   0
 2017-01-10 07:19:24.1 |       3 |   0
 2017-01-10 07:19:25.0 |       3 |   0
 2017-01-10 07:19:26.0 |       5 |   1
 2017-01-10 07:19:27.1 |       3 |   2
 2017-01-10 07:19:28.0 |       5 |   3
 2017-01-10 07:19:29.0 |       5 |   3
 2017-01-10 07:19:30.1 |       3 |   4
 2017-01-10 07:19:31.0 |       5 |   5
 2017-01-10 07:19:32.0 |       3 |   6
 2017-01-10 07:19:33.1 |       5 |   7
 2017-01-10 07:19:35.0 |       5 |   7
 2017-01-10 07:19:36.1 |       5 |   7
 2017-01-10 07:19:37.1 |       5 |   7
(16 rows)

Dann wickeln wir in einem subselect GROUP BYund ORDERund wählen Sie den min max (Bereich)

SELECT id_type, grp, min(date), max(date)
FROM (
  .. stuff
) AS g
GROUP BY id_type, grp
ORDER BY min(date);
Evan Carroll
quelle
16

1. Fensterfunktionen plus Unterabfragen

Zählen Sie die Schritte, um Gruppen zu bilden, ähnlich wie bei Evans Idee , mit Änderungen und Korrekturen:

SELECT id_type
     , min(date) AS begin
     , max(date) AS end
     , count(*)  AS row_ct  -- optional addition
FROM  (
   SELECT date, id_type, count(step OR NULL) OVER (ORDER BY date) AS grp
   FROM  (
      SELECT date, id_type
           , lag(id_type, 1, id_type) OVER (ORDER BY date) <> id_type AS step
      FROM   tmp
      ) sub1
   ) sub2
GROUP  BY id_type, grp
ORDER  BY min(date);

Dies setzt voraus, dass es sich um betroffene Spalten handelt NOT NULL. Sonst müssen Sie mehr tun.

Auch vorausgesetzt date, definiert zu sein UNIQUE, sonst müssen Sie einen Tiebreaker zu den ORDER BYKlauseln hinzufügen , um deterministische Ergebnisse zu erhalten. Wie: ORDER BY date, id.

Detaillierte Erklärung (Antwort auf sehr ähnliche Frage):

Beachten Sie insbesondere:

  • In verwandten Fällen kann es lag()mit 3 Parametern wesentlich sein, den Eckfall der ersten (oder letzten) Reihe elegant abzudecken. (Der 3. Parameter wird standardmäßig verwendet, wenn keine vorherige (nächste) Zeile vorhanden ist.

    lag(id_type, 1, id_type) OVER ()

    Da wir nur an einer tatsächlichen Änderung von id_type( TRUE) interessiert sind , spielt es in diesem speziellen Fall keine Rolle. NULLund FALSEbeide zählen nicht als step.

  • count(step OR NULL) OVER (ORDER BY date)ist die kürzeste Syntax, die auch in Postgres 9.3 oder älter funktioniert. count()zählt nur Werte ungleich Null ...

    In modernen Postgres wäre die sauberere, äquivalente Syntax:

    count(step) FILTER (WHERE step) OVER (ORDER BY date)

    Einzelheiten:

2. Subtrahieren Sie zwei Fensterfunktionen, eine Unterabfrage

Ähnlich wie Eriks Idee mit Modifikationen:

SELECT min(date) AS begin
     , max(date) AS end
     , id_type
FROM  (
   SELECT date, id_type
        , row_number() OVER (ORDER BY date)
        - row_number() OVER (PARTITION BY id_type ORDER BY date) AS grp
   FROM   tmp
   ) sub
GROUP  BY id_type, grp
ORDER  BY min(date);

Wenn datedefiniert ist UNIQUE, wie ich oben erwähne (Sie haben es nie geklärt), dense_rank()wäre es sinnlos, da das Ergebnis dasselbe ist wie für row_number()und das letztere wesentlich billiger ist.

Wenn dateist nicht definiert UNIQUE(und wir wissen nicht , dass die einzigen Duplikate auf(date, id_type) ), die alle diese Fragen sind sinnlos, da das Ergebnis willkürlich ist.

Außerdem ist eine Unterabfrage in der Regel billiger als ein CTE in Postgres. Verwenden Sie CTEs nur, wenn Sie sie benötigen .

Verwandte Antworten mit mehr Erklärung:

In verwandten Fällen, in denen wir bereits eine laufende Nummer in der Tabelle haben, können wir mit einer einzigen Fensterfunktion auskommen:

3. Höchstleistung mit plpgsql Funktion

Da diese Frage unerwartet populär geworden ist, füge ich eine weitere Lösung hinzu, um die Spitzenleistung zu demonstrieren.

SQL verfügt über viele ausgereifte Tools, um Lösungen mit kurzer und eleganter Syntax zu erstellen. Eine deklarative Sprache hat jedoch ihre Grenzen für komplexere Anforderungen, die prozedurale Elemente beinhalten.

Eine serverseitige prozedurale Funktion ist dafür schneller als alles, was bisher veröffentlicht wurde, da sie nur einen einzigen sequentiellen Scan der Tabelle und eine einzige Sortieroperation benötigt . Wenn ein passender Index verfügbar ist, kann auch nur ein einziger Index-Scan durchgeführt werden.

CREATE OR REPLACE FUNCTION f_tmp_groups()
  RETURNS TABLE (id_type int, grp_begin timestamp, grp_end timestamp) AS
$func$
DECLARE
   _row  tmp;                       -- use table type for row variable
BEGIN
   FOR _row IN
      TABLE tmp ORDER BY date       -- add more columns to make order deterministic
   LOOP
      CASE _row.id_type = id_type 
      WHEN TRUE THEN                -- same group continues
         grp_end := _row.date;      -- remember last date so far
      WHEN FALSE THEN               -- next group starts
         RETURN NEXT;               -- return result for last group
         id_type   := _row.id_type;
         grp_begin := _row.date;
         grp_end   := _row.date;
      ELSE                          -- NULL for 1st row
         id_type   := _row.id_type; -- remember row data for starters
         grp_begin := _row.date;
         grp_end   := _row.date;
      END CASE;
   END LOOP;

   RETURN NEXT;                     -- return last result row      
END
$func$ LANGUAGE plpgsql;

Anruf:

SELECT * FROM f_tmp_groups();

Testen Sie mit:

EXPLAIN (ANALYZE, TIMING OFF)  -- to focus on total performance
SELECT * FROM  f_tmp_groups();

Sie können die Funktion mit polymorphen Typen generisch machen und Tabellentyp und Spaltennamen übergeben. Einzelheiten:

Wenn Sie eine Funktion dafür nicht beibehalten möchten oder können, lohnt es sich sogar, eine temporäre Funktion im laufenden Betrieb zu erstellen. Kostet ein paar ms.


dbfiddle für Postgres 9.6, Leistungsvergleich für alle drei. Aufbau aufJacks Testfall, modifiziert.

dbfiddle für Postgres 8.4, wo die Leistungsunterschiede noch größer sind.

Erwin Brandstetter
quelle
Lesen Sie dies ein paar Mal - Sie sind sich immer noch nicht sicher, wovon Sie mit den drei Argumenten sprechen, wann Sie sie verwenden müssen count(x or null)oder was sie dort tun. Vielleicht könnten Sie einige Beispiele zeigen , wo es ist erforderlich, weil es hier nicht erforderlich. Und was würde das Erfordernis ausmachen, diese Eckfälle abzudecken? Übrigens habe ich meine Downvote in die Upvote geändert, nur für das Beispiel pl / pgsql. Das ist wirklich cool. (Aber im Allgemeinen bin ich gegen Antworten, die andere Antworten zusammenfassen oder Eckfälle abdecken - obwohl ich es hasse zu sagen, dass dies ein Eckfall ist, weil ich es nicht verstehe).
Evan Carroll
Ich würde sie in zwei separate, selbst beantwortete Fragen stellen, da ich sicher nicht der einzige bin, der sich fragt, was das count(x or null)macht. Gerne stelle ich Ihnen beide Fragen, wenn Sie es vorziehen.
Evan Carroll
7

Sie können dies als einfache Subtraktion von ROW_NUMBER()Operationen tun (oder wenn Ihre Daten nicht eindeutig sind, obwohl sie pro immer noch eindeutig sind id_type, können Sie DENSE_RANK()stattdessen verwenden, obwohl dies eine teurere Abfrage sein wird):

WITH IdTypes AS (
   SELECT
      date,
      id_type,
      Row_Number() OVER (ORDER BY date)
         - Row_Number() OVER (PARTITION BY id_type ORDER BY date)
         AS Seq
   FROM
      tmp
)
SELECT
   Min(date) AS begin,
   Max(date) AS end,
   id_type
FROM IdTypes
GROUP BY id_type, Seq
ORDER BY begin
;

Sehen Sie sich diese Arbeit bei DB Fiddle an (oder schauen Sie sich die DENSE_RANK-Version an) )

Ergebnis:

begin                  end                    id_type
---------------------  ---------------------  -------
2017-01-10 07:19:21    2017-01-10 07:19:25    3
2017-01-10 07:19:26    2017-01-10 07:19:26    5
2017-01-10 07:19:27.1  2017-01-10 07:19:27.1  3
2017-01-10 07:19:28    2017-01-10 07:19:29    5
2017-01-10 07:19:30.1  2017-01-10 07:19:30.1  3
2017-01-10 07:19:31    2017-01-10 07:19:31    5
2017-01-10 07:19:32    2017-01-10 07:19:32    3
2017-01-10 07:19:33.1  2017-01-10 07:19:37.1  5

Logischerweise können Sie sich dies als ein einfaches DENSE_RANK()mit einem vorstellen, dh PREORDER BYSie möchten, dass DENSE_RANKalle Elemente, die zusammen gereiht sind, und Sie möchten, dass sie nach Datum geordnet sind. Sie müssen sich nur mit dem lästigen Problem der Tatsache befassen, dass wird bei jeder Änderung des Datums DENSE_RANKerhöht. Verwenden Sie dazu den Ausdruck, den ich Ihnen oben gezeigt habe. Stellen Sie sich vor, Sie hätten diese Syntax: DENSE_RANK() OVER (PREORDER BY date, ORDER BY id_type)Dabei wird das PREORDERvon der Rangfolgenberechnung ausgeschlossen und nur das ORDER BYgezählt.

Beachten Sie, dass dies GROUP BYsowohl für die generierte SeqSpalte als auch für die id_typeSpalte wichtig ist . Seqist NICHT einzigartig für sich, es kann Überlappungen geben - Sie müssen auch nach gruppieren id_type.

Weitere Informationen zu diesem Thema:

Dieser erste Link gibt Ihnen einen Code, den Sie verwenden können, wenn Sie möchten, dass das Start- oder Enddatum mit dem End- / Startdatum des vorherigen oder nächsten Zeitraums übereinstimmt (es gibt also keine Lücken). Plus andere Versionen, die Ihnen bei Ihrer Anfrage behilflich sein könnten. Sie müssen jedoch aus der SQL Server-Syntax übersetzt werden ...

ErikE
quelle
6

In Postgres 8.4 können Sie eine RECURSIVE- Funktion verwenden.

Wie machen Sie das

Die rekursive Funktion fügt jedem unterschiedlichen id_type eine Ebene hinzu, indem die Daten nacheinander in absteigender Reihenfolge ausgewählt werden.

       date           | id_type | lv
--------------------------------------
2017-01-10 07:19:21.0      3       8
2017-01-10 07:19:22.0      3       8
2017-01-10 07:19:23.1      3       8
2017-01-10 07:19:24.1      3       8
2017-01-10 07:19:25.0      3       8
2017-01-10 07:19:26.0      5       7
2017-01-10 07:19:27.1      3       6
2017-01-10 07:19:28.0      5       5
2017-01-10 07:19:29.0      5       5
2017-01-10 07:19:30.1      3       4
2017-01-10 07:19:31.0      5       3
2017-01-10 07:19:32.0      3       2
2017-01-10 07:19:33.1      5       1
2017-01-10 07:19:35.0      5       1
2017-01-10 07:19:36.1      5       1
2017-01-10 07:19:37.1      5       1

Verwenden Sie dann MAX (Datum), MIN (Datum), gruppiert nach Ebene, id_type, um das gewünschte Ergebnis zu erhalten.

with RECURSIVE rdates as 
(
    (select   date, id_type, 1 lv 
     from     yourTable
     order by date desc
     limit 1
    )
    union
    (select    d.date, d.id_type,
               case when r.id_type = d.id_type 
                    then r.lv 
                    else r.lv + 1 
               end lv    
    from       yourTable d
    inner join rdates r
    on         d.date < r.date
    order by   date desc
    limit      1)
)
select   min(date) StartDate,
         max(date) EndDate,
         id_type
from     rdates
group by lv, id_type
;

+---------------------+---------------------+---------+
| startdate           |       enddate       | id_type |
+---------------------+---------------------+---------+
| 10.01.2017 07:19:21 | 10.01.2017 07:19:25 |    3    |
| 10.01.2017 07:19:26 | 10.01.2017 07:19:26 |    5    |
| 10.01.2017 07:19:27 | 10.01.2017 07:19:27 |    3    |
| 10.01.2017 07:19:28 | 10.01.2017 07:19:29 |    5    |
| 10.01.2017 07:19:30 | 10.01.2017 07:19:30 |    3    |
| 10.01.2017 07:19:31 | 10.01.2017 07:19:31 |    5    |
| 10.01.2017 07:19:32 | 10.01.2017 07:19:32 |    3    |
| 10.01.2017 07:19:33 | 10.01.2017 07:19:37 |    5    |
+---------------------+---------------------+---------+

Überprüfen Sie es: http://rextester.com/WCOYFP6623

McNets
quelle
5

Hier ist eine andere Methode, die Evan und Erwin insofern ähnlich ist, als sie die LAG zur Bestimmung von Inseln verwendet. Es unterscheidet sich von diesen Lösungen darin, dass es nur eine Ebene der Verschachtelung, keine Gruppierung und erheblich mehr Fensterfunktionen verwendet:

SELECT
  id_type,
  date AS begin,
  COALESCE(
    LEAD(prev_date) OVER (ORDER BY date ASC),
    last_date
  ) AS end
FROM
  (
    SELECT
      id_type,
      date,
      LAG(date) OVER (ORDER BY date ASC) AS prev_date,
      MAX(date) OVER () AS last_date,
      CASE id_type
        WHEN LAG(id_type) OVER (ORDER BY date ASC)
        THEN 0
        ELSE 1
      END AS is_start
    FROM
      tmp
  ) AS derived
WHERE
  is_start = 1
ORDER BY
  date ASC
;

Die is_startberechnete Spalte in der verschachtelten SELECT-Anweisung markiert den Anfang jeder Insel. Darüber hinaus macht das verschachtelte SELECT das vorherige Datum jeder Zeile und das letzte Datum der Datenmenge verfügbar.

Für Zeilen, bei denen es sich um die Anfänge der jeweiligen Inseln handelt, ist das vorherige Datum das Enddatum der vorherigen Insel. Das ist es, als was das Haupt-SELECT es verwendet. Es werden nur die Zeilen ausgewählt, die der is_start = 1Bedingung entsprechen, und für jede zurückgegebene Zeile werden die eigenen Zeilen dateals beginund die folgenden Zeilen prev_dateals angezeigt end. Da die letzte Zeile keine folgende Zeile enthält,LEAD(prev_date) eine Null zurückgegeben, für die die COALESCE-Funktion das letzte Datum der Datenmenge ersetzt.

Sie können mit dieser Lösung bei dbfiddle spielen .

Wenn Sie zusätzliche Spalten zur Identifizierung der Inseln einfügen, möchten Sie wahrscheinlich eine PARTITION BY-Unterklausel in die OVER-Klausel jeder Fensterfunktion einfügen. Wenn Sie beispielsweise die Inseln in Gruppen erkennen möchten, die durch a definiert sind parent_id, muss die obige Abfrage wahrscheinlich so aussehen:

SELECT
  parent_id,
  id_type,
  date AS begin,
  COALESCE(
    LEAD(prev_date) OVER (PARTITION BY parent_id ORDER BY date ASC),
    last_date
  ) AS end
FROM
  (
    SELECT
      parent_id,
      id_type,
      date,
      LAG(date) OVER (PARTITION BY parent_id ORDER BY date ASC) AS prev_date,
      MAX(date) OVER (PARTITION BY parent_id) AS last_date,
      CASE id_type
        WHEN LAG(id_type) OVER (PARTITION BY parent_id ORDER BY date ASC)
        THEN 0
        ELSE 1
      END AS is_start
    FROM
      tmp
  ) AS derived
WHERE
  is_start = 1
ORDER BY
  date ASC
;

Und wenn Sie sich für die Lösung von Erwin oder Evan entscheiden, muss meiner Meinung nach auch eine ähnliche Änderung vorgenommen werden.

Andriy M
quelle
5

Mehr aus akademischem Interesse als als praktische Lösung können Sie dies auch mit einem benutzerdefinierten Aggregat erreichen . Wie die anderen Lösungen funktioniert dies auch unter Postgres 8.4, aber wie andere bereits kommentiert haben, führen Sie bitte ein Upgrade durch, wenn Sie können.

Das Aggregat wird so behandelt, nullals ob es sich um ein anderes handelt foo_type, sodass Nullläufe gleich behandelt werden grp- das kann sein oder auch nicht, was Sie wollen.

create function grp_sfunc(integer[],integer) returns integer[] language sql as $$
  select array[$1[1]+($1[2] is distinct from $2 or $1[3]=0)::integer,$2,1];
$$;
create function grp_finalfunc(integer[]) returns integer language sql as $$
  select $1[1];
$$;
create aggregate grp(integer)(
  sfunc = grp_sfunc
, stype = integer[]
, finalfunc = grp_finalfunc
, initcond = '{0,0,0}'
);
select min(foo_at) begin_at, max(foo_at) end_at, foo_type
from (select *, grp(foo_type) over (order by foo_at) from foo) z
group by grp, foo_type
order by 1;
begin_at | end_at | foo_type
: -------------------- | : -------------------- | -------:
2017-01-10 07:19:21 | 2017-01-10 07:19:25 | 3
2017-01-10 07:19:26 | 2017-01-10 07:19:26 | 5
2017-01-10 07: 19: 27.1 | 2017-01-10 07: 19: 27.1 | 3
2017-01-10 07:19:28 | 2017-01-10 07:19:29 | 5
2017-01-10 07: 19: 30.1 | 2017-01-10 07: 19: 30.1 | 3
2017-01-10 07:19:31 | 2017-01-10 07:19:31 | 5
2017-01-10 07:19:32 | 2017-01-10 07:19:32 | 3
2017-01-10 07: 19: 33.1 | 2017-01-10 07: 19: 37.1 | 5

dbfiddle hier

Jack sagt, versuchen Sie es mit topanswers.xyz
quelle
4

Dies kann durchgeführt werden RECURSIVE CTE, um die "Startzeit" von einer Zeile zur nächsten zu verschieben, und um einige zusätzliche (bequeme) Vorbereitungen zu treffen.

Diese Abfrage gibt das gewünschte Ergebnis zurück:

WITH RECURSIVE q AS
(
    SELECT
        id_type,
        "date",
        /* We compute next id_type for convenience, plus row_number */
        row_number()  OVER (w) AS rn,
        lead(id_type) OVER (w) AS next_id_type
    FROM
        t
    WINDOW
        w AS (ORDER BY "date") 
)

nach der vorbereitung ... rekursiver teil

, rec AS 
(
    /* Anchor */
    SELECT
        q.rn,
        q."date" AS "begin",
        /* When next_id_type is different from Look also at **next** row to find out whether we need to mark an end */
        case when q.id_type is distinct from q.next_id_type then q."date" END AS "end",
        q.id_type
    FROM
        q
    WHERE
        rn = 1

    UNION ALL

    /* Loop */
    SELECT
        q.rn,
        /* We keep copying 'begin' from one row to the next while type doesn't change */
        case when q.id_type = rec.id_type then rec.begin else q."date" end AS "begin",
        case when q.id_type is distinct from q.next_id_type then q."date" end AS "end",
        q.id_type
    FROM
        rec
        JOIN q ON q.rn = rec.rn+1
)
-- We filter the rows where "end" is not null, and project only needed columns
SELECT
    "begin", "end", id_type
FROM
    rec
WHERE
    "end" is not null ;

Sie können dies unter http://rextester.com/POYM83542 überprüfen

Diese Methode lässt sich nicht gut skalieren. Für eine 8_641-Zeilentabelle werden 7 Sekunden benötigt, für eine doppelt so große Tabelle 28 Sekunden. Einige Beispiele mehr zeigen Ausführungszeiten, die wie O (n ^ 2) aussehen.

Die Methode von Evan Carrol dauert weniger als eine Sekunde (sprich: mach mit!) Und sieht aus wie O (n). Rekursive Abfragen sind absolut ineffizient und sollten als letzter Ausweg betrachtet werden.

joanolo
quelle