Giả sử chúng ta có một bảng có bốn cột (a,b,c,d)
cùng loại dữ liệu.
Có thể chọn tất cả các giá trị riêng biệt trong dữ liệu trong các cột và trả về chúng dưới dạng một cột hay tôi phải tạo một hàm để đạt được điều này?
UNION
Giả sử chúng ta có một bảng có bốn cột (a,b,c,d)
cùng loại dữ liệu.
Có thể chọn tất cả các giá trị riêng biệt trong dữ liệu trong các cột và trả về chúng dưới dạng một cột hay tôi phải tạo một hàm để đạt được điều này?
UNION
Câu trả lời:
Cập nhật: Đã kiểm tra tất cả 5 truy vấn trong SQLfiddle với 100K hàng (và 2 trường hợp riêng biệt, một có ít (25) giá trị khác biệt và một truy vấn khác có nhiều (khoảng 25K giá trị).
Một truy vấn rất đơn giản sẽ được sử dụng UNION DISTINCT
. Tôi nghĩ sẽ hiệu quả nhất nếu có một chỉ mục riêng biệt cho mỗi trong bốn cột Nó sẽ hiệu quả với một chỉ mục riêng trên mỗi bốn cột, nếu Postgres đã thực hiện tối ưu hóa Quét chỉ mục lỏng lẻo , điều này không có. Vì vậy, truy vấn này sẽ không hiệu quả vì nó yêu cầu 4 lần quét bảng (và không có chỉ mục nào được sử dụng):
-- 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 ;
Một cái khác sẽ là đầu tiên UNION ALL
và sau đó sử dụng DISTINCT
. Điều này cũng sẽ yêu cầu 4 lần quét bảng (và không sử dụng chỉ mục). Hiệu quả không tồi khi các giá trị còn ít và với nhiều giá trị trở thành nhanh nhất trong thử nghiệm (không bao quát) của tôi:
-- 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 ;
Các câu trả lời khác đã cung cấp nhiều tùy chọn hơn bằng cách sử dụng các hàm mảng hoặc LATERAL
cú pháp. Truy vấn của Jack ( 187 ms, 261 ms
) có hiệu suất hợp lý nhưng truy vấn của AndriyM có vẻ hiệu quả hơn ( 125 ms, 155 ms
). Cả hai đều thực hiện quét tuần tự bảng và không sử dụng bất kỳ chỉ mục nào.
Trên thực tế, kết quả truy vấn của Jack tốt hơn một chút so với hiển thị ở trên (nếu chúng tôi xóa order by
) và có thể được cải thiện hơn nữa bằng cách xóa 4 nội bộ distinct
và chỉ để lại bên ngoài.
Cuối cùng, nếu - và chỉ khi - các giá trị riêng biệt của 4 cột tương đối ít, bạn có thể sử dụng WITH RECURSIVE
hack / tối ưu hóa được mô tả trong trang Loose Index Scan ở trên và sử dụng cả 4 chỉ mục, với kết quả nhanh đáng kể! Đã thử nghiệm với cùng 100K hàng và khoảng 25 giá trị riêng biệt trải đều trên 4 cột (chỉ chạy trong 2 ms!) Trong khi với 25K giá trị khác biệt, nó chậm nhất với 368 ms:
-- 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 ;
Tóm lại, khi các giá trị riêng biệt còn ít, truy vấn đệ quy là người chiến thắng tuyệt đối trong khi với rất nhiều giá trị, lần thứ 2 của tôi, Jack (phiên bản cải tiến bên dưới) và truy vấn của AndriyM là những người thực hiện tốt nhất.
Bổ sung muộn, một biến thể của truy vấn thứ 1, mặc dù có các hoạt động khác biệt hơn, hoạt động tốt hơn nhiều so với truy vấn ban đầu và chỉ kém hơn một chút so với truy vấn thứ 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 ;
và Jack đã cải thiện:
-- Query 4b. (104 ms, 128 ms)
select distinct unnest( array_agg(a)||
array_agg(b)||
array_agg(c)||
array_agg(d) )
from t ;
Bạn có thể sử dụng LATITH, như trong truy vấn này :
SELECT DISTINCT
x.n
FROM
atable
CROSS JOIN LATERAL (
VALUES (a), (b), (c), (d)
) AS x (n)
;
Từ khóa LATITH cho phép phía bên phải của phép nối tham chiếu các đối tượng từ phía bên trái. Trong trường hợp này, phía bên phải là một hàm tạo GIÁ TRỊ xây dựng một tập hợp con một cột trong số các giá trị cột bạn muốn đặt vào một cột. Truy vấn chính chỉ cần tham chiếu cột mới, cũng áp dụng DISTINCT cho cột đó.
Để rõ ràng, tôi sẽ sử dụng union
như ypercube gợi ý , nhưng cũng có thể với các mảng:
select distinct unnest( array_agg(distinct a)|| array_agg(distinct b)|| array_agg(distinct c)|| array_agg(distinct d) ) from t order by 1;
| không tốt nhất | | : ----- | | 0 | | 1 | | 2 | | 3 | | 5 | | 6 | | 8 | | 9 |
dbfiddle ở đây
SELECT DISTINCT n FROM observations, unnest(ARRAY[a,b,c,d]) n;
Một phiên bản ít dài hơn của ý tưởng của Irina chỉ dài hơn một chút, nhưng thanh lịch hơn và nhanh hơn.
Đối với nhiều giá trị khác nhau / vài giá trị trùng lặp:
SELECT DISTINCT n FROM observations, LATERAL (VALUES (a),(b),(c),(d)) t(n);
Với một chỉ số trên mỗi cột liên quan!
Đối với một vài giá trị khác biệt / nhiều trùng lặp:
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;
Đây là một biến thể rCTE khác, tương tự như một @ypercube đã được đăng , nhưng tôi sử dụng ORDER BY 1 LIMIT 1
thay vì min(a)
thường nhanh hơn một chút. Tôi cũng không cần thêm biến vị ngữ để loại trừ các giá trị NULL.
Và LATERAL
thay vì một truy vấn con tương quan, bởi vì nó sạch hơn (không nhất thiết phải nhanh hơn).
Giải thích chi tiết trong câu trả lời của tôi cho kỹ thuật này:
Tôi đã cập nhật SQL Fiddle của ypercube và thêm tôi vào danh sách phát.
EXPLAIN (ANALYZE, TIMING OFF)
để xác minh hiệu suất tổng thể tốt nhất? (Tốt nhất trong số 5 để loại trừ các hiệu ứng bộ đệm.)
VALUES ...
nhanh hơn unnest(ARRAY[...])
. LATERAL
là ẩn cho các hàm trả về trong FROM
danh sách.
Bạn có thể, nhưng khi tôi viết và kiểm tra chức năng tôi cảm thấy sai. Đó là một sự lãng phí tài nguyên.
Chỉ cần vui lòng sử dụng một liên minh và lựa chọn nhiều hơn. Chỉ có lợi thế (nếu có), một lần quét từ bảng chính.
Trong fiddle sql, bạn cần thay đổi dấu phân cách từ $ sang thứ khác, như /
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 ;
sao?