Diyelim ki (a,b,c,d)
aynı veri tipinde dört sütunlu bir tablomuz var .
Sütunlardaki verilerdeki tüm farklı değerleri seçmek ve bunları tek bir sütun olarak döndürmek mümkün mü yoksa bunu elde etmek için bir işlev mi oluşturmalıyım?
UNION
Diyelim ki (a,b,c,d)
aynı veri tipinde dört sütunlu bir tablomuz var .
Sütunlardaki verilerdeki tüm farklı değerleri seçmek ve bunları tek bir sütun olarak döndürmek mümkün mü yoksa bunu elde etmek için bir işlev mi oluşturmalıyım?
UNION
Yanıtlar:
Güncelleme: SQLfiddle'da 5 sorgunun tümünü 100K satırlarla (ve 2 ayrı durum, biri birkaç (25) farklı değerde, diğeri de lotlu (yaklaşık 25K değer) test etti .
Çok basit bir sorgu kullanmak olacaktır UNION DISTINCT
. Dört sütunun her birinde ayrı bir dizin olması en etkili olacağını düşünüyorum. Eğer Postgres'in sahip olmadığı Gevşek İndeks Taraması optimizasyonunu uygulamış olsaydı, dört sütunun her birinde ayrı bir endeks ile verimli olur . Bu nedenle, bu sorgu, tablonun 4 taramasını gerektirdiğinden etkili olmayacaktır (ve dizin kullanılmamaktadır):
-- 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 ;
Başka bir ilk önce UNION ALL
kullanmak ve sonra kullanmak olacaktır DISTINCT
. Bu aynı zamanda 4 masa taraması gerektirecektir (ve indeks kullanmaz). Değerler az olduğunda verim düşük değil ve daha fazla değere sahip (kapsamlı değil) testimde en hızlı olanı:
-- 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 ;
Diğer cevaplar, dizi işlevlerini veya LATERAL
sözdizimini kullanarak daha fazla seçenek sağlamıştır . Jack'in sorgusu ( 187 ms, 261 ms
) makul bir performansa sahip ancak AndriyM'in sorgusu daha verimli görünüyor ( 125 ms, 155 ms
). Her ikisi de tablonun bir sıralı taramasını yapar ve herhangi bir dizin kullanmaz.
Aslında Jack'in sorgu sonuçları yukarıda gösterilenden biraz daha iyidir (eğer kaldırırsak order by
) ve 4 tane içini çıkartıp distinct
sadece hariciini bırakarak daha da iyileştirilebilir .
Son olarak, eğer - ve sadece - eğer 4 sütununun belirgin değerleri göreceli olarak WITH RECURSIVE
azsa, yukarıdaki Gevşek İndeks Tarama sayfasında açıklanan hack / optimizasyonu kullanabilir ve 4 dizinin hepsini de oldukça hızlı bir şekilde kullanabilirsiniz! Aynı 100K satırları ve 4 sütuna yayılmış yaklaşık 25 farklı değerle test edilmiştir (sadece 2 ms'de çalışır!), 25K farklı değerlerle ise 368 ms'de en yavaş değer:
-- 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 ;
Özetlemek gerekirse, farklı değerler az olduğunda özyinelemeli sorgu mutlak kazanırken, birçok değere sahip olduğumda, ikinci olanım, Jack'in (aşağıdaki geliştirilmiş sürüm) ve AndriyM'in sorguları en iyi performans gösterenlerdir.
Ekstra belirgin işlemlere rağmen, ilk sorgudaki bir varyasyon olan geç eklemeler, orijinal 1’den çok daha iyi performans gösterirken,
-- 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 ;
ve Jack gelişmiş:
-- Query 4b. (104 ms, 128 ms)
select distinct unnest( array_agg(a)||
array_agg(b)||
array_agg(c)||
array_agg(d) )
from t ;
Bu sorguda olduğu gibi LATERAL kullanabilirsiniz :
SELECT DISTINCT
x.n
FROM
atable
CROSS JOIN LATERAL (
VALUES (a), (b), (c), (d)
) AS x (n)
;
LATERAL anahtar sözcüğü, birleşimin sağ tarafının sol taraftan başvuru nesnelerine izin verir. Bu durumda, sağ taraf, tek bir sütuna koymak istediğiniz sütun değerlerinden tek bir sütun altkümesi oluşturan bir VALUES yapıcısıdır. Ana sorgu basitçe DISTINCT de uygulayan yeni sütuna başvurur.
Açıkçası, ypercube'un önerdiğiunion
gibi kullanırdım , ancak dizilerle de mümkündür:
select distinct unnest( array_agg(distinct a)|| array_agg(distinct b)|| array_agg(distinct c)|| array_agg(distinct d) ) from t order by 1;
| unnest | | : ----- | | 0 | | 1 | | 2 | | 3 | | 5 | | 6 | | 8 | | 9 |
dbfiddle burada
SELECT DISTINCT n FROM observations, unnest(ARRAY[a,b,c,d]) n;
Andriy'nin fikrinin daha az ayrıntılı bir sürümü sadece biraz daha uzun, ama daha zarif ve daha hızlı.
İçin birçok farklı / birkaç yinelenen değerler:
SELECT DISTINCT n FROM observations, LATERAL (VALUES (a),(b),(c),(d)) t(n);
Her ilgili sütunda bir indeks ile!
İçin birkaç farklı / birçok yinelenen değerler:
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;
Bu, daha önce yayınlanan bir @ypercube'e benzeyen başka bir rCTE değişkenidir, ancak ORDER BY 1 LIMIT 1
bunun yerine kullanıyorum min(a)
, genellikle biraz daha hızlı. NULL değerleri dışlamak için ek bir yüklemeye de ihtiyacım yok.
Ve LATERAL
ilişkili bir alt sorgu yerine, çünkü daha temiz (zorunlu olarak daha hızlı değil).
Bu tekniğe vereceğim cevapta ayrıntılı açıklama:
Ypercube'un SQL Fiddle'ı güncelledim ve çalma listesine benimkileri ekledim.
EXPLAIN (ANALYZE, TIMING OFF)
iyi genel performansı doğrulamak için test edebilir misiniz ? (Önbellekleme etkilerini dışlamak için
VALUES ...
daha hızlı demek istiyorum unnest(ARRAY[...])
. LATERAL
Bu, FROM
listedeki set-return işlevlerinde gizlidir.
Yapabilirsin, ancak fonksiyonu yazıp test ederken yanlış hissettim. Bu bir kaynak israfıdır.
Lütfen bir sendika kullanın ve daha fazlasını seçin. Yalnızca avantaj (öyleyse), ana tablodan tek bir tarama.
Sql fiddle'da separator'ı $ ' dan başkasına çevirmelisiniz /
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 ;
istiyorsun