複数の列でのSELECT DISTINCT


23

(a,b,c,d)同じデータ型の4つの列を持つテーブルがあるとします。

列内のデータ内のすべての個別の値を選択し、それらを単一の列として返すことは可能ですか、またはこれを達成するために関数を作成する必要がありますか?


7
という意味SELECT a FROM tablename UNION SELECT b FROM tablename UNION SELECT c FROM tablename UNION SELECT d FROM tablename ;ですか?
ypercubeᵀᴹ

はい。それで十分ですが、4つのクエリを実行する必要があります。パフォーマンスのボトルネックになりませんか?
ファブリツィオマッゾーニ

6
それは4つのクエリではなく、1つのクエリです。
ypercubeᵀᴹ15年

1
私はなど、使用可能な索引に応じて、異なる性能を有することがクエリを記述するためのいくつかの方法を見ることができます。しかし、私は、関数が役立つだろうかENVISIONすることはできません
ypercubeᵀᴹ

1
OK。一緒にUNION
やっ

回答:


24

更新: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 ;

SQLfiddle


要約すると、個別の値が少ない場合、再帰クエリが絶対的な勝者であり、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 ;

12

このクエリのように、LATERALを使用できます

SELECT DISTINCT
  x.n
FROM
  atable
  CROSS JOIN LATERAL (
    VALUES (a), (b), (c), (d)
  ) AS x (n)
;

LATERALキーワードを使用すると、結合の右側で左側からオブジェクトを参照できます。この場合、右側はVALUESコンストラクターであり、単一の列に配置する列値から単一列のサブセットを構築します。メインクエリは単に新しい列を参照し、それにDISTINCTも適用します。


10

明確にするためにunionypercubeが示唆するように使用しますが、配列でも可能です:

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


7

最短

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つのベスト。)
アーウィンブランドステッター

面白い。コンマ結合は、あらゆる点でCROSS JOINと同等だと思いました。つまり、パフォーマンスの面でも。LATERALの使用に固有の違いはありますか?
アンドリーM

または多分私は誤解した。私の提案のより冗長なバージョンについて「より速い」と言ったとき、あなたは私のものよりも速いか、またはネストなしのSELECT DISTINCTよりも速いことを意味しましたか?
アンドリーM

1
@AndriyM:コンマ同等です(結合シーケンスを解決するときに明示的な `CROSS JOIN`構文がより強くバインドすることを除いて)。はい、私はあなたの考えVALUES ...がより速いことを意味しますunnest(ARRAY[...])。リストLATERAL内の集合を返す関数に対しては暗黙的ですFROM
アーウィンブランドステッター

改善のためのThnx!order / limit-1バリアントを試しましたが、目立った違いはありませんでした。LATERALを使用すると、複数のIS NOT NULLチェックを回避でき、非常に優れています。Loose-Index-Scanページに追加されるこのバリアントをPostgresのユーザーに提案する必要があります。
ypercubeᵀᴹ

3

できますが、関数を書いてテストしたとき、私は間違っていると感じました。それは資源の無駄です。
ユニオンを使用して、さらに選択してください。唯一の利点(ある場合)、メインテーブルからの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();

関数はまだユニオンを使用するので、実際には正しいです。いずれにせよ、努力のために+1。
ファブリツィオマッゾーニ

2
なぜこの配列とカーソルのマジックをしているのですか?@ypercubeのソリューションが作業を行い、SQL言語関数にラップするのは非常に簡単です。
dezso

申し訳ありませんが、関数をコンパイルできませんでした。私はおそらくばかげたことをした。ここでうまく機能している場合は、リンクを提供してください。他の回答と比較できるように、回答を結果で更新します。
ypercubeᵀᴹ

@ypercube編集済みのソリューションが機能する必要があります。フィドルでセパレータを変更することを忘れないでください。テーブルを作成してローカルデータベースでテストし、正常に動作します。
user_0
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.