สมมติว่าเรามีตารางที่มีสี่คอลัมน์(a,b,c,d)
ของชนิดข้อมูลเดียวกัน
เป็นไปได้หรือไม่ที่จะเลือกค่าที่แตกต่างทั้งหมดภายในข้อมูลในคอลัมน์และส่งกลับเป็นคอลัมน์เดียวหรือฉันต้องสร้างฟังก์ชันเพื่อให้ได้สิ่งนี้?
UNION
สมมติว่าเรามีตารางที่มีสี่คอลัมน์(a,b,c,d)
ของชนิดข้อมูลเดียวกัน
เป็นไปได้หรือไม่ที่จะเลือกค่าที่แตกต่างทั้งหมดภายในข้อมูลในคอลัมน์และส่งกลับเป็นคอลัมน์เดียวหรือฉันต้องสร้างฟังก์ชันเพื่อให้ได้สิ่งนี้?
UNION
คำตอบ:
อัปเดต:ทดสอบการค้นหาทั้งหมด 5 รายการในSQLfiddle ที่มีแถว 100K (และ 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
ไวยากรณ์ ข้อความค้นหาของแจ็ค ( 187 ms, 261 ms
) มีประสิทธิภาพที่สมเหตุสมผล แต่ข้อความค้นหาของ AndriyM นั้นมีประสิทธิภาพมากกว่า ( 125 ms, 155 ms
) ทั้งคู่ทำการสแกนตามลำดับหนึ่งตารางและไม่ใช้ดัชนีใด ๆ
ที่จริงแล้วผลการสืบค้นของแจ็คดีกว่าที่แสดงด้านบนเล็กน้อย (ถ้าเราลบออกorder by
) และสามารถปรับปรุงให้ดียิ่งขึ้นได้โดยการลบ 4 ภายในdistinct
และทิ้งเฉพาะภายนอกเท่านั้น
ท้ายที่สุดถ้า - และหาก - ค่าที่แตกต่างของคอลัมน์ 4 มีค่าค่อนข้างน้อยคุณสามารถใช้WITH RECURSIVE
แฮ็ค / การเพิ่มประสิทธิภาพที่อธิบายไว้ในหน้า Loose Index Scan ข้างต้นและใช้ดัชนีทั้ง 4 ด้วยผลลัพธ์ที่รวดเร็วอย่างน่าทึ่ง! ทดสอบด้วยแถว 100K ที่เหมือนกันและค่าที่แตกต่างกันประมาณ 25 ค่าแผ่กระจายไปทั่วทั้ง 4 คอลัมน์ (ทำงานใน 2 มิลลิวินาทีเท่านั้น!) ในขณะที่ค่า 25K แตกต่างกันนั้นช้าที่สุดด้วย 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 ;
เพื่อสรุปเมื่อค่าที่แตกต่างกันน้อยแบบสอบถามแบบเรียกซ้ำเป็นผู้ชนะแน่นอนในขณะที่มีจำนวนมากค่าที่สองของฉันแจ็ค (รุ่นที่ปรับปรุงด้านล่าง) และแบบสอบถามของ AndriyM เป็นนักแสดงที่ดีที่สุด
การเพิ่มเติมล่าช้าการเปลี่ยนแปลงในแบบสอบถามครั้งที่ 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 ;
คุณสามารถใช้ LATERAL เหมือนในแบบสอบถามนี้ :
SELECT DISTINCT
x.n
FROM
atable
CROSS JOIN LATERAL (
VALUES (a), (b), (c), (d)
) AS x (n)
;
คำหลัก LATERAL ช่วยให้ด้านขวาของการเข้าร่วมเพื่ออ้างอิงวัตถุจากด้านซ้าย ในกรณีนี้ทางด้านขวาคือตัวสร้างค่าที่สร้างชุดย่อยคอลัมน์เดี่ยวจากค่าคอลัมน์ที่คุณต้องการใส่ลงในคอลัมน์เดียว แบบสอบถามหลักเพียงแค่อ้างอิงคอลัมน์ใหม่นอกจากนี้ยังใช้ DISTINCT กับมัน
เพื่อความชัดเจนฉันจะใช้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
แทนที่จะเป็นคำถามย่อยที่สัมพันธ์กันเพราะมันสะอาดกว่า (ไม่จำเป็นต้องเร็วกว่า)
คำอธิบายโดยละเอียดในคำตอบของฉันสำหรับเทคนิคนี้:
ฉันอัปเดตSQL Fiddleของ ypercube และเพิ่มของฉันลงในเพลย์ลิสต์
EXPLAIN (ANALYZE, TIMING OFF)
เพื่อตรวจสอบประสิทธิภาพโดยรวมที่ดีที่สุดได้หรือไม่ (ดีที่สุด 5 อันดับเพื่อยกเว้นเอฟเฟ็กต์แคช)
VALUES ...
มีความหมายสำหรับฟังก์ชั่น set-return ในรายการ 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 ;
อะไร