(a,b,c,d)
同じデータ型の4つの列を持つテーブルがあるとします。
列内のデータ内のすべての個別の値を選択し、それらを単一の列として返すことは可能ですか、またはこれを達成するために関数を作成する必要がありますか?
UNION
(a,b,c,d)
同じデータ型の4つの列を持つテーブルがあるとします。
列内のデータ内のすべての個別の値を選択し、それらを単一の列として返すことは可能ですか、またはこれを達成するために関数を作成する必要がありますか?
UNION
回答:
更新:SQLfiddleで5つのクエリすべてを10 万行でテストしました(2つの別個のケース、1つは少数(25)の異なる値、もう1つはロット(約25Kの値))。
非常に単純なクエリはを使用することUNION DISTINCT
です。4つの列のそれぞれに個別のインデックスがある場合、最も効率的だと思います。PostgresがLoose Index Scan最適化を実装していた場合、4つの列のそれぞれに個別のインデックスがあると効率的です。したがって、このクエリはテーブルの4回のスキャンを必要とするため、効率的ではありません(インデックスは使用されません)。
-- 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 ;
もう1つは、最初UNION ALL
に使用することDISTINCT
です。また、これには4つのテーブルスキャンが必要です(インデックスを使用しません)。値が少ない場合、効率は悪くなく、値が大きいほど、私の(広範囲ではない)テストで最速になります。
-- 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 ;
他の回答には、配列関数またはLATERAL
構文を使用したより多くのオプションが用意されています。ジャックのクエリ(187 ms, 261 ms
)のパフォーマンスは妥当ですが、AndriyMのクエリの方が効率的です(125 ms, 155 ms
)。どちらもテーブルの1回の順次スキャンを実行し、インデックスを使用しません。
実際、Jackのクエリ結果は上に示したものより少し良く(を削除した場合order by
)、内部4を削除しdistinct
て外部1のみを残すことでさらに改善できます。
最後に、4列の個別の値が比較的少ない場合にのみWITH RECURSIVE
、上記のLoose Index Scanページで説明したハック/最適化を使用し、4つのインデックスすべてを使用して、非常に高速な結果を得ることができます!同じ10万行でテストし、約25の異なる値を4列に分散(2ミリ秒で実行!)しますが、25万の異なる値では368ミリ秒で最も遅いです:
-- 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 ;
要約すると、個別の値が少ない場合、再帰クエリが絶対的な勝者であり、2番目の値、Jackのクエリ(以下の改良バージョン)、およびAndriyMのクエリが最高のパフォーマンスを発揮します。
後期の追加、1番目のクエリのバリエーションで、余分な個別の操作にもかかわらず、元の1番目のクエリよりはるかに優れており、2番目のクエリよりもわずかに悪いだけです。
-- 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 ;
ジャックの改善:
-- Query 4b. (104 ms, 128 ms)
select distinct unnest( array_agg(a)||
array_agg(b)||
array_agg(c)||
array_agg(d) )
from t ;
明確にするためにunion
、ypercubeが示唆するように使用しますが、配列でも可能です:
select distinct unnest( array_agg(distinct a)|| array_agg(distinct b)|| array_agg(distinct c)|| array_agg(distinct d) ) from t order by 1;
| ネスト解除| | :----- | | 0 | | 1 | | 2 | | 3 | | 5 | | 6 | | 8 | | 9 |
ここに dbfiddle
SELECT DISTINCT n FROM observations, unnest(ARRAY[a,b,c,d]) n;
Andriyのアイデアのより冗長なバージョンは、わずかに長いだけですが、よりエレガントで高速です。
以下のために多くの異なった/ 数の重複値:
SELECT DISTINCT n FROM observations, LATERAL (VALUES (a),(b),(c),(d)) t(n);
関係する各列にインデックスを付けます!
以下のためのいくつかの明確な/ 多くの重複値:
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;
これは、すでに投稿された@ypercubeに似た別のrCTEバリアントですが、ORDER BY 1 LIMIT 1
代わりに使用しますmin(a)
が、通常は少し高速です。NULL値を除外するための追加の述語も必要ありません。
またLATERAL
、相関サブクエリの代わりに、クリーンである(必ずしも高速ではない)ため。
このテクニックの私の答えの詳細な説明:
ypercubeのSQL Fiddleを更新し、プレイリストに追加しました。
EXPLAIN (ANALYZE, TIMING OFF)
最高の全体的なパフォーマンスを検証するためにテストできますか?(キャッシュ効果を除外するための5つのベスト。)
VALUES ...
がより速いことを意味しますunnest(ARRAY[...])
。リストLATERAL
内の集合を返す関数に対しては暗黙的ですFROM
。
できますが、関数を書いてテストしたとき、私は間違っていると感じました。それは資源の無駄です。
ユニオンを使用して、さらに選択してください。唯一の利点(ある場合)、メインテーブルからの1回のスキャン。
SQLフィドルでは、セパレータを$から/のような他の何かに変更する必要があります
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 ;
ですか?