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.