假设我们有一个包含四列(a,b,c,d)
相同数据类型的表。
是否可以在列中的数据中选择所有不同的值,然后将它们作为单个列返回,还是必须创建一个函数来实现?
UNION
假设我们有一个包含四列(a,b,c,d)
相同数据类型的表。
是否可以在列中的数据中选择所有不同的值,然后将它们作为单个列返回,还是必须创建一个函数来实现?
UNION
Answers:
更新:测试了SQLfiddle中的所有5个查询,其中包含10万行(和2个单独的案例),其中一个具有很少(25)个不同的值,另一个具有很多(大约25K个值)。
一个非常简单的查询将是使用UNION DISTINCT
。我认为,如果四列中的每一列都有一个单独的索引,这将是最有效的。如果Postgres实施了松散索引扫描优化,那么四列中的每一列都有一个单独的索引将是有效的。因此,此查询将效率不高,因为它需要对表进行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
)。它们都对表进行一次顺序扫描,并且不使用任何索引。
实际上,杰克的查询结果比上面显示的要好一些(如果我们删除了order by
),可以通过删除内部的4个distinct
而仅保留外部的4个来进一步改善。
最后,如果- 并且仅当 -4列的不同值相对较少,您可以使用WITH RECURSIVE
上面的“松散索引扫描”页面中描述的hack /优化,并使用所有4个索引,从而获得非常快的结果!使用相同的100K行和大约25个不同的值在4列中分布进行测试(运行时间仅2毫秒!),而使用25K的不同值进行测试则是最慢的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 ;
总而言之,当不同的值很少时,递归查询绝对是赢家,而值很多,我的第二个查询(杰克(下面是改进版本)和AndriyM的查询)表现最佳。
后期添加是第一个查询的变体,尽管有一些额外的不同操作,但其性能要比原始的第一个好得多,但仅比第二个差一些:
-- 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;
这是另一个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 ;
?