Kết hợp mẫu với THÍCH, SIMILAR ĐẾN hoặc biểu thức thông thường trong PostgreSQL


94

Tôi đã phải viết một truy vấn đơn giản trong đó tôi đi tìm tên người bắt đầu bằng B hoặc D:

SELECT s.name 
FROM spelers s 
WHERE s.name LIKE 'B%' OR s.name LIKE 'D%'
ORDER BY 1

Tôi đã tự hỏi nếu có một cách để viết lại điều này để trở nên hiệu quả hơn. Vì vậy, tôi có thể tránh orvà / hoặc like?


Tại sao bạn cố gắng viết lại? Hiệu suất? Sự gọn gàng? Được s.namelập chỉ mục?
Martin Smith

Tôi muốn viết cho hiệu suất, s.name không được lập chỉ mục.
Lucas Kauffman

8
Cũng như bạn đang tìm kiếm mà không dẫn đầu các thẻ hoang dã và không chọn bất kỳ cột bổ sung nào, một chỉ mục trên namecó thể hữu ích ở đây nếu bạn quan tâm đến hiệu suất.
Martin Smith

Câu trả lời:


161

Truy vấn của bạn là khá nhiều tối ưu. Cú pháp sẽ không ngắn hơn nhiều, truy vấn sẽ không nhanh hơn nhiều:

SELECT name
FROM   spelers
WHERE  name LIKE 'B%' OR name LIKE 'D%'
ORDER  BY 1;

Nếu bạn thực sự muốn rút ngắn cú pháp , hãy sử dụng biểu thức chính quy với các nhánh :

...
WHERE  name ~ '^(B|D).*'

Hoặc nhanh hơn một chút, với một lớp nhân vật :

...
WHERE  name ~ '^[BD].*'

Một thử nghiệm nhanh mà không có chỉ số mang lại kết quả nhanh hơn so với SIMILAR TOtrong cả hai trường hợp đối với tôi.
Với chỉ số B-Tree thích hợp, LIKEchiến thắng cuộc đua này bằng các đơn đặt hàng lớn.

Đọc những điều cơ bản về khớp mẫu trong hướng dẫn .

Chỉ số cho hiệu suất vượt trội

Nếu bạn quan tâm đến hiệu suất, hãy tạo một chỉ mục như thế này cho các bảng lớn hơn:

CREATE INDEX spelers_name_special_idx ON spelers (name text_pattern_ops);

Làm cho loại truy vấn này nhanh hơn theo thứ tự cường độ. Xem xét đặc biệt áp dụng cho thứ tự sắp xếp địa phương cụ thể. Đọc thêm về các lớp toán tử trong hướng dẫn . Nếu bạn đang sử dụng ngôn ngữ "C" tiêu chuẩn (hầu hết mọi người không), một chỉ mục đơn giản (với lớp toán tử mặc định) sẽ làm.

Một chỉ mục như vậy chỉ tốt cho các mẫu neo trái (khớp từ đầu chuỗi).

SIMILAR TOhoặc các biểu thức chính quy với các biểu thức neo trái cơ bản cũng có thể sử dụng chỉ mục này. Nhưng không phải với các nhánh (B|D)hoặc các lớp ký tự [BD](ít nhất là trong các thử nghiệm của tôi trên PostgreQuery 9.0).

Kết hợp trigram hoặc tìm kiếm văn bản sử dụng các chỉ mục GIN hoặc GiST đặc biệt.

Tổng quan về toán tử khớp mẫu

  • LIKE( ~~) là đơn giản và nhanh chóng nhưng hạn chế trong khả năng của nó.
    ILIKE( ~~*) trường hợp biến thể không nhạy cảm.
    pg_trgm mở rộng hỗ trợ chỉ mục cho cả hai.

  • ~ (kết hợp biểu thức chính quy) mạnh mẽ nhưng phức tạp hơn và có thể chậm đối với mọi thứ hơn các biểu thức cơ bản.

  • SIMILAR TOchỉ là vô nghĩa . Một nửa giống đặc biệt LIKEvà biểu thức chính quy. Tôi không bao giờ sử dụng nó. Xem bên dưới.

  • % là toán tử "tương tự", được cung cấp bởi mô-đun bổ sungpg_trgm. Xem bên dưới.

  • @@là toán tử tìm kiếm văn bản. Xem bên dưới.

pg_trgm - trigram phù hợp

Bắt đầu với PostgreSQL 9.1, bạn có thể tạo điều kiện cho tiện ích mở rộng pg_trgmcung cấp hỗ trợ chỉ mục cho bất kỳ LIKE / ILIKEmẫu nào (và các mẫu biểu thức chính quy đơn giản với ~) bằng cách sử dụng chỉ mục GIN hoặc GiST.

Chi tiết, ví dụ và liên kết:

pg_trgmcũng cung cấp các toán tử này :

  • % - toán tử "tương tự"
  • <%( %>cổ góp :) - toán tử "word_similarity" trong Postgres 9.6 trở lên
  • <<%( %>>cổ góp :) - toán tử "rict_word_similarity" trong Postgres 11 trở lên

Tìm kiếm văn bản

Là một kiểu mẫu đặc biệt phù hợp với các loại chỉ mục và cơ sở hạ tầng riêng biệt. Nó sử dụng từ điển và xuất phát và là một công cụ tuyệt vời để tìm các từ trong tài liệu, đặc biệt là các ngôn ngữ tự nhiên.

Kết hợp tiền tố cũng được hỗ trợ:

Cũng như tìm kiếm cụm từ kể từ Postgres 9.6:

Xem xét phần giới thiệu trong hướng dẫntổng quan về các toán tử và chức năng .

Các công cụ bổ sung để khớp chuỗi mờ

Các fuzzystrmatch mô-đun bổ sung cung cấp một số tùy chọn khác, nhưng hiệu suất thường kém hơn tất cả các bên trên.

Cụ thể, việc thực hiện các levenshtein()chức năng khác nhau có thể là công cụ.

Tại sao các biểu thức chính quy ( ~) luôn nhanh hơn SIMILAR TO?

Đáp án đơn giản. SIMILAR TObiểu thức được viết lại thành biểu thức chính quy trong nội bộ. Vì vậy, đối với mỗi SIMILAR TObiểu thức, có ít nhất một biểu thức chính quy nhanh hơn (giúp tiết kiệm chi phí viết lại biểu thức). Không có hiệu suất đạt được trong việc sử dụng SIMILAR TO bao giờ .

Và các biểu thức đơn giản có thể được thực hiện với LIKE( ~~) nhanh hơn với LIKEdù sao đi nữa.

SIMILAR TOchỉ được hỗ trợ trong PostgreSQL vì nó đã kết thúc trong các bản nháp đầu tiên của tiêu chuẩn SQL. Họ vẫn chưa thoát khỏi nó. Nhưng có kế hoạch để loại bỏ nó và bao gồm các trận đấu regrec thay vào đó - hoặc vì vậy tôi đã nghe.

EXPLAIN ANALYZEtiết lộ nó Chỉ cần thử với bất kỳ bảng nào!

EXPLAIN ANALYZE SELECT * FROM spelers WHERE name SIMILAR TO 'B%';

Tiết lộ:

...  
Seq Scan on spelers  (cost= ...  
  Filter: (name ~ '^(?:B.*)$'::text)

SIMILAR TOđã được viết lại với một biểu thức chính quy ( ~).

Hiệu suất cuối cùng cho trường hợp cụ thể này

Nhưng EXPLAIN ANALYZEtiết lộ nhiều hơn. Hãy thử, với chỉ số được đề cập ở trên:

EXPLAIN ANALYZE SELECT * FROM spelers WHERE name ~ '^B.*;

Tiết lộ:

...
 ->  Bitmap Heap Scan on spelers  (cost= ...
       Filter: (name ~ '^B.*'::text)
        ->  Bitmap Index Scan on spelers_name_text_pattern_ops_idx (cost= ...
              Index Cond: ((prod ~>=~ 'B'::text) AND (prod ~<~ 'C'::text))

Bên trong, với một chỉ số mà không được Locale-aware ( text_pattern_opshoặc sử dụng locale C) biểu thức đơn giản trái neo được viết lại với các nhà khai thác mô hình văn bản: ~>=~, ~<=~, ~>~, ~<~. Đây là trường hợp cho ~, ~~hoặc SIMILAR TOnhư nhau.

Điều này cũng đúng với các chỉ mục trên varcharcác loại có varchar_pattern_opshoặc charbpchar_pattern_ops.

Vì vậy, áp dụng cho câu hỏi ban đầu, đây là cách nhanh nhất có thể :

SELECT name
FROM   spelers  
WHERE  name ~>=~ 'B' AND name ~<~ 'C'
    OR name ~>=~ 'D' AND name ~<~ 'E'
ORDER  BY 1;

Tất nhiên, nếu bạn tình cờ tìm kiếm tên viết tắt liền kề , bạn có thể đơn giản hóa hơn nữa:

WHERE  name ~>=~ 'B' AND name ~<~ 'D'   -- strings starting with B or C

Việc đạt được trên sử dụng đơn giản ~hoặc ~~là rất nhỏ. Nếu hiệu suất không phải là yêu cầu tối quan trọng của bạn, bạn chỉ nên gắn bó với các toán tử tiêu chuẩn - đi đến những gì bạn đã có trong câu hỏi.


OP không có chỉ mục về tên nhưng bạn có biết không, nếu có, truy vấn ban đầu của họ có liên quan đến 2 lần tìm kiếm và similarquét không?
Martin Smith

2
@MartinSmith: Một thử nghiệm nhanh với EXPLAIN ANALYZEhiển thị 2 lần quét chỉ mục bitmap. Nhiều lần quét chỉ mục bitmap có thể được kết hợp khá nhanh chóng.
Erwin Brandstetter

Cảm ơn. Vì vậy, sẽ có bất kỳ milage với thay thế ORbằng UNION ALLhoặc thay thế name LIKE 'B%'bằng name >= 'B' AND name <'C'trong Postgres?
Martin Smith

1
@MartinSmith: UNIONsẽ không nhưng, vâng, kết hợp các phạm vi thành một WHEREmệnh đề sẽ tăng tốc truy vấn. Tôi đã thêm nhiều hơn vào câu trả lời của tôi. Tất nhiên, bạn phải đưa địa điểm của bạn vào tài khoản. Tìm kiếm nhận thức địa phương luôn luôn chậm hơn.
Erwin Brandstetter

2
@a_horse_with_no_name: Tôi không mong đợi. Các khả năng mới của pg_tgrm với các chỉ mục GIN là một điều trị cho tìm kiếm văn bản chung. Một tìm kiếm neo ở đầu đã nhanh hơn thế.
Erwin Brandstetter

11

Làm thế nào về việc thêm một cột vào bảng. Tùy thuộc vào yêu cầu thực tế của bạn:

person_name_start_with_B_or_D (Boolean)

person_name_start_with_char CHAR(1)

person_name_start_with VARCHAR(30)

PostgreSQL không hỗ trợ các cột được tính toán trong các bảng cơ sở là SQL Server nhưng cột mới có thể được duy trì thông qua kích hoạt. Rõ ràng, cột mới này sẽ được lập chỉ mục.

Ngoài ra, một chỉ mục trên một biểu thức sẽ cung cấp cho bạn như nhau, rẻ hơn. Ví dụ:

CREATE INDEX spelers_name_initial_idx ON spelers (left(name, 1)); 

Các truy vấn phù hợp với biểu thức trong điều kiện của họ có thể sử dụng chỉ số này.

Bằng cách này, lần truy cập hiệu suất được thực hiện khi dữ liệu được tạo hoặc sửa đổi, do đó có thể chỉ phù hợp với môi trường hoạt động thấp (nghĩa là ghi ít hơn nhiều so với đọc).


8

Bạn có thể thử

SELECT s.name
FROM   spelers s
WHERE  s.name SIMILAR TO '(B|D)%' 
ORDER  BY s.name

Tôi không biết liệu ở trên hay biểu hiện ban đầu của bạn có thể nói được trong Postgres hay không.

Nếu bạn tạo chỉ mục được đề xuất cũng sẽ được quan tâm để nghe cách so sánh với các tùy chọn khác.

SELECT name
FROM   spelers
WHERE  name >= 'B' AND name < 'C'
UNION ALL
SELECT name
FROM   spelers
WHERE  name >= 'D' AND name < 'E'
ORDER  BY name

1
Nó hoạt động và tôi có chi phí 1,19 trong đó tôi có 1,25. Cảm ơn !
Lucas Kauffman

2

Những gì tôi đã làm trong quá khứ, đối mặt với một vấn đề hiệu suất tương tự, là tăng ký tự ASCII của chữ cái cuối cùng và thực hiện GIỮA. Sau đó, bạn có được hiệu suất tốt nhất, cho một tập hợp con của chức năng THÍCH. Tất nhiên, nó chỉ hoạt động trong một số trường hợp nhất định, nhưng đối với các bộ dữ liệu cực lớn mà bạn đang tìm kiếm trên một tên chẳng hạn, nó làm cho hiệu suất đi từ vực thẳm đến chấp nhận được.


2

Câu hỏi rất cũ, nhưng tôi đã tìm thấy một giải pháp nhanh khác cho vấn đề này:

SELECT s.name 
FROM spelers s 
WHERE ascii(s.name) in (ascii('B'),ascii('D'))
ORDER BY 1

Vì hàm ascii () chỉ nhìn vào ký tự đầu tiên của chuỗi.


1
Điều này có sử dụng một chỉ số trên (name)?
ypercubeᵀᴹ

2

Để kiểm tra tên viết tắt, tôi thường sử dụng chuyển sang "char"(với dấu ngoặc kép). Nó không phải là di động, nhưng rất nhanh. Trong nội bộ, nó chỉ đơn giản là hủy văn bản và trả về ký tự đầu tiên và các hoạt động so sánh "char" rất nhanh vì loại có độ dài cố định 1 byte:

SELECT s.name 
FROM spelers s 
WHERE s.name::"char" =ANY( ARRAY[ "char" 'B', 'D' ] )
ORDER BY 1

Lưu ý rằng việc truyền tới "char"nhanh hơn độ ascii()dốc của @ Sole021, nhưng nó không tương thích với UTF8 (hoặc bất kỳ mã hóa nào khác cho vấn đề đó), chỉ trả về byte đầu tiên, do đó chỉ nên được sử dụng trong trường hợp so sánh với 7 cũ -bit ký tự ASCII.


1

Có hai phương pháp chưa được đề cập để xử lý các trường hợp như vậy:

  1. Chỉ mục một phần (hoặc được phân vùng - nếu được tạo cho toàn bộ phạm vi theo cách thủ công) - hữu ích nhất khi chỉ yêu cầu một tập hợp con dữ liệu (ví dụ: trong một số bảo trì hoặc tạm thời cho một số báo cáo):

    CREATE INDEX ON spelers WHERE name LIKE 'B%'
  2. tự phân vùng bảng (sử dụng ký tự đầu tiên làm khóa phân vùng) - kỹ thuật này đặc biệt đáng xem xét trong PostgreQuery 10+ (phân vùng ít đau đớn hơn) và 11+ (cắt xén phân vùng trong khi thực hiện truy vấn).

Hơn nữa, nếu dữ liệu trong một bảng được sắp xếp, người ta có thể hưởng lợi từ việc sử dụng chỉ số BRIN (qua ký tự đầu tiên).


-4

Có lẽ nhanh hơn để làm một so sánh nhân vật duy nhất:

SUBSTR(s.name,1,1)='B' OR SUBSTR(s.name,1,1)='D'

1
Không hẳn vậy. column LIKE 'B%'sẽ hiệu quả hơn so với sử dụng chức năng chuỗi con trên cột.
ypercubeᵀᴹ
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.