Załóżmy, że mamy tabelę z czterema kolumnami (a,b,c,d)
tego samego typu danych.
Czy można wybrać wszystkie odrębne wartości w danych w kolumnach i zwrócić je jako pojedynczą kolumnę, czy muszę utworzyć funkcję, aby to osiągnąć?
UNION
Załóżmy, że mamy tabelę z czterema kolumnami (a,b,c,d)
tego samego typu danych.
Czy można wybrać wszystkie odrębne wartości w danych w kolumnach i zwrócić je jako pojedynczą kolumnę, czy muszę utworzyć funkcję, aby to osiągnąć?
UNION
Odpowiedzi:
Aktualizacja: Przetestowano wszystkie 5 zapytań w SQLfiddle ze 100 000 wierszy (i 2 oddzielnymi przypadkami, jedno z kilkoma (25) odrębnymi wartościami i drugie z partiami (około 25 000 wartości).
Można użyć bardzo prostego zapytania UNION DISTINCT
. Myślę, że byłoby najbardziej wydajne, gdyby istniał osobny indeks w każdej z czterech kolumn. Byłoby wydajne z osobnym indeksem w każdej z czterech kolumn, gdyby Postgres zaimplementował optymalizację Loose Index Scan , czego nie ma. To zapytanie nie będzie wydajne, ponieważ wymaga 4 skanów tabeli (i nie jest używany indeks):
-- Query 1. (334 ms, 368ms)
SELECT a AS abcd FROM tablename
UNION -- means UNION DISTINCT
SELECT b FROM tablename
UNION
SELECT c FROM tablename
UNION
SELECT d FROM tablename ;
Innym byłoby najpierw, UNION ALL
a następnie użyć DISTINCT
. Będzie to również wymagać 4 skanów tabel (i bez użycia indeksów). Niezła wydajność, gdy wartości są nieliczne, a przy większej wartości staje się najszybsza w moim (nie obszernym) teście:
-- Query 2. (87 ms, 117 ms)
SELECT DISTINCT a AS abcd
FROM
( SELECT a FROM tablename
UNION ALL
SELECT b FROM tablename
UNION ALL
SELECT c FROM tablename
UNION ALL
SELECT d FROM tablename
) AS x ;
Inne odpowiedzi zawierają więcej opcji przy użyciu funkcji tablicowych lub LATERAL
składni. Zapytanie Jacka ( 187 ms, 261 ms
) ma rozsądną wydajność, ale zapytanie AndriyM wydaje się bardziej wydajne ( 125 ms, 155 ms
). Obaj wykonują jeden sekwencyjny skan tabeli i nie używają żadnego indeksu.
W rzeczywistości wyniki zapytania Jacka są nieco lepsze niż pokazano powyżej (jeśli usuniemy order by
) i można je ulepszyć, usuwając 4 wewnętrzne distinct
i pozostawiając tylko zewnętrzne.
Wreszcie, jeśli - i tylko jeśli - odrębne wartości 4 kolumn są względnie nieliczne, możesz użyć WITH RECURSIVE
hack / optymalizacji opisanej na powyższej stronie Loose Index Scan i użyć wszystkich 4 indeksów, z wyjątkowo szybkim wynikiem! Testowany z tymi samymi 100 000 wierszami i około 25 odrębnymi wartościami rozłożonymi na 4 kolumny (działa tylko 2 ms!), Natomiast z 25 000 odrębnymi wartościami jest najwolniejszy z 368 ms:
-- Query 3. (2 ms, 368ms)
WITH RECURSIVE
da AS (
SELECT min(a) AS n FROM observations
UNION ALL
SELECT (SELECT min(a) FROM observations
WHERE a > s.n)
FROM da AS s WHERE s.n IS NOT NULL ),
db AS (
SELECT min(b) AS n FROM observations
UNION ALL
SELECT (SELECT min(b) FROM observations
WHERE b > s.n)
FROM db AS s WHERE s.n IS NOT NULL ),
dc AS (
SELECT min(c) AS n FROM observations
UNION ALL
SELECT (SELECT min(c) FROM observations
WHERE c > s.n)
FROM dc AS s WHERE s.n IS NOT NULL ),
dd AS (
SELECT min(d) AS n FROM observations
UNION ALL
SELECT (SELECT min(d) FROM observations
WHERE d > s.n)
FROM db AS s WHERE s.n IS NOT NULL )
SELECT n
FROM
( TABLE da UNION
TABLE db UNION
TABLE dc UNION
TABLE dd
) AS x
WHERE n IS NOT NULL ;
Podsumowując, gdy odrębnych wartości jest niewiele, zapytanie rekurencyjne jest absolutnym zwycięzcą, podczas gdy z dużą ilością wartości, moja druga, Jack (poprawiona wersja poniżej) i zapytania AndriyM są najlepsze.
Późne dodawanie, odmiana pierwszego zapytania, które pomimo bardzo wyraźnych operacji, działa znacznie lepiej niż pierwotne pierwsze i tylko nieznacznie gorsze niż drugie:
-- Query 1b. (85 ms, 149 ms)
SELECT DISTINCT a AS n FROM observations
UNION
SELECT DISTINCT b FROM observations
UNION
SELECT DISTINCT c FROM observations
UNION
SELECT DISTINCT d FROM observations ;
a Jack poprawił:
-- Query 4b. (104 ms, 128 ms)
select distinct unnest( array_agg(a)||
array_agg(b)||
array_agg(c)||
array_agg(d) )
from t ;
Możesz użyć LATERAL, tak jak w tym zapytaniu :
SELECT DISTINCT
x.n
FROM
atable
CROSS JOIN LATERAL (
VALUES (a), (b), (c), (d)
) AS x (n)
;
Słowo kluczowe LATERAL pozwala prawej stronie złączenia odwoływać się do obiektów z lewej strony. W tym przypadku po prawej stronie znajduje się konstruktor VALUES, który buduje podzbiór jednokolumnowy z wartości kolumn, które chcesz umieścić w jednej kolumnie. Główne zapytanie po prostu odwołuje się do nowej kolumny, również stosując do niej DISTINCT.
Dla jasności użyłbym,union
jak sugeruje ypercube , ale jest to również możliwe w przypadku tablic:
select distinct unnest( array_agg(distinct a)|| array_agg(distinct b)|| array_agg(distinct c)|| array_agg(distinct d) ) from t order by 1;
| niespokojny | | : ----- | | 0 | | 1 | | 2 | | 3 | | 5 | | 6 | | 8 | | 9 |
dbfiddle tutaj
SELECT DISTINCT n FROM observations, unnest(ARRAY[a,b,c,d]) n;
Mniej szczegółowa wersja pomysłu Andrija jest tylko nieco dłuższa, ale bardziej elegancka i szybsza.
W przypadku wielu różnych / kilku zduplikowanych wartości:
SELECT DISTINCT n FROM observations, LATERAL (VALUES (a),(b),(c),(d)) t(n);
Z indeksem w każdej zaangażowanej kolumnie!
W przypadku kilku różnych / wielu zduplikowanych wartości:
WITH RECURSIVE
ta AS (
(SELECT a FROM observations ORDER BY a LIMIT 1) -- parentheses required!
UNION ALL
SELECT o.a FROM ta t
, LATERAL (SELECT a FROM observations WHERE a > t.a ORDER BY a LIMIT 1) o
)
, tb AS (
(SELECT b FROM observations ORDER BY b LIMIT 1)
UNION ALL
SELECT o.b FROM tb t
, LATERAL (SELECT b FROM observations WHERE b > t.b ORDER BY b LIMIT 1) o
)
, tc AS (
(SELECT c FROM observations ORDER BY c LIMIT 1)
UNION ALL
SELECT o.c FROM tc t
, LATERAL (SELECT c FROM observations WHERE c > t.c ORDER BY c LIMIT 1) o
)
, td AS (
(SELECT d FROM observations ORDER BY d LIMIT 1)
UNION ALL
SELECT o.d FROM td t
, LATERAL (SELECT d FROM observations WHERE d > t.d ORDER BY d LIMIT 1) o
)
SELECT a
FROM (
TABLE ta
UNION TABLE tb
UNION TABLE tc
UNION TABLE td
) sub;
To kolejny wariant rCTE, podobny do już opublikowanego @ypercube , ale używam go ORDER BY 1 LIMIT 1
zamiast tego, min(a)
który jest zwykle nieco szybszy. Nie potrzebuję też żadnych dodatkowych predykatów, aby wykluczyć wartości NULL.
I LATERAL
zamiast skorelowanego podkwerendy, ponieważ jest on czystszy (niekoniecznie szybszy).
Szczegółowe wyjaśnienie w mojej odpowiedzi na tę technikę:
Zaktualizowałem SQL Fiddle w ypercube i dodałem mój do listy odtwarzania.
EXPLAIN (ANALYZE, TIMING OFF)
aby sprawdzić najlepszą ogólną wydajność? (Najlepsze z 5, aby wykluczyć efekty buforowania.)
VALUES ...
jest szybszy niż unnest(ARRAY[...])
. LATERAL
jest niejawny dla funkcji zwracających zestaw na FROM
liście.
Możesz, ale kiedy napisałem i przetestowałem funkcję, poczułem się źle. To marnotrawstwo zasobów.
Po prostu skorzystaj ze związku i wybierz więcej. Jedyna zaleta (jeśli jest), jedno skanowanie z głównej tabeli.
W sql fiddle musisz zmienić separator z $ na coś innego, takiego jak /
CREATE TABLE observations (
id serial
, a int not null
, b int not null
, c int not null
, d int not null
, created_at timestamp
, foo text
);
INSERT INTO observations (a, b, c, d, created_at, foo)
SELECT (random() * 20)::int AS a -- few values for a,b,c,d
, (15 + random() * 10)::int
, (10 + random() * 10)::int
, ( 5 + random() * 20)::int
, '2014-01-01 0:0'::timestamp
+ interval '1s' * g AS created_at -- ascending (probably like in real life)
, 'aöguihaophgaduigha' || g AS foo -- random ballast
FROM generate_series (1, 10) g; -- 10k rows
CREATE INDEX observations_a_idx ON observations (a);
CREATE INDEX observations_b_idx ON observations (b);
CREATE INDEX observations_c_idx ON observations (c);
CREATE INDEX observations_d_idx ON observations (d);
CREATE OR REPLACE FUNCTION fn_readuniqu()
RETURNS SETOF text AS $$
DECLARE
a_array text[];
b_array text[];
c_array text[];
d_array text[];
r text;
BEGIN
SELECT INTO a_array, b_array, c_array, d_array array_agg(a), array_agg(b), array_agg(c), array_agg(d)
FROM observations;
FOR r IN
SELECT DISTINCT x
FROM
(
SELECT unnest(a_array) AS x
UNION
SELECT unnest(b_array) AS x
UNION
SELECT unnest(c_array) AS x
UNION
SELECT unnest(d_array) AS x
) AS a
LOOP
RETURN NEXT r;
END LOOP;
END;
$$
LANGUAGE plpgsql STABLE
COST 100
ROWS 1000;
SELECT * FROM fn_readuniqu();
SELECT a FROM tablename UNION SELECT b FROM tablename UNION SELECT c FROM tablename UNION SELECT d FROM tablename ;
?