Chương 12. Tìm kiếm toàn văn
12.3. Kiểm soát tìm kiếm toàn văn
Để triển khai tìm kiếm toàn văn sẽ phải có một hàm để tạo một tsvector từ một tài liệu và một
tsquery từ một truy vấn người sử dụng. Hơn nữa, chúng ta cần phải trả về các két quả theo một trật tự hữu dụng, nên chúng ta cần một hàm so sánh các tài liệu với lưu ý về tính phù hợp của chúng đối với truy vấn đó. Cũng quan trọng để có khả năng hiển thị các kết quả một cách sáng sủa.
PostgreSQL đưa ra sự hỗ trợ cho tất cả các hàm đó.
12.3.1. Phân tích tài liệu
PostgreSQL đưa ra hàm to_tsvector cho việc biến đổi một tài liệu sang dạng dữ liệu tsvector.
to_tsvector([ config regconfig, ] document text) returns tsvector
to_tsvector phân tích một tài liệu văn bản thành các thẻ token, giảm các thẻ token thành các từ vị, và trả về một tsvector mà liệt kê các từ vị cùng với các vị trí của chúng trong tài liệu đó. Tài liệu được xử lý theo cấu hình tìm kiếm văn bản mặc định hoặc được chỉ định. Ở đây là một ví dụ đơn giản:
SELECT to_tsvector(’english’, ’a fat cat sat on a mat - it ate a fat rats’);
to_tsvector
---
’ate’:9 ’cat’:3 ’fat’:2,11 ’mat’:7 ’rat’:12 ’sat’:4
Trong ví dụ ở trên chúng ta thấy rằng kết quả tsvector không chứa các từ a, on, hoặc it, từ rats trở thành rat, và dấu gạch ngang - đã bị bỏ qua.
Hàm tsvector ban đầu gọi một trình phân tích chia văn bản tài liệu thành các thẻ token và chỉ định một dạng cho từng thẻ đó. Đối với từng thẻ token, một danh sách các từ điển (Phần 12.6) được tư vấn, nơi mà danh sách đó có thể khác nhau phụ thuộc vào dạng thẻ token. Từ điển đầu tiên nhận
thức được thẻ token đó phát ra một hoặc nhiều từ vị được bình thường hóa hơn để đại diện cho thẻ token đó. Ví dụ, rats trở thành rat vì một trong các từ điển đã nhận thức được rằng từ rats là một dạng số nhiều của rat. Một số từ được nhận thức như là các từ chết (Phần 12.6.1), chúng vì thế bị bỏ qua vì chúng xảy ra quá thường xuyên không hữu dụng khi tìm kiếm. Trong ví dụ của chúng ta đó là a, on, và it. Nếu không từ điển nào trong danh sách đó nhận thức được thẻ token thì nó cũng bị bỏ qua. Trong ví dụ này điều đó đã xảy ra đối với dấu gạch ngang - vì trong thực tế không từ điển nào được chỉ định cho dạng thẻ token của nó (các ký tự trắng), nghĩa là các thẻ token trắng sẽ không bao giờ được đánh chỉ số. Các lựa chọn của trình phân tích, các từ điển và các dạng nào của các thẻ token đánh chỉ số sẽ được cấu hình tìm kiếm văn bản được lựa chọn xác định (Phần 12.7). Có khả năng có nhiều cấu hình khác nhau trong cùng một cơ sở dữ liệu, và các cấu hình được xác định sẵn trước là sẵn sàng cho nhiều ngôn ngữ. Trong ví dụ của chúng ta, chúng ta đã sử dụng cấu hình mặc định english cho ngôn ngữ tiếng Anh.
Hàm setweight có thể được sử dụng để gắn nhãn cho các khoản đầu vào của một tsvector với một trọng số được đưa ra, nơi mà một trọng số là một trong các ký tự A, B, C, hoặc D. Điều này điển hình được sử dụng để đánh dấu các khoản đầu vào tới từ các phần khác nhau của một tài liệu, như tiêu đề so với thân. Sau đó, thông tin này có thể được sử dụng cho việc xếp hạng các kết quả tìm kiếm.
Vì to_tsvector(NULL) sẽ trả về NULL, được khuyến cáo sử dụng coalesce bất kỳ khi nào một trường có thể là null. Ở đây phương pháp được khuyến cáo cho việc tạo tsvector từ một tài liệu có cấu trúc:
UPDATE tt SET ti =
setweight(to_tsvector(coalesce(title,”)), ’A’) ||
setweight(to_tsvector(coalesce(keyword,”)), ’B’) ||
setweight(to_tsvector(coalesce(abstract,”)), ’C’) ||
setweight(to_tsvector(coalesce(body,”)), ’D’);
Chúng ta ở đây đã sử dụng setweight để gắn nhãn cho nguồn của từng từ vị trong tsvector được kết thúc, và sau đó đã trộn các giá trị của tsvector được gắn nhãn bằng việc sử dụng toán tử ghép
tsvector là ||. (Phần 12.4.1 đưa ra các chi tiết về các hoạt động đó).
12.3.2. Phân tích truy vấn
PostgreSQL đưa ra các hàm to_tsquery và plainto_tsquery cho việc biến đổi một truy vấn thành dạng dữ liệu tsquery. to_tsquery đưa ra sự truy cập tới nhiều chức năng hơn plainto_tsquery, nhưng ít tha thứ hơn về đầu vào của nó.
to_tsquery([ config regconfig, ] querytext text) returns tsquery
to_tsquery tạo ra một giá trị tsquery từ querytext, nó phải bao gồm các thẻ token duy nhất được các toán từ & (AND), | (OR) và ! (NOT) tách bạch ra. Các toán tử đó có thể được nhóm lại bằng việc sử dụng các dấu ngoặc đơn. Nói cách khác, đầu vào đối với to_tsquery phải tuân theo rồi các qui tắc chung đối với đầu vào tsquery, như được mô tả trong Phần 8.11. Sự khác biệt là trong khi đầu vào cơ bản tsquery lấy thẻ token ở giá pháp định (face value), thì to_tsquery bình thường hóa từng thẻ token thành một từ vị bằng việc sử dụng cấu hình được chỉ định hoặc mặc định, và hủy bỏ bất kỳ thẻ token nào mà là các từ chết theo cấu hình đó. Ví dụ:
SELECT to_tsquery(’english’, ’The & Fat & Rats’);
to_tsquery ---
’fat’ & ’rat’
Như trong đầu vào cơ bản tsquery, (các) trọng số có thể được gắn vào từng từ vị để hạn chế nó trùng khớp chỉ tsvector các từ vị của (các) trọng số đó. Ví dụ:
SELECT to_tsquery(’english’, ’Fat | Rats:AB’);
to_tsquery ---
’fat’ | ’rat’:AB
Hơn nữa, * cũng được gắn vào một từ vị để chỉ định việc khớp tiền tố:
SELECT to_tsquery(’supern:*A & star:A*B’);
to_tsquery
---
’supern’:*A & ’star’:*AB
Một từ vị như vậy sẽ khớp với bất kỳ từ nào trong một tsvector mà bắt đầu với chuỗi được đưa ra.
to_tsquery cũng có thể chấp nhận các mệnh đề trong các dấu ngoặc đơn. Trước hết điều này là hữu dụng khi cấu hình đó bao gồm một từ điển các từ đồng nghĩa mà có thể làm bật ra các mệnh đề như vậy. Trong ví dụ bên dưới, một từ đồng nghĩa có chứa qui tắc supernovae stars : sn:
SELECT to_tsquery(”’supernovae stars” & !crab’);
to_tsquery ---
’sn’ & !’crab’
Không có các dấu ngoặc, to_tsquery sẽ tạo ra lỗi cú pháp đối với các thẻ token mà không được một toán tử AND hoặc OR tách biệt nhau.
plainto_tsquery([ config regconfig, ] querytext text) returns tsquery
plainto_tsquery biến đổi văn bản không được định dạng querytext sang tsquery. Văn bản đó được phân tích và bình thường hóa nhiều như đối với to_tsvector, sau đó toán tử & (AND) Boolean sẽ được chèn vào giữa các từ đang sống sót.
Ví dụ:
SELECT plainto_tsquery(’english’, ’The Fat Rats’);
plainto_tsquery ---
’fat’ & ’rat’
Lưu ý rằng plainto_tsquery không thể nhận biết được các toán tử Boolean, các nhãn trọng số hoặc các nhãn khớp tiền tố ở đầu vào của nó:
SELECT plainto_tsquery(’english’, ’The Fat & Rats:C’);
plainto_tsquery ---
’fat’ & ’rat’ & ’c’
Ở đây, tất cả các dấu ngắt ở đầu vào từng bị bỏ qua như đang là các ký hiệu trắng.
12.3.3. Xếp hạng các kết quả tìm kiếm
Các cố gắng đo đếm việc xếp hạng các tài liệu phù hợp thế nào là đối với một truy vấn cụ thể, sao cho khi có nhiều sự trùng khớp thì các trùng khớp phù hợp nhất có thể được trình bày đầu tiên.
PostgreSQL đưa ra 2 hàm xếp hạng được định nghĩa trước, chúng tính tới thông tin về từ vị, tính gần đúng và cấu trúc; đó là, chúng xem xét các khoản của truy vấn đó thường xuyên xuất hiện thế nào trong tài liệu, các khoản trong tài liệu đó gần nhau như thế nào, và quan trọng thế nào phần của tài liệu nơi mà chúng xảy ra. Tuy nhiên, khái niệm về tính phù hợp là mơ hồ và rất đặc thù ứng dụng. Các ứng dụng khác nhau có thể đòi hỏi thông tin bổ sung cho việc xếp hạng, như, thời gian sửa đổi tài liệu. Các hàm xếp hạng được xây dựng sẵn chỉ là những ví dụ. Bạn có thể tự mình viết các hàm xếp hạng và/hoặc các kết quả của chúng với các yếu tố bổ sung thêm cho khớp với các nhu cầu đặc thù.
2 hàm xếp hạng hiện sẵn có là:
ts_rank([ weights float4[], ] vector tsvector,
query tsquery [, normalization integer ]) returns float4
Hàm xếp hạng tiêu chuẩn
ts_rank_cd([ weights float4[], ] vector tsvector,
query tsquery [, normalization integer ]) returns float4
Hàm này tính toán xếp hạng mật độ bao trùm đối với vector và truy vấn tài liệu được đưa ra, như được mô tả trong "Xếp hạng phù hợp cho 1 tới 3 khoản truy vấn" của các tác giả Clarke, Cormack, và Tudhope trong tạp chí "Xử lý và Quản lý Thông tin", 1999.
Hàm này đòi hỏi thông tin vị trí ở đầu vào của nó. Vì thế nó sẽ không làm việc trong các giá trị “bị tước bỏ” của tsvector - nó sẽ luôn trả về 0.
Đối với các hàm đó, đối số tùy chọn weights đưa ra khả năng đánh trọng số các lần xuất hiện của từ nhiều hơn hoặc ít hơn, phụ thuộc nhiều vào cách mà chúng được gắn nhãn. Các mảng trọng số chỉ định cách đánh trọng số nặng thế nào cho từng chủng loại từ, theo trật tự:
{D-weight, C-weight, B-weight, A-weight}
Nếu không trọng số nào được đưa ra, thì các mặc định đó sẽ được sử dụng:
{0.1, 0.2, 0.4, 1.0}
Các trọng số điển hình được sử dụng để đánh dấu các từ từ các vùng đặc biệt của tài liệu, như tiêu đề hoặc một trích đoạn ban đầu, sao cho chúng có thể được đối xử với nhiều hoặc ít tầm quan trọng hơn các từ trong thân của tài liệu đó.
Vì một tài liệu dài hơn có một cơ hội lớn hơn trong việc có một khoản truy vấn là hợp lý để tính tới kích cỡ của tài liệu, như, một tài liệu hàng trăm từ với 5 lần xuất hiện của một từ tìm kiếm có thể là phù hợp hơn so với một tài liệu hàng ngàn từ với 5 lần xuất hiện. Các hàm xếp hạng lấy một lựa chọn normalization số nguyên chỉ định liệu và như thế nào độ dài của một tài liệu sẽ tác động tới sự xếp hạng của nó. Tùy chọn số nguyên sẽ kiểm soát vài hành vi, vì thế đó là một mặt nạ bit: bạn có thể chỉ định một hoặc nhiều hành vi bằng việc sử dụng | (ví dụ, 2|4).
• 0 (mặc định) bỏ qua độ dài tài liệu
• 1 chia xếp hạng cho 1 + logarit của chiều dài tài liệu
• 2 chia xếp hạng cho độ dài tài liệu
• 4 chia xếp hạng cho trung bình khoảng cách hài hòa giữa các mở rộng (điều này chỉ được triển khai với ts_rank_cd)
• 8 chia xếp hạng cho số các từ duy nhất trong tài liệu
• 16 chia xếp hạng cho 1 + logarit của số các từ duy nhất trong tài liệu
• 32 chia xếp hạng cho bản thân nó + 1
Nếu hơn một cờ bit được chỉ định, thì các biến đổi được áp dụng trong trật tự được liệt kê.
Là không quan trọng để lưu ý rằng các hàm xếp hạng không sử dụng bất kỳ thông tin tổng thể nào, nên không có khả năng để tạo ra một sự bình thường hóa công bằng tới 1% hoặc 100% như đôi khi được mong muốn. Lựa chọn bình thường hóa 32 (rank/(rank+1) ) có thể được áp dụng để mở rộng phạm vi cho tất cả các xếp hạng trong dải từ 0 tới 1, nhưng tất nhiên điều này chỉ là một thay đổi nhỏ; nó sẽ không ảnh hưởng tới việc xếp thứ tự các kết quả tìm kiếm.
Ở đây là một ví dụ mà chỉ lựa chọn 10 sự trùng khớp được xếp hạng cao nhất:
SELECT title, ts_rank_cd(textsearch, query) AS rank FROM apod, to_tsquery(’neutrino|(dark & matter)’) query WHERE query @@ textsearch
ORDER BY rank DESC LIMIT 10;
title | rank
---+---
Neutrinos in the Sun | 3.1
The Sudbury Neutrino Detector | 2.4
A MACHO View of Galactic Dark Matter | 2.01317
Hot Gas and Dark Matter | 1.91171
The Virgo Cluster: Hot Plasma and Dark Matter | 1.90953
Rafting for Solar Neutrinos | 1.9
NGC 4650A: Strange Galaxy and Dark Matter | 1.85774
Hot Gas and Dark Matter | 1.6123
Ice Fishing for Cosmic Neutrinos | 1.6 Weak Lensing Distorts the Universe | 0.818218
Đây là ví dụ y hệt bằng việc sử dụng xếp hạng được bình thường hóa:
SELECT title, ts_rank_cd(textsearch, query, 32 /* rank/(rank+1) */ ) AS rank FROM apod, to_tsquery(’neutrino|(dark & matter)’) query
WHERE query @@ textsearch ORDER BY rank DESC
LIMIT 10;
title | rank
---+---
Neutrinos in the Sun | 0.756097569485493
The Sudbury Neutrino Detector | 0.705882361190954
A MACHO View of Galactic Dark Matter | 0.668123210574724
Hot Gas and Dark Matter | 0.65655958650282
The Virgo Cluster: Hot Plasma and Dark Matter | 0.656301290640973
Rafting for Solar Neutrinos | 0.655172410958162
NGC 4650A: Strange Galaxy and Dark Matter | 0.650072921219637
Hot Gas and Dark Matter | 0.617195790024749
Ice Fishing for Cosmic Neutrinos | 0.615384618911517 Weak Lensing Distorts the Universe | 0.450010798361481
Việc xếp hạng có thể là đắt giá vì nó đòi hỏi việc tư vấn tsvector đối với từng tài liệu trùng khớp, nó có thể là ràng buộc I/O và vì thế chậm. Không may, hầu như không có khả năng để tránh vì các truy vấn thực tiễn thường dẫn tới các số lượng trùng khớp lớn.
12.3.4. Nhấn mạnh các kết quả
Để thể hiện các kết quả, là lý tưởng để chỉ ra một phần của từng tài liệu và cách mà nó có liên quan tới truy vấn đó. Thường thì các máy tìm kiếm chỉ ra các đoạn tài liệu với các khoản tìm kiếm được đánh dấu. PostgreSQL đưa ra một hàm ts_headline, nó triển khai chức năng này.
ts_headline([ config regconfig, ] document text, query tsquery [, options text ]) returns text
ts_headline chấp nhận một tài liệu đi với một truy vấn, và trả về một trích đoạn từ tài liệu trong đó các khoản từ truy vấn được nhấn mạnh. Cấu hình sẽ được sử dụng để phân tích tài liệu có thể được chỉ định bằng config; Nếu config bị làm mờ đi, thì cấu hình default_text_search_config được sử dụng.
Nếu một chuỗi options được chỉ định thì nó phải bao gồm một danh sách tách bạch nhau bằng dấu phẩy của một hoặc nhiều cặp option=value. Các lựa chọn sẵn sàng là:
• StartSel , StopSel: các chuỗi với chúng để bỏ hạn chế các từ truy vấn xuất hiện trong tài liệu, để phân biệt chúng với các từ được trích đoạn khác. Bạn phải đưa các chuỗi đó vào các dấu ngoặc kép nếu chúng có chứa các khoảng trống hoặc các dấu phẩy.
• MaxWords, MinWords: các số đó xác định các đầu đề dài nhất và ngắn nhất cho đầu ra.
• ShortWord: các từ có độ dài này hoặc ít hơn sẽ bị bỏ đi ở đầu và cuối của một đầu đề. Giá trị mặc định là 3 loại bỏ các bài tiếng Anh phổ biến.
• HighlightAll: cờ Boolean; nếu là true thì toàn bộ tài liệu sẽ được sử dụng như là đầu đề, bỏ qua 3 tham số đi đầu.
• MaxFragments: số lượng tối đa các trích đoạn hoặc đoạn văn bản được hiển thị. Giá trị mặc định là 0 sẽ lựa chọn một phương pháp tạo đầu đề hướng tới không có đoạn nào. Một giá trị lớn hơn 0 chọn sự tạo ra đầu đề dựa vào sự phân đoạn. Phương pháp này thấy các phân đoạn văn bản với càng nhiều từ truy vấn càng tốt và trải các đoạn đó xung quanh các từ truy vấn.
Kết quả là các từ truy vấn nằm gần giữa của từng đoạn và có các từ nằm về các bên. Mỗi đoạn sẽ có hầu hết MaxWords và các từ độ dài ShortWord hoặc nhỏ hơn sẽ bị bỏ đi ở đầu và cuối của từng đoạn. Nếu không phải tất cả các từ truy vấn được thấy trong tài liệu, thì một đoạn duy nhất của MinWords đầu tiên trong tài liệu sẽ được hiển thị.
• FragmentDelimiter: Khi nhiều hơn 1 đoạn được hiển thị, thì các đoạn sẽ được cách nhau bằng chuỗi này.
Bất kỳ lựa chọn không được chỉ định nào cũng nhận các mặc định đó:
StartSel=<b>, StopSel=</b>,
MaxWords=35, MinWords=15, ShortWord=3, HighlightAll=FALSE, MaxFragments=0, FragmentDelimiter=" ... "
Ví dụ:
SELECT ts_headline(’english’,
’The most common type of search
is to find all documents containing given query terms and return them in order of their similarity to the query.’,
to_tsquery(’query & similarity’));
ts_headline
--- containing given <b>query</b> terms
and return them in order of their <b>similarity</b> to the
<b>query</b>.
SELECT ts_headline(’english’,
’The most common type of search
is to find all documents containing given query terms and return them in order of their similarity to the query.’,
to_tsquery(’query & similarity’),
’StartSel = <, StopSel = >’);
ts_headline
--- containing given <query> terms
and return them in order of their <similarity> to the
<query>.
ts_headline sử dụng tài liệu gốc ban đầu, chứ không phải một tóm tắt tsvector, nên nó có thể là chậm và nên được sử dụng thận trọng. Một sai sót điển hình là gọi ts_headline cho từng tài liệu trùng khớp khi chỉ 10 tài liệu sẽ được hiển thị. Các truy vấn phụ SQL có thể giúp: đây là một ví dụ:
SELECT id, ts_headline(body, q), rank
FROM (SELECT id, body, q, ts_rank_cd(ti, q) AS rank FROM apod, to_tsquery(’stars’) q
WHERE ti @@ q ORDER BY rank DESC LIMIT 10) AS foo;