Câu hỏi
Bảng kiểm tra:
CREATE TABLE tbl (id int, str text);
INSERT INTO tbl VALUES
(1, 'a.b.c.d.e')
, (2, 'x1.yy2.zzz3') -- different number & length of elements for testing
, (3, '') -- empty string
, (4, NULL); -- NULL
CTE đệ quy trong một truy vấn con LATITH
SELECT *
FROM tbl, LATERAL (
WITH RECURSIVE cte AS (
SELECT str
UNION ALL
SELECT right(str, strpos(str, '.') * -1) -- trim leading name
FROM cte
WHERE str LIKE '%.%' -- stop after last dot removed
)
SELECT ARRAY(TABLE cte) AS result
) r;
Các CROSS JOIN LATERAL
( , LATERAL
cho ngắn) là an toàn, bởi vì kết quả tổng hợp của subquery luôn trả về một hàng. Bạn lấy ...
- ... một mảng có phần tử chuỗi rỗng
str = ''
trong bảng cơ sở
- ... một mảng có phần tử NULL
str IS NULL
trong bảng cơ sở
Được kết hợp với một hàm tạo mảng giá rẻ trong truy vấn con, do đó không có tổng hợp trong truy vấn bên ngoài.
Một biểu hiện của các tính năng SQL, nhưng chi phí rCTE có thể ngăn chặn hiệu suất cao nhất.
Lực lượng vũ phu cho số lượng yếu tố tầm thường
Đối với trường hợp của bạn có số lượng phần tử nhỏ , một cách tiếp cận đơn giản không có truy vấn con có thể nhanh hơn:
SELECT id, array_remove(ARRAY[substring(str, '(?:[^.]+\.){4}[^.]+$')
, substring(str, '(?:[^.]+\.){3}[^.]+$')
, substring(str, '(?:[^.]+\.){2}[^.]+$')
, substring(str, '[^.]+\.[^.]+$')
, substring(str, '[^.]+$')], NULL)
FROM tbl;
Giả sử tối đa 5 yếu tố như bạn đã nhận xét. Bạn có thể dễ dàng mở rộng để biết thêm.
Nếu một miền nhất định có ít thành phần hơn, các substring()
biểu thức thừa sẽ trả về NULL và bị xóa bởi array_remove()
.
Trên thực tế, biểu thức từ trên ( right(str, strpos(str, '.')
), được lồng nhiều lần có thể nhanh hơn (mặc dù khó đọc) vì các hàm biểu thức thông thường đắt hơn.
Một ngã ba truy vấn của @ Dudu
Truy vấn thông minh của @ Dudu có thể được cải thiện với generate_subscripts()
:
SELECT id, array_agg(array_to_string(arr[i:], '.')) AS result
FROM (SELECT id, string_to_array(str,'.') AS arr FROM tbl) t
LEFT JOIN LATERAL generate_subscripts(arr, 1) i ON true
GROUP BY id;
Cũng sử dụng LEFT JOIN LATERAL ... ON true
để bảo tồn các hàng có thể có giá trị NULL.
Hàm PL / pgSQL
Logic tương tự như rCTE. Thực chất đơn giản và nhanh hơn những gì bạn có:
CREATE OR REPLACE FUNCTION string_part_seq(input text, OUT result text[]) AS
$func$
BEGIN
LOOP
result := result || input; -- text[] || text array concatenation
input := right(input, strpos(input, '.') * -1);
EXIT WHEN input = '';
END LOOP;
END
$func$ LANGUAGE plpgsql IMMUTABLE STRICT;
Các OUT
tham số được trả vào cuối của hàm tự động.
Không cần phải khởi tạo result
, bởi vì NULL::text[] || text 'a' = '{a}'::text[]
.
Điều này chỉ hoạt động với 'a'
được gõ đúng. NULL::text[] || 'a'
(chuỗi ký tự) sẽ phát sinh lỗi vì Postgres chọn array || array
toán tử.
strpos()
trả về 0
nếu không tìm thấy dấu chấm nào, vì vậy right()
trả về một chuỗi rỗng và vòng lặp kết thúc.
Đây có lẽ là nhanh nhất trong tất cả các giải pháp ở đây.
Tất cả chúng đều hoạt động trong Postgres 9.3+
(ngoại trừ ký hiệu lát cắt ngắn arr[3:]
. Tôi đã thêm một giới hạn trên trong fiddle để làm cho nó hoạt động trong pg 9.3 : arr[3:999]
.)
Câu đố SQL.
Cách tiếp cận khác nhau để tối ưu hóa tìm kiếm
Tôi với @ jpmc26 (và chính bạn): một cách tiếp cận hoàn toàn khác sẽ được ưa thích hơn. Tôi thích sự kết hợp của jpmc26 reverse()
và a text_pattern_ops
.
Một chỉ số trigram sẽ vượt trội hơn cho các trận đấu một phần hoặc mờ. Nhưng vì bạn chỉ quan tâm đến toàn bộ từ , Tìm kiếm toàn văn bản là một lựa chọn khác. Tôi mong đợi một kích thước chỉ mục nhỏ hơn đáng kể và do đó hiệu suất tốt hơn.
pg_trgm cũng như các truy vấn không nhạy cảm trong trường hợp hỗ trợ FTS , btw.
Tên máy chủ như q.x.t.com
hoặc t.com
(từ có dấu chấm nội tuyến) được xác định là loại "máy chủ" và được coi là một từ. Nhưng cũng có kết hợp tiền tố trong FTS (đôi khi dường như bị bỏ qua). Hướng dẫn sử dụng:
Ngoài ra, *
có thể được đính kèm với một từ vựng để chỉ định khớp tiền tố:
Sử dụng ý tưởng thông minh của @ jpmc26 với reverse()
, chúng tôi có thể thực hiện công việc này:
SELECT *
FROM tbl
WHERE to_tsvector('simple', reverse(str))
@@ to_tsquery ('simple', reverse('c.d.e') || ':*');
-- or with reversed prefix: reverse('*:c.d.e')
Được hỗ trợ bởi một chỉ mục:
CREATE INDEX tbl_host_idx ON tbl USING GIN (to_tsvector('simple', reverse(str)));
Lưu ý 'simple'
cấu hình: Chúng tôi không muốn xuất phát hoặc từ điển đồng nghĩa với 'english'
cấu hình mặc định .
Ngoài ra (với nhiều truy vấn có thể lớn hơn), chúng tôi có thể sử dụng khả năng tìm kiếm cụm từ mới của tìm kiếm văn bản trong Postgres 9.6. Ghi chú phát hành:
Một truy vấn tìm kiếm cụm từ có thể được chỉ định trong đầu vào tsquery bằng cách sử dụng các toán tử mới <->
và . Cái trước có nghĩa là các từ vựng trước và sau nó phải xuất hiện liền kề nhau theo thứ tự đó. Điều thứ hai có nghĩa là chúng phải cách nhau chính xác .<
N
>
N
Truy vấn:
SELECT *
FROM tbl
WHERE to_tsvector ('simple', replace(str, '.', ' '))
@@ phraseto_tsquery('simple', 'c d e');
Thay thế dấu chấm ( '.'
) bằng dấu cách ( ' '
) để giữ trình phân tích cú pháp phân loại 't.com' làm tên máy chủ và thay vào đó sử dụng mỗi từ làm từ vựng riêng biệt.
Và một chỉ số phù hợp để đi với nó:
CREATE INDEX tbl_phrase_idx ON tbl USING GIN (to_tsvector('simple', replace(str, '.', ' ')));