ฉันกำลังสมมติประเภทข้อมูลtext
สำหรับคอลัมน์ที่เกี่ยวข้อง
CREATE TABLE prefix (code text, name text, price int);
CREATE TABLE num (number text, time int);
โซลูชัน "แบบง่าย"
SELECT DISTINCT ON (1)
n.number, p.code
FROM num n
JOIN prefix p ON right(n.number, -1) LIKE (p.code || '%')
ORDER BY n.number, p.code DESC;
องค์ประกอบสำคัญ:
DISTINCT ON
เป็นส่วนขยายของ Postgres DISTINCT
มาตรฐานของ หาคำอธิบายรายละเอียดสำหรับเทคนิคที่ใช้ในแบบสอบถามนี้คำตอบที่เกี่ยวข้องในดังนั้น
ORDER BY p.code DESC
เลือกการจับคู่ที่ยาวที่สุดเพราะ'1234'
เรียงลำดับหลัง'123'
(เรียงตามลำดับจากมากไปหาน้อย)
ง่ายซอ SQL
โดยไม่ต้องดัชนีแบบสอบถามจะวิ่งไปหามากเวลานาน (ไม่รอที่จะเห็นมันจบ) เพื่อให้เร็วขึ้นคุณต้องมีการสนับสนุนดัชนี ดัชนี trigram ที่คุณพูดถึงซึ่งจัดหาโดยโมดูลเพิ่มเติมpg_trgm
นั้นเป็นตัวเลือกที่ดี คุณต้องเลือกระหว่างดัชนี GIN และ GiST ตัวอักษรตัวแรกของตัวเลขเป็นเพียงเสียงรบกวนและสามารถแยกออกจากดัชนีได้ทำให้เป็นดัชนีการทำงานเพิ่มเติม
ในการทดสอบของฉันดัชนี GIN ของ trigram ที่ใช้งานได้ชนะการแข่งขันมากกว่าดัชนี GiST Trigram (ตามที่คาดไว้):
CREATE INDEX num_trgm_gin_idx ON num USING gin (right(number, -1) gin_trgm_ops);
ขั้นสูงdbfiddleที่นี่
ผลการทดสอบทั้งหมดมาจากการติดตั้งแบบทดสอบท้องถิ่น Postgres 9.1 พร้อมการตั้งค่าที่ลดลง: หมายเลข 17k และรหัส 2k:
- รันไทม์ทั้งหมด: 1719.552 ms (trigram GiST)
- รันไทม์ทั้งหมด: 912.329 ms (trigram GIN)
เร็วขึ้นมาก
ไม่สามารถลองด้วย text_pattern_ops
เมื่อเราเพิกเฉยต่อตัวอักษรแรกที่ทำให้เสียสมาธิมันจะเข้าสู่รูปแบบพื้นฐานที่จับคู่ซ้าย ดังนั้นฉันจึงลองใช้ดัชนีต้นไม้แบบ B ที่ใช้งานได้กับ คลาสโอเปอเรเตอร์text_pattern_ops
(สมมติว่าเป็นประเภทคอลัมน์text
)
CREATE INDEX num_text_pattern_idx ON num(right(number, -1) text_pattern_ops);
วิธีนี้ใช้งานได้อย่างยอดเยี่ยมสำหรับการค้นหาโดยตรงด้วยคำค้นหาเดียวและทำให้ดัชนี trigram ดูไม่ดีเมื่อเปรียบเทียบ:
SELECT * FROM num WHERE right(number, -1) LIKE '2345%'
- จำนวนรันไทม์ทั้งหมด: 3.816 ms (trgm_gin_idx)
- จำนวนรันไทม์ทั้งหมด: 0.147 ms (text_pattern_idx)
อย่างไรก็ตามตัววางแผนคิวรีจะไม่พิจารณาดัชนีนี้สำหรับการเข้าร่วมสองตาราง ฉันเคยเห็นข้อ จำกัด นี้มาก่อน ฉันยังไม่มีคำอธิบายที่มีความหมายสำหรับสิ่งนี้
ดัชนี B-tree บางส่วน / การทำงาน
ทางเลือกที่จะใช้การตรวจสอบความเท่าเทียมกันในสตริงบางส่วนกับดัชนีบางส่วน นี้สามารถJOIN
นำมาใช้ใน
เนื่องจากโดยทั่วไปเรามีจำนวน จำกัดdifferent lengths
สำหรับคำนำหน้าเท่านั้นเราจึงสามารถสร้างโซลูชันที่คล้ายกับที่นำเสนอที่นี่ด้วยดัชนีบางส่วน
สมมติว่าเรามีคำนำหน้าตั้งแต่1ถึง5ตัวอักษร สร้างดัชนีการทำงานบางส่วนจำนวนหนึ่งสำหรับทุกความยาวของคำนำหน้า:
CREATE INDEX prefix_code_idx5 ON prefix(code) WHERE length(code) = 5;
CREATE INDEX prefix_code_idx4 ON prefix(code) WHERE length(code) = 4;
CREATE INDEX prefix_code_idx3 ON prefix(code) WHERE length(code) = 3;
CREATE INDEX prefix_code_idx2 ON prefix(code) WHERE length(code) = 2;
CREATE INDEX prefix_code_idx1 ON prefix(code) WHERE length(code) = 1;
เนื่องจากสิ่งเหล่านี้เป็นดัชนีบางส่วนพวกเขาทั้งหมดจึงมีขนาดใหญ่กว่าดัชนีสมบูรณ์แบบเดียว
เพิ่มดัชนีที่ตรงกันสำหรับตัวเลข (คำนึงถึงตัวอักษรนำเสียงเข้าบัญชี):
CREATE INDEX num_number_idx5 ON num(substring(number, 2, 5)) WHERE length(number) >= 6;
CREATE INDEX num_number_idx4 ON num(substring(number, 2, 4)) WHERE length(number) >= 5;
CREATE INDEX num_number_idx3 ON num(substring(number, 2, 3)) WHERE length(number) >= 4;
CREATE INDEX num_number_idx2 ON num(substring(number, 2, 2)) WHERE length(number) >= 3;
CREATE INDEX num_number_idx1 ON num(substring(number, 2, 1)) WHERE length(number) >= 2;
ในขณะที่ดัชนีเหล่านี้เก็บซับสตริงแต่ละอันเท่านั้นและเป็นบางส่วนแต่ละส่วนครอบคลุมตารางส่วนใหญ่หรือทั้งหมด ดังนั้นพวกมันจึงมีขนาดใหญ่กว่าดัชนีรวมทั้งหมดยกเว้นจำนวนยาว และพวกเขากำหนดงานเพิ่มเติมสำหรับการเขียน นั่นคือค่าใช้จ่ายสำหรับความเร็วที่น่าอัศจรรย์
หากค่าใช้จ่ายนั้นสูงเกินไปสำหรับคุณ (ประสิทธิภาพการเขียนมีความสำคัญ / มีปัญหาในการเขียน / เนื้อที่ดิสก์มากเกินไป) คุณสามารถข้ามดัชนีเหล่านี้ได้ ที่เหลือก็ยังเร็วกว่านี้ถ้าไม่เร็วอย่างที่ควรจะเป็น ...
หากตัวเลขไม่สั้นลงดังนั้นn
ให้วางWHERE
คำสั่งที่ซ้ำซ้อนจากบางส่วนหรือทั้งหมดและปล่อยWHERE
ประโยคที่เกี่ยวข้องจากแบบสอบถามต่อไปนี้ทั้งหมด
CTE แบบเรียกซ้ำ
ด้วยการตั้งค่าทั้งหมดจนถึงตอนนี้ฉันหวังว่าโซลูชันที่หรูหรามากพร้อมCTE แบบเรียกซ้ำ :
WITH RECURSIVE cte AS (
SELECT n.number, p.code, 4 AS len
FROM num n
LEFT JOIN prefix p
ON substring(number, 2, 5) = p.code
AND length(n.number) >= 6 -- incl. noise character
AND length(p.code) = 5
UNION ALL
SELECT c.number, p.code, len - 1
FROM cte c
LEFT JOIN prefix p
ON substring(number, 2, c.len) = p.code
AND length(c.number) >= c.len+1 -- incl. noise character
AND length(p.code) = c.len
WHERE c.len > 0
AND c.code IS NULL
)
SELECT number, code
FROM cte
WHERE code IS NOT NULL;
- รันไทม์ทั้งหมด: 1045.115 ms
อย่างไรก็ตามในขณะที่การค้นหานี้ไม่ได้เลวร้าย - มันทำงานได้ดีเหมือนกับเวอร์ชันง่าย ๆ ที่มีดัชนี GIN Trigram - มันไม่ได้ส่งมอบสิ่งที่ฉันต้องการ คำที่เกิดซ้ำมีการวางแผนเพียงครั้งเดียวดังนั้นจึงไม่สามารถใช้ดัชนีที่ดีที่สุดได้ เฉพาะคำศัพท์ที่ไม่เกิดซ้ำได้เท่านั้น
ยูเนี่ยนทั้งหมด
เนื่องจากเราจัดการกับการสอบถามซ้ำจำนวนน้อยเราจึงสามารถสะกดพวกเขาซ้ำ ๆ สิ่งนี้ช่วยให้แผนที่เหมาะสมที่สุดสำหรับแต่ละแผน (เราสูญเสียการแยกแบบเรียกซ้ำสำหรับตัวเลขที่ประสบความสำเร็จอยู่แล้วดังนั้นยังมีห้องพักสำหรับการปรับปรุงโดยเฉพาะอย่างยิ่งสำหรับช่วงความยาวของคำนำหน้าที่กว้างขึ้น)):
SELECT DISTINCT ON (1) number, code
FROM (
SELECT n.number, p.code
FROM num n
JOIN prefix p
ON substring(number, 2, 5) = p.code
AND length(n.number) >= 6 -- incl. noise character
AND length(p.code) = 5
UNION ALL
SELECT n.number, p.code
FROM num n
JOIN prefix p
ON substring(number, 2, 4) = p.code
AND length(n.number) >= 5
AND length(p.code) = 4
UNION ALL
SELECT n.number, p.code
FROM num n
JOIN prefix p
ON substring(number, 2, 3) = p.code
AND length(n.number) >= 4
AND length(p.code) = 3
UNION ALL
SELECT n.number, p.code
FROM num n
JOIN prefix p
ON substring(number, 2, 2) = p.code
AND length(n.number) >= 3
AND length(p.code) = 2
UNION ALL
SELECT n.number, p.code
FROM num n
JOIN prefix p
ON substring(number, 2, 1) = p.code
AND length(n.number) >= 2
AND length(p.code) = 1
) x
ORDER BY number, code DESC;
- จำนวนรันไทม์ทั้งหมด: 57.578 ms (!!)
ความก้าวหน้าในที่สุด!
ฟังก์ชัน SQL
การตัดสิ่งนี้ลงในฟังก์ชั่น SQL จะลบค่าใช้จ่ายในการวางแผนคิวรีเพื่อใช้ซ้ำ:
CREATE OR REPLACE FUNCTION f_longest_prefix()
RETURNS TABLE (number text, code text) LANGUAGE sql AS
$func$
SELECT DISTINCT ON (1) number, code
FROM (
SELECT n.number, p.code
FROM num n
JOIN prefix p
ON substring(number, 2, 5) = p.code
AND length(n.number) >= 6 -- incl. noise character
AND length(p.code) = 5
UNION ALL
SELECT n.number, p.code
FROM num n
JOIN prefix p
ON substring(number, 2, 4) = p.code
AND length(n.number) >= 5
AND length(p.code) = 4
UNION ALL
SELECT n.number, p.code
FROM num n
JOIN prefix p
ON substring(number, 2, 3) = p.code
AND length(n.number) >= 4
AND length(p.code) = 3
UNION ALL
SELECT n.number, p.code
FROM num n
JOIN prefix p
ON substring(number, 2, 2) = p.code
AND length(n.number) >= 3
AND length(p.code) = 2
UNION ALL
SELECT n.number, p.code
FROM num n
JOIN prefix p
ON substring(number, 2, 1) = p.code
AND length(n.number) >= 2
AND length(p.code) = 1
) x
ORDER BY number, code DESC
$func$;
โทร:
SELECT * FROM f_longest_prefix_sql();
- จำนวนรันไทม์ทั้งหมด: 17.138 ms (!!!)
ฟังก์ชัน PL / pgSQL พร้อม SQL แบบไดนามิก
ฟังก์ชัน plpgsql นี้คล้ายกับ recursive CTE ด้านบน แต่ SQL แบบไดนามิกที่มีการEXECUTE
บังคับให้เคียวรีถูกวางแผนใหม่สำหรับการวนซ้ำทุกครั้ง ตอนนี้มันใช้ประโยชน์จากดัชนีที่ปรับแต่งแล้วทั้งหมด
นอกจากนี้ยังใช้ได้กับความยาวของช่วงนำหน้าด้วย ฟังก์ชั่นรับพารามิเตอร์สองตัวสำหรับช่วง แต่ฉันเตรียมไว้พร้อมกับDEFAULT
ค่าดังนั้นจึงใช้งานได้โดยไม่มีพารามิเตอร์ที่ชัดเจนเช่นกัน:
CREATE OR REPLACE FUNCTION f_longest_prefix2(_min int = 1, _max int = 5)
RETURNS TABLE (number text, code text) LANGUAGE plpgsql AS
$func$
BEGIN
FOR i IN REVERSE _max .. _min LOOP -- longer matches first
RETURN QUERY EXECUTE '
SELECT n.number, p.code
FROM num n
JOIN prefix p
ON substring(n.number, 2, $1) = p.code
AND length(n.number) >= $1+1 -- incl. noise character
AND length(p.code) = $1'
USING i;
END LOOP;
END
$func$;
ขั้นตอนสุดท้ายไม่สามารถห่อเป็นฟังก์ชันเดียวได้อย่างง่ายดาย
ทั้งเรียกมันว่าอย่างนี้:
SELECT DISTINCT ON (1)
number, code
FROM f_longest_prefix_prefix2() x
ORDER BY number, code DESC;
- รันไทม์ทั้งหมด: 27.413 ms
หรือใช้ฟังก์ชัน SQL อื่นเป็น wrapper:
CREATE OR REPLACE FUNCTION f_longest_prefix3(_min int = 1, _max int = 5)
RETURNS TABLE (number text, code text) LANGUAGE sql AS
$func$
SELECT DISTINCT ON (1)
number, code
FROM f_longest_prefix_prefix2($1, $2) x
ORDER BY number, code DESC
$func$;
โทร:
SELECT * FROM f_longest_prefix3();
- รันไทม์ทั้งหมด: 37.622 ms
ช้าลงเล็กน้อยเนื่องจากค่าใช้จ่ายในการวางแผนที่จำเป็น แต่หลากหลายกว่า SQL และสั้นกว่าสำหรับคำนำหน้าอีกต่อไป
code
ในตารางแรกเหมือนกับคำนำหน้าในภายหลัง คุณช่วยอธิบายให้ฟังหน่อยได้ไหม? และการแก้ไขข้อมูลตัวอย่างและผลลัพธ์ที่ต้องการ (เพื่อให้ง่ายต่อการติดตามปัญหาของคุณ) จะได้รับการต้อนรับ