여러 열에서 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 개의 쿼리를 실행해야합니다. 성능 병목 현상이 아닌가?
Fabrizio Mazzoni

6
즉 하나 개의 쿼리가 아닌 4의
ypercubeᵀᴹ

1
나는 다른 성능을 가질 수있는 쿼리를 작성하는 방법은 여러 가지를 볼 등 가능한 인덱스에 따라하지만 함수가 도움이 될 방법을 구상 할 수는 없습니다
ypercubeᵀᴹ

1
승인. 로 한번 풀어주기UNION
파브리 지오 오신 마조니

답변:


24

업데이트 : 100K 행이 있는 SQLfiddle 에서 5 개의 쿼리 (및 별개의 값이 거의없는 (25)와 로트 (약 25K 값)가있는 2 개의 별도 사례)를 모두 테스트했습니다 .

매우 간단한 쿼리를 사용하는 것 UNION DISTINCT입니다. 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 ;

다른 하나는 먼저 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구문을 사용하여 더 많은 옵션을 제공했습니다 . Jack의 쿼리 ( 187 ms, 261 ms)는 적절한 성능을 제공하지만 AndriyM의 쿼리는 더 효율적인 것 같습니다 ( 125 ms, 155 ms). 둘 다 테이블의 순차적 스캔을 수행하며 인덱스를 사용하지 않습니다.

실제로 Jack의 쿼리 결과는 위에 표시된 것보다 약간 낫습니다 (을 제거하면 order by) 4 내부를 제거 distinct하고 외부 만 남겨두면 더 향상 될 수 있습니다 .


마지막으로, 4 개의 열의 고유 값이 상대적으로 적은 경우에만WITH RECURSIVE 위의 느슨한 인덱스 스캔 페이지에 설명 된 해킹 / 최적화를 사용하고 4 개의 인덱스를 모두 사용하여 매우 빠른 결과를 얻을 수 있습니다! 동일한 100K 행으로 테스트되었으며 약 25 개의 고유 값이 4 개의 열에 분산되어 있으며 (2ms 만 실행 됨) 25K 고유 값은 368ms에서 가장 느립니다.

-- 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


요약하면 구별 값이 적을 때 재귀 쿼리는 절대적인 승자이며 많은 값을 가진 내 두 번째 값은 Jack (아래 개선 된 버전)과 AndriyM의 쿼리가 가장 우수한 성능을 나타냅니다.


추가 추가 작업에도 불구하고 첫 번째 쿼리의 변형 인 늦은 추가 기능은 원래 1st보다 훨씬 우수하고 2nd보다 약간 나쁩니다.

-- 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

분명히하기 위해 ypercube가 제안한union 대로 사용 하지만 배열에서도 가능합니다.

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;

이것은 하나의 유사 다른 rCTE 변형이며, 이미 게시 @ypercube ,하지만 난 사용 ORDER BY 1 LIMIT 1대신 min(a)하는 일반적으로 약간 빠릅니다. 또한 NULL 값을 제외하기 위해 추가 술어가 필요하지 않습니다.
그리고 LATERAL더 명확하지 않기 때문에 상관 하위 쿼리 대신에 더 빠릅니다.

이 기술에 대한 내 대답에 대한 자세한 설명 :

ypercube의 SQL Fiddle을 업데이트 하고 재생 목록에 내 것을 추가했습니다.


EXPLAIN (ANALYZE, TIMING OFF)전체적으로 최상의 성능을 확인하기 위해 테스트 할 수 있습니까 ? (캐싱 효과를 배제하기 위해 5 개 중 최고)
Erwin Brandstetter

흥미 롭군 나는 콤마 조인이 모든면에서, 즉 성능 측면에서 CROSS JOIN과 동등한 것이라고 생각했습니다. LATERAL 사용과의 차이점이 있습니까?
Andriy M

아니면 내가 오해했을 수도 있습니다. 내 제안의 덜 장황한 버전에 대해 "빠르게"라고 말했을 때, 내 것보다 빠르거나 SELECT DISTINCT보다 더 빠른 것을 의미하지 않았습니까?
Andriy M

1
@AndriyM : 쉼표 동일합니다 (결합 시퀀스를 해석 할 때 명시적인`CROSS JOIN` 구문이 더 강력하게 바인딩되는 것을 제외하고). 네, 당신의 아이디어 VALUES ...가보다 빠릅니다 unnest(ARRAY[...]). 목록의 LATERAL반환 함수에 암시 적입니다 FROM.
Erwin Brandstetter

개선을위한 Thnx! order / limit-1 변형을 시도했지만 눈에 띄는 차이는 없었습니다. LATERAL을 사용하면 여러 IS NOT NULL 검사를 피하는 것이 멋지다. Loose-Index-Scan 페이지에서이 변형을 Postgres 사용자에게 제안해야합니다.
ypercubeᵀᴹ

3

할 수는 있지만 함수를 작성하고 테스트 할 때 잘못된 느낌이 들었습니다. 자원 낭비입니다.
조합과 더 많은 것을 선택하십시오. 기본 테이블에서 한 번만 스캔하면 유리합니다 (있는 경우).

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.
Fabrizio Mazzoni 2016 년

2
왜이 배열과 커서 마술을하고 있습니까? @ypercube의 솔루션은 작업을 수행하며 SQL 언어 함수로 래핑하는 것은 매우 쉽습니다.
dezso

죄송합니다, 귀하의 함수를 컴파일 할 수 없습니다. 아마 어리석은 일을했을 것입니다. 여기 에서 작동 하게하는 경우 링크를 제공해 주시면 답변을 결과로 업데이트하여 다른 답변과 비교할 수 있습니다.
ypercubeᵀᴹ

@ypercube 편집 된 솔루션이 작동해야합니다. 바이올린에서 구분 기호를 변경해야합니다. 테이블 생성을 통해 로컬 DB를 테스트했으며 정상적으로 작동합니다.
user_0
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.