12/08/2018, 14:00

Full-Text Search trong PostgreSQL - Phần 3

Phần 3: Làm quen với Ranking, Trigger, Index Ở phần trước mình có nói về 2 loại dữ liệu là tsvector và tsquery, cách để FTS 1 term với 1 document. Hôm nay mình sẽ giới thiệu tiếp về Ranking của kết quả tìm kiếm, các viết Trigger để tự động cập nhật tsvector khi document có sự thay đổi, cũng như ...

Phần 3: Làm quen với Ranking, Trigger, Index

Ở phần trước mình có nói về 2 loại dữ liệu là tsvector và tsquery, cách để FTS 1 term với 1 document. Hôm nay mình sẽ giới thiệu tiếp về Ranking của kết quả tìm kiếm, các viết Trigger để tự động cập nhật tsvector khi document có sự thay đổi, cũng như cách đánh index cho document giúp việc tìm kiếm nhanh hơn.

3.1 Ranking

FTS không như việc tìm kiếm Like thông thường, nó có thể đánh giá với 1 Term, document nào khớp nhiều hơn, gần với kết quả mong muốn của người tìm kiếm hơn. Và nó được đánh giá thông qua 1 chỉ số là rank.

FTS trong PostgreSQL đưa ra 4 loại rank là {D-weight, C-weight, B-weight, A-weight}, với mức điểm rank tương ứng là {0.1, 0.2, 0.4, 1.0}

Nghĩa là điểm rank sẽ bằng tổng xích ma các phần tử khớp của term trong tsvector nhân với điểm rank tương ứng của phần tử khớp đó.

Mình sẽ mô tả kỹ hơn phần ranking này ở cuối bài, sau khi nói về cách viết Trigger và đánh Index.

3.2 Viết Trigger cập nhật tsvector cho Document

Đầu tiên ta cần có 1 Table để thực hiện FTS.

CREATE TABLE NEWS(
    ID BIGSERIAL PRIMARY KEY NOT NULL,
    TITLE VARCHAR(500) NOT NULL,
    CONTENT TEXT NOT NULL,
    TAGS VARCHAR(500) NOT NULL
)

Bảng NEWS mình tạo ra có 3 Column là title, chứa tiêu đề bài báo, content là nội dung bài báo, tags là các từ khóa liên quan đến bài báo.

Trước tiên ta cần thêm 1 Column là news_tsv vào bảng NEWS, có kiểu dữ liệu là tsvector, sẽ là Column chứa tsvector của 3 Column title, content, tags.

ALTER TABLE NEWS ADD COLUMN news_tsv tsvector;

Bây giờ mình sẽ viết 1 Function, chuyển hết các ký tự tiếng Việt và dấu thành tiếng Anh, giúp cho ta có thể Search không dấu và có dấu dễ dàng hơn. Câu lệnh để tạo Function đó như sau

CREATE OR REPLACE FUNCTION vn_unaccent(text)
  RETURNS text AS
$func$
SELECT lower(translate($1,
'¹²³ÀÁẢẠÂẤẦẨẬẪÃÄÅÆàáảạâấầẩẫậãäåæĀāĂẮẰẲẴẶăắằẳẵặĄąÇçĆćĈĉĊċČčĎďĐđÈÉẸÊẾỀỄỆËèéẹêềếễệëĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨÌÍỈỊÎÏìíỉịîïĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłÑñŃńŅņŇňʼnŊŋÒÓỎỌÔỐỒỔỖỘỐỒỔỖỘƠỚỜỞỠỢÕÖòóỏọôốồổỗộơớờỡợởõöŌōŎŏŐőŒœØøŔŕŖŗŘřߌśŜŝŞşŠšŢţŤťŦŧÙÚỦỤƯỪỨỬỮỰÛÜùúủụûưứừửữựüŨũŪūŬŭŮůŰűŲųŴŵÝýÿŶŷŸŹźŻżŽžёЁ',
'123AAAAAAAAAAAAAAaaaaaaaaaaaaaaAaAAAAAAaaaaaaAaCcCcCcCcCcDdDdEEEEEEEEEeeeeeeeeeEeEeEeEeEeGgGgGgGgHhHhIIIIIIIiiiiiiiIiIiIiIiIiJjKkkLlLlLlLlLlNnNnNnNnnNnOOOOOOOOOOOOOOOOOOOOOOOooooooooooooooooooOoOoOoEeOoRrRrRrSSsSsSsSsTtTtTtUUUUUUUUUUUUuuuuuuuuuuuuUuUuUuUuUuUuWwYyyYyYZzZzZzеЕ'));
$func$ LANGUAGE sql IMMUTABLE;

Oke, bây giờ quay trở lại bài toán tìm kiếm và vấn đề ranking của kết quả tìm kiếm, khi ta FTS, ta muốn ưu tiên kết quả trả về khớp với Title là lớn nhất, xong đến Tags, và cuối cùng là Content. Cái này tùy thuộc vào cách giải quyết bài toán của từng người, mình thì chọn, rank khớp với title là 1.0 = A-weight, rank khớp với tags là 0.4 = B-weight, rank khớp với content là 0.1 = D-weight. Gọi x là số trường hợp khớp của term với title, y là số trường hợp khớp của term với tags, z là số trường hợp khớp của term với content, Rank của kết quả search sẽ bằng x1 + y0.4 + z*0.1

Oke, Trigger cần viết cho bảng NEWS sẽ như sau:

CREATE OR REPLACE FUNCTION news_tsv_trigger_func()
RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN NEW.news_tsv =
	setweight(to_tsvector(coalesce(vn_unaccent(NEW.title))), 'A') ||
	setweight(to_tsvector(coalesce(vn_unaccent(NEW.tags))), 'B') ||
	setweight(to_tsvector(coalesce(vn_unaccent(NEW.content))), 'D');
RETURN NEW;
END $$;

CREATE TRIGGER news_tsv_trigger BEFORE INSERT OR UPDATE
OF title, tags, content ON news FOR EACH ROW
EXECUTE PROCEDURE news_tsv_trigger_func();

3.3. Index FTS

Để FTS được nhanh thì ta cần phải đánh Index cho Column chứa tsvector của Document, như ở trên của mình chính là Column news_tsv, trong PostgreSQL có 2 công nghệ là GIN và GIST, GIN Index chậm nhưng cho tốc độ tìm kiếm nhanh, GIST thì ngược lại. Ở đây mình sử dụng GIN. Câu lệnh là

CREATE INDEX news_idx ON news USING GIN(news_tsv);

Oke, bây giờ insert 1 số bản ghi vào:


![alt](https://s9.postimg.org/3kodzfhsd/2016_10_19_21_26_05.jpg)
Bài tiếp theo mình sẽ hướng dẫn tìm kiếm điều kiện AND, OR, tìm kiếm với prefix của từ, hiển thị Rank, viết Procedure. Các bạn nhớ đón đọc nhé.
0