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:
Za kilka punktów
tmp
który po prostu staje się mylący..0
)date
. Jeśli ma datę i godzinę, jest to znacznik czasu (i zapisz go jako jeden)Lepiej użyć funkcji okna.
Wyjścia
Wyjaśnienie
Najpierw potrzebujemy resetów. Generujemy je
lag()
Następnie liczymy, aby uzyskać grupy.
Następnie zawijany w podselekcji
GROUP BY
iORDER
wybierz min max (zakres)źródło
1. Funkcje okna i podkwerendy
Policz kroki, aby utworzyć grupy, podobne do pomysłu Evana , z modyfikacjami i poprawkami:
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 zdefiniowanyUNIQUE
, w przeciwnym razie musisz dodać krawatORDER BY
rozstrzygają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.Ponieważ jesteśmy zainteresowani tylko w rzeczywistej zmiany w
id_type
(TRUE
), to nie ma znaczenia w tym konkretnym przypadku.NULL
iFALSE
oba nie liczą się jakostep
.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:
Detale:
2. Odejmij dwie funkcje okna, jedną podkwerendę
Podobne do pomysłu Erika z modyfikacjami:
Jeśli
date
jest zdefiniowaneUNIQUE
, 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 nie
date
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.
Połączenie:
Testuj z:
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.
źródło
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).count(x or null)
robi. Z przyjemnością zadam oba pytania, jeśli wolisz.count(x or null)
potrzebne są luki i wyspy?Możesz to zrobić jako zwykłe odejmowanie
ROW_NUMBER()
operacji (lub jeśli twoje daty nie są unikalne, choć nadal unikalne dlaid_type
, możesz użyćDENSE_RANK()
zamiast tego, ale będzie to droższe zapytanie):Zobacz tę pracę w DB Fiddle (lub zobacz wersję DENSE_RANK )
Wynik:
Logicznie rzecz biorąc, można myśleć o tym jako prosty
DENSE_RANK()
zPREORDER BY
, to znaczy, chceszDENSE_RANK
wszystkich 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 datyDENSE_RANK
bę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)
gdziePREORDER
wyklucza się ją z obliczeń rankingu i tylko sięORDER BY
liczy.Pamiętaj, że jest to ważne
GROUP BY
zarówno dla wygenerowanejSeq
kolumny, jak iid_type
kolumny.Seq
sam w sobie NIE jest unikalny, mogą się nakładać - musisz również pogrupować wedługid_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 ...
źródło
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.
Następnie użyj grupowania MAX (data), MIN (data) według poziomu, typ_ id, aby uzyskać pożądany wynik.
Sprawdź to: http://rextester.com/WCOYFP6623
źródło
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:
is_start
Kolumna 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 = 1
warunek i dla każdego zwróconego wiersza pokazuje własny wierszdate
jakobegin
i następny wierszprev_date
jakoend
. 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:A jeśli zdecydujesz się na rozwiązanie Erwina lub Evana, uważam, że należy do niego dodać podobną zmianę.
źródło
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ę
null
tak, jakby była innafoo_type
, więc ciągi zerowe otrzymałyby to samogrp
- to może być lub nie być to, czego chcesz.dbfiddle tutaj
źródło
Można to zrobić,
RECURSIVE CTE
aby przekazać „czas rozpoczęcia” z jednego wiersza do drugiego i kilka dodatkowych (wygodnych) przygotowań.To zapytanie zwraca pożądany wynik:
po przygotowaniu ... część rekurencyjna
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ść.
źródło