1. 前言
本小节,我们一起来学习 Postgresql 中的一大杀器——FTS
(Full Text Search,全文检索)。
提到全文搜索,你是否立刻想到了大名鼎鼎的Lucene
和Elasticsearch
。Elasticsearch 基于 Lucene ,并为开发者提供丰富的接口和工具,但是这也造成了它日益庞大。
使用它,你得备上一个大的服务器,一个优秀的运维团队,还要承受数据同步的心智负担。但你的需求其实很简单,只是一个小功能搜索,或者一个简单的全站搜索。如果在项目的初期,花费如此大成本在搜索上有些得不偿失。
2. FTS配置库
List of text search configurations
Schema | Name | Description
------------+------------+--------------------------------------------
pg_catalog | arabic | configuration for arabic language
pg_catalog | danish | configuration for danish language
pg_catalog | dutch | configuration for dutch language
pg_catalog | english | configuration for english language
pg_catalog | finnish | configuration for finnish language
pg_catalog | french | configuration for french language
pg_catalog | german | configuration for german language
pg_catalog | hungarian | configuration for hungarian language
.......
2.2 pg_jiebe FTS
jieba
是国内一个颇为著名分词库,如果你是 Python 开发者,那么一定听过它的大名。有贡献者为 Postgresql 提供了 jieba 分词插件——pg_jieba,让我们可以在 Postgresql 使用到中文全文检索。
如果你安装成功,那么可以通过\dF
命令来找到jieba
相关的分词配置:
public | jiebacfg | Mix segmentation configuration for jieba
public | jiebahmm | Hmm segmentation configuration for jieba
public | jiebamp | MP segmentation configuration for jieba
public | jiebaqry | Query segmentation configuration for jieba
3. 基本使用
3.1 FTS 流程
全文搜索大致可分为两部分:
- 构建文本对应的索引(倒排索引)
- 通过搜索索引来找到对应的文本
3.2 文本向量化
在 FTS 中,原始文本在构建索引之前需要被向量化。原始文本(如:字符串)必须先被向量化后才能通过 FTS 对其检索,向量化后的内容需要存储到一个单独的向量字段中,该向量的数据类型是tsvector
。
3.3 搜索关键字向量化
有了索引后,我们如何来搜索索引了?
一般情况下,我们是通过关键词
来检索的,那么如何来组织关键词呢?
SELECT to_tsquery('sql & java');
to_tsquery
----------------
'sql' & 'java'
SELECT to_tsquery('sql & (java | python)');
to_tsquery
-------------------------------
'sql' & ( 'java' | 'python' )
3.4 搜索关键句向量化
当然你也可以使用句子来搜索:
SELECT * FROM to_tsquery('jiebacfg','sql难道不香吗?');
to_tsquery
---------------------------
'sql' & '难道' & '不香吗'
在输入句子的情况下,to_tsquery
会自动将句子分词,然后将其拼接为tsquery
。
3.5 FTS 总结
我们总结一下 FTS 的使用:
4. 实践
接下来,我们以实践的角度来使用和学习一下 FTS。
首先,我们新建文章数据表:
DROP TABLE IF EXISTS article;
CREATE TABLE article
(
id serial PRIMARY KEY,
title varchar(),
content text
);
INSERT INTO article(id, title, content)
VALUES (, '科学和人文谁更有意义', '科学和人文谁更有意义,发生了会如何,不发生又会如何。 本人也是经过了深思熟虑,在每个日日夜夜思考这个问题。 一般来讲,我们都必须务必慎重的考虑考虑。 本人也是经过了深思熟虑,在每个日日夜夜思考这个问题。 马云曾经提到过,最大的挑战和突破在于用人,而用人最大的突破在于信任人。我希望诸位也能好好地体会这句话。 既然如此, 科学和人文谁更有意义,发生了会如何,不发生又会如何。 富勒在不经意间这样说过,苦难磨炼一些人,也毁灭另一些人。这启发了我, 塞内加曾经提到过,勇气通往天堂,怯懦通往地狱。这不禁令我深思。 '),
(, '编程的艺术','对我个人而言,编程的艺术不仅仅是一个重大的事件,还可能会改变我的人生。 编程的艺术,到底应该如何实现。 伏尔泰曾经提到过,坚持意志伟大的事业需要始终不渝的精神。这似乎解答了我的疑惑。 既然如何, 生活中,若编程的艺术出现了,我们就不得不考虑它出现了的事实。 我们不得不面对一个非常尴尬的事实,那就是, 莎士比亚曾经说过,抛弃时间的人,时间也抛弃他。这启发了我, 编程的艺术因何而发生? 要想清楚,编程的艺术,到底是一种怎么样的存在。 编程的艺术的发生,到底需要如何做到,不编程的艺术的发生,又会如何产生。 既然如此, 那么。'),
(, '生命在于创造','在这种困难的抉择下,本人思来想去,寝食难安。 带着这些问题,我们来审视一下生命在于创造。 我认为, 一般来说, 生命在于创造因何而发生? 可是,即使是这样,生命在于创造的出现仍然代表了一定的意义。 生命在于创造,到底应该如何实现。 问题的关键究竟为何? 生活中,若生命在于创造出现了,我们就不得不考虑它出现了的事实。 生命在于创造因何而发生? 莎士比亚曾经提到过,人的一生是短的,但如果卑劣地过这一生,就太长了。我希望诸位也能好好地体会这句话。');
4.2 构建文章索引
ALTER TABLE article ADD COLUMN fts tsvector;
UPDATE article
SET fts = setweight(to_tsvector('jiebacfg', title), 'A') ||
setweight(to_tsvector('jiebacfg', content), 'B');
CREATE INDEX article_fts_gin_index ON article USING gin (fts);
在 sql 语句中,我们首先为article
数据表新增了一个fts
字段,字段类型为tsvector
。有了该字段后,我们需要为该字段赋值,通过to_tsvector
我们将每篇文章的title
和content
分别向量化。
4.3 使用 FTS
SELECT title FROM article WHERE fts @@ to_tsquery('问题');
title
----------------------
科学和人文谁更有意义
生命在于创造
注意: 在 article 表中,只有 fts 是 tsvector 字段,因此只有它能使用 @@ 操作符。
SELECT title FROM article WHERE fts @@ to_tsquery('问题 & 生命');
title
--------------
生命在于创造
DROP TRIGGER IF EXISTS trig_article_insert_update ON article;
CREATE TRIGGER trig_article_insert_update
BEFORE INSERT OR UPDATE OF title,content
ON article
FOR EACH ROW
EXECUTE PROCEDURE tsvector_update_trigger(fts, 'public.jiebacfg', title, content);
有了 trig_article_insert_update 这个触发器后,article 表中插入或 title,content 的更新都会引起 fts 向量的重建,由此一个比较完备的全文检索功能点也就完成了。