Grupowanie lub okno


13

Mam sytuację, którą moim zdaniem można rozwiązać za pomocą funkcji okna, ale nie jestem pewien.

Wyobraź sobie poniższą tabelę

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 )
  ;

Chciałbym mieć nową grupę przy każdej zmianie w kolumnie id_type. EG 1. grupa od 7:19:21 do 7:19:25, 2. początek i koniec o 7:19:26 i tak dalej.
Po uruchomieniu chcę podać więcej kryteriów definiowania grup.

W tej chwili za pomocą zapytania poniżej ...

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;

Otrzymuję następujący wynik:

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

Podczas gdy chciałbym:

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

Po rozwiązaniu tego pierwszego kroku dodam więcej kolumn, które będą służyć jako reguły do ​​dzielenia grup, a te inne będą miały wartość zerową.

Wersja Postgres: 8.4 (Mamy Postgres z Postgis, więc aktualizacja nie jest łatwa. Funkcje Postgis zmieniają nazwy i są inne problemy, ale mam nadzieję, że już wszystko piszemy, a nowa wersja będzie używać nowszej wersji 9.X z postgis 2.x)


Odpowiedzi:


4

Za kilka punktów

  • Nie dzwoń do tymczasowego stołu, tmpktóry po prostu staje się mylący.
  • Nie używaj tekstu dla znaczników czasu (robisz to w swoim przykładzie, ponieważ możemy stwierdzić, że znacznik czasu nie został obcięty i ma .0)
  • Nie nazywaj pola, w którym jest czas date. Jeśli ma datę i godzinę, jest to znacznik czasu (i zapisz go jako jeden)

Lepiej użyć funkcji okna.

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);

Wyjścia

 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)

Wyjaśnienie

Najpierw potrzebujemy resetów. Generujemy je 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)

Następnie liczymy, aby uzyskać grupy.

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)

Następnie zawijany w podselekcji GROUP BYi ORDERwybierz min max (zakres)

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

16

1. Funkcje okna i podkwerendy

Policz kroki, aby utworzyć grupy, podobne do pomysłu Evana , z modyfikacjami i poprawkami:

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);

Zakłada się, że zaangażowane kolumny to NOT NULL. W przeciwnym razie musisz zrobić więcej.

Również zakładając, dateże zostanie zdefiniowany UNIQUE, w przeciwnym razie musisz dodać krawat ORDER BYrozstrzygający, aby uzyskać deterministyczne wyniki. Jak: ORDER BY date, id.

Szczegółowe wyjaśnienie (odpowiedź na bardzo podobne pytanie):

Uwaga w szczególności:

  • W powiązanych przypadkach lag()3 parametry mogą być niezbędne do eleganckiego pokrycia narożnika pierwszego (lub ostatniego) rzędu. (Trzeci parametr jest używany domyślnie, jeśli nie ma poprzedniego (następnego) wiersza.

    lag(id_type, 1, id_type) OVER ()

    Ponieważ jesteśmy zainteresowani tylko w rzeczywistej zmiany w id_type( TRUE), to nie ma znaczenia w tym konkretnym przypadku. NULLi FALSEoba nie liczą się jako step.

  • count(step OR NULL) OVER (ORDER BY date)to najkrótsza składnia, która działa również w Postgresie 9.3 lub starszym. count()liczy tylko wartości inne niż null ...

    W nowoczesnym Postgres czystszą, równoważną składnią byłoby:

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

    Detale:

2. Odejmij dwie funkcje okna, jedną podkwerendę

Podobne do pomysłu Erika z modyfikacjami:

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);

Jeśli datejest zdefiniowane UNIQUE, jak wspomniałem powyżej (nigdy nie wyjaśniłeś), dense_rank()byłoby bezcelowe, ponieważ wynik jest taki sam jak dla, row_number()a ten jest znacznie tańszy.

Jeśli niedate jest zdefiniowany (i nie wiemy, że są włączone tylko duplikaty ), wszystkie te zapytania są bezcelowe, ponieważ wynik jest arbitralny.UNIQUE(date, id_type)

Ponadto podzapytanie jest zazwyczaj tańsze niż CTE w Postgres. Używaj CTE tylko wtedy, gdy ich potrzebujesz .

Powiązane odpowiedzi z dodatkowymi wyjaśnieniami:

W powiązanych przypadkach, w których mamy już numer bieżący w tabeli, możemy zrobić to za pomocą funkcji jednego okna:

3. Najwyższa wydajność dzięki funkcji plpgsql

Ponieważ to pytanie stało się nieoczekiwanie popularne, dodam inne rozwiązanie, aby zademonstrować najwyższą wydajność.

SQL ma wiele wyrafinowanych narzędzi do tworzenia rozwiązań o krótkiej i eleganckiej składni. Ale język deklaratywny ma swoje granice dla bardziej złożonych wymagań, które obejmują elementy proceduralne.

Server-side Funkcja proceduralna jest szybsze niż to coś pisał do tej pory, ponieważ wymaga jedynie jednego sekwencyjne skanowanie nad stołem i jednej operacji sortowania . Jeśli dostępny jest indeks dopasowania, nawet tylko pojedynczy skan indeksu.

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;

Połączenie:

SELECT * FROM f_tmp_groups();

Testuj z:

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

Możesz uczynić tę funkcję ogólną z typami polimorficznymi i typem tabeli i nazwami kolumn. Detale:

Jeśli nie chcesz lub nie możesz utrzymywać funkcji do tego celu, opłacalne byłoby nawet utworzenie funkcji tymczasowej w locie. Kosztuje kilka ms.


dbfiddle dla Postgres 9.6, porównujący wydajność wszystkich trzech. Zmodyfikowano budowanieprzypadku testowego Jacka.

dbfiddle dla Postgres 8.4, gdzie różnice wydajności są jeszcze większe.


Przeczytaj to kilka razy - wciąż nie jesteś pewien, o czym mówisz z trzema argumentami lag, kiedy będziesz musiał użyć, count(x or null)a nawet co tam robi. Być może mógłbyś pokazać niektóre próbki tam, gdzie jest to wymagane, ponieważ tutaj nie jest wymagane. I co kluczowy byłby wymóg objęcia tych przypadków narożnych. BTW, zmieniłem moje zdanie na upvote tylko dla przykładu pl / pgsql. To jest naprawdę świetne. (Ale generalnie jestem przeciwny odpowiedziom, które podsumowują inne odpowiedzi lub obejmują sprawy narożne - chociaż nie chcę mówić, że jest to sprawa narożna, ponieważ jej nie rozumiem).
— Evan Carroll

Postawiłbym je na dwa osobne pytania, na które odpowiedziano sobie sam, ponieważ jestem pewien, że nie jestem jedynym, który zastanawia się, co count(x or null)robi. Z przyjemnością zadam oba pytania, jeśli wolisz.
— Evan Carroll


7

Możesz to zrobić jako zwykłe odejmowanie ROW_NUMBER()operacji (lub jeśli twoje daty nie są unikalne, choć nadal unikalne dla id_type, możesz użyć DENSE_RANK()zamiast tego, ale będzie to droższe zapytanie):

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
;

Zobacz tę pracę w DB Fiddle (lub zobacz wersję DENSE_RANK )

Wynik:

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

Logicznie rzecz biorąc, można myśleć o tym jako prosty DENSE_RANK()z PREORDER BY, to znaczy, chcesz DENSE_RANKwszystkich elementów, które są uporządkowane razem, i chcesz je uporządkowane według dat, wystarczy mieć do czynienia z nieznośnego problemu z faktu, że przy każdej zmianie daty DENSE_RANKbędzie się zwiększać. Robisz to, używając wyrażenia, jak pokazałem ci powyżej. Wyobraź sobie, że miałeś tę składnię: DENSE_RANK() OVER (PREORDER BY date, ORDER BY id_type)gdzie PREORDERwyklucza się ją z obliczeń rankingu i tylko się ORDER BYliczy.

Pamiętaj, że jest to ważne GROUP BYzarówno dla wygenerowanej Seqkolumny, jak i id_typekolumny. Seqsam w sobie NIE jest unikalny, mogą się nakładać - musisz również pogrupować według id_type.

Więcej informacji na ten temat:

Ten pierwszy link zawiera kod, którego możesz użyć, jeśli chcesz, aby data rozpoczęcia lub zakończenia była taka sama jak data zakończenia / rozpoczęcia poprzedniego lub następnego okresu (więc nie ma żadnych przerw). Oraz inne wersje, które mogą pomóc w zapytaniu. Chociaż muszą zostać przetłumaczone ze składni SQL Server ...


6

W Postgres 8.4 możesz użyć funkcji RECURSIVE .

Jak oni to robią

Funkcja rekurencyjna dodaje poziom do każdego innego typu id_typ, wybierając daty jeden po drugim w kolejności malejącej.

       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

Następnie użyj grupowania MAX (data), MIN (data) według poziomu, typ_ id, aby uzyskać pożądany wynik.

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    |
+---------------------+---------------------+---------+

Sprawdź to: http://rextester.com/WCOYFP6623


5

Oto kolejna metoda, podobna do Evana i Erwina, ponieważ wykorzystuje LGD do określania wysp. Różni się od tych rozwiązań tym, że wykorzystuje tylko jeden poziom zagnieżdżania, bez grupowania i znacznie więcej funkcji okna:

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
;

is_startKolumna obliczana w zagnieżdżonych znaczników wybrać początek każdej wyspie. Ponadto zagnieżdżony WYBÓR wyświetla poprzednią datę każdego wiersza i ostatnią datę zestawu danych.

W przypadku wierszy, które są początkami ich wysp, poprzednia data faktycznie jest datą końcową poprzedniej wyspy. Tak używa go główny SELECT. Wybiera tylko wiersze spełniające is_start = 1warunek i dla każdego zwróconego wiersza pokazuje własny wiersz datejako begini następny wiersz prev_datejako end. Ponieważ ostatni wiersz nie zawiera następnego wiersza, LEAD(prev_date)zwraca wartość null, dla której funkcja WSPÓŁCZYNNIKA zastępuje ostatnią datę zestawu danych.

Możesz grać z tym rozwiązaniem na dbfiddle .

Wprowadzając dodatkowe kolumny identyfikujące wyspy, prawdopodobnie będziesz chciał wprowadzić subkluzję PARTITION BY do klauzuli OVER każdej funkcji okna. Na przykład, jeśli chcesz wykryć wyspy w grupach zdefiniowanych przez a parent_id, powyższe zapytanie prawdopodobnie będzie musiało wyglądać następująco:

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
;

A jeśli zdecydujesz się na rozwiązanie Erwina lub Evana, uważam, że należy do niego dodać podobną zmianę.


5

Bardziej poza zainteresowaniem akademickim niż praktycznym rozwiązaniem, można to również osiągnąć za pomocą agregatu zdefiniowanego przez użytkownika . Podobnie jak inne rozwiązania, będzie to działać nawet na Postgres 8.4, ale jak skomentowali inni, zaktualizuj, jeśli możesz.

Agregacja obsługuje się nulltak, jakby była inna foo_type, więc ciągi zerowe otrzymałyby to samo grp- to może być lub nie być to, czego chcesz.

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
: -------------------- | : -------------------- | -------:
01.01.2017, 07:19:21 | 01.01.2017, 07:19:25 | 3)
01.01.2017, 07:19:26 | 01.01.2017, 07:19:26 | 5
10.01.2017, 07: 19: 27.1 | 10.01.2017, 07: 19: 27.1 | 3)
10.01.2017, 07:19:28 | 01.01.2017, 07:19:29 | 5
10.01.2017 07: 19: 30.1 | 10.01.2017 07: 19: 30.1 | 3)
01.01.2017, 07:19:31 | 01.01.2017, 07:19:31 | 5
01.01.2017, 07:19:32 | 01.01.2017, 07:19:32 | 3)
10.01.2017, 07: 19: 33.1 | 01.01.2017 07: 19: 37.1 | 5

dbfiddle tutaj


4

Można to zrobić, RECURSIVE CTEaby przekazać „czas rozpoczęcia” z jednego wiersza do drugiego i kilka dodatkowych (wygodnych) przygotowań.

To zapytanie zwraca pożądany wynik:

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") 
)

po przygotowaniu ... część rekurencyjna

, 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 ;

Możesz to sprawdzić na stronie http://rextester.com/POYM83542

Ta metoda nie jest dobrze skalowana. W przypadku tabeli z wierszami 8_641 zajmuje 7 sekund, w przypadku tabeli o dwukrotnie większym rozmiarze - 28 sekund. Kilka próbek pokazuje czasy wykonania wyglądające jak O (n ^ 2).

Metoda Evana Carrola zajmuje mniej niż 1s (tzn. Idź na całość!) I wygląda jak O (n). Zapytania rekurencyjne są absolutnie nieefektywne i powinny być traktowane jako ostateczność.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.