(a,b,c,d)
동일한 데이터 유형의 열 이 4 개인 테이블이 있다고 가정 합니다.
열의 데이터 내에서 모든 고유 값을 선택하여 단일 열로 반환 할 수 있습니까? 아니면 이것을 달성하기 위해 함수를 만들어야합니까?
UNION
(a,b,c,d)
동일한 데이터 유형의 열 이 4 개인 테이블이 있다고 가정 합니다.
열의 데이터 내에서 모든 고유 값을 선택하여 단일 열로 반환 할 수 있습니까? 아니면 이것을 달성하기 위해 함수를 만들어야합니까?
UNION
답변:
업데이트 : 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 ;
요약하면 구별 값이 적을 때 재귀 쿼리는 절대적인 승자이며 많은 값을 가진 내 두 번째 값은 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 ;
분명히하기 위해 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
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 개 중 최고)
VALUES ...
가보다 빠릅니다 unnest(ARRAY[...])
. 목록의 LATERAL
반환 함수에 암시 적입니다 FROM
.
할 수는 있지만 함수를 작성하고 테스트 할 때 잘못된 느낌이 들었습니다. 자원 낭비입니다.
조합과 더 많은 것을 선택하십시오. 기본 테이블에서 한 번만 스캔하면 유리합니다 (있는 경우).
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 ;
합니까?