实战6:PostgreSQL全文检索功能实战
本小节,我们一起来学习 Postgre 中的一大杀器——FTS
(Full Text Search,全文检索)。
提到全文,你是否立刻想到了大名鼎鼎的Lucene
和Elasticsearch
。Elasticsearch 基于 Lucene ,并为开发者提供丰富的接口和工具,但是这也造成了它日益庞大。
使用它,你得备上大的服务器,优秀的运维团队,还要承受数据同步的心智负担。但你的需求其实很简单,只是,或者简单的全站。如果在项目的初期,花费如此大成本在上有些得不偿失。
如果本身就全文检索,那该多好啊!没错,Postgre 就全文,而且很强大,还扩展定制。
Postgre 全文是通过 FTS 配置库来的,大多数 Postgre 发行版都了 10 个以上的 FTS 配置库,我们可以通过p
的\dF
命令来查看已安装的配置库:
List of text search conurations
Schema | Name | Description
------------+------------+--------------------------------------------
pg_catalog | arabic | conuration for arabic language
pg_catalog | danish | conuration for danish language
pg_catalog | dutch | conuration for dutch language
pg_catalog | english | conuration for english language
pg_catalog | finnish | conuration for finnish language
pg_catalog | french | conuration for french language
pg_catalog | german | conuration for german language
pg_catalog | hungarian | conuration for hungarian language
.......
可以看到 Postgre 认已经安装了大量的 FTS 配置库,但是很不幸没有配置库
。但好在,Postgre 的形式来扩展 FTS,所以我们可以使用成熟的扩展库。
jieba
是国内颇为著名分词库,如果你是 Python 开发者,那么一定听过它的大名。有贡献者为 Postgre 提供了 jieba 分词——,让我们可以在 Postgre 使用到全文检索。
如果你想跟着我们一起,完成本节的实战,那么请先点开此安装 pg_jieba。
如果你安装成功,那么可以通过\dF
命令来找到jieba
相关的分词配置:
public | jiebacfg | Mix segmentation conuration for jieba
public | jiebahmm | Hmm segmentation conuration for jieba
public | jiebamp | MP segmentation conuration for jieba
public | jiebaqry | Query segmentation conuration for jieba
可以看到jieba
提供了4
种器,它们分别对应了不同的分词算法,如果你感兴趣,可以查阅相关的资料,这里我们不做过多的介绍,认使用jiebacfg
即可。
全文大致可分为两部分:
在 FTS 中,原始文本在构建索引之前需要被向量化。原始文本(如:字符串)必须先被向量化后才能通过 FTS 对其检索,向量化后的需要存储到单独的向量字段中,该向量的数据类型是tsvector
。
Postgre 提供了to_tsvector
来将原始文本向量化,如下:
SELECT * FROM to_tsvector('jiebacfg',',你敢吃我俺老孙一棒吗?');
to_tsvector
-------------------------------------------
'':1 '一棒吗':9 '吃':5 '敢':4 '老孙':8
tsvector
是由(词,序列)
组成的列表,如是原始文本中的第词,所以它的序列是
1
。
有了索引后,我们如何来索引了?
一般情况下,我们是通过关键词
来检索的,那么如何来组织关键词呢?
Postgre 提供了to_tsquery
来将词组织成tsquery
向量,然后通过向量去。如下:
SELECT to_tsquery(' & java');
to_tsquery
----------------
'' & 'java'
tsquery
是一种特殊的数据类型,它会将关键词拼接来表示条件,如&
表示的必须包含和java
。举个复杂的例子:
SELECT to_tsquery(' & (java | python)');
to_tsquery
-------------------------------
'' & ( 'java' | 'python' )
这个例子表示,的必须包含和
java与python
中的一种。
当然你也可以使用句子来:
SELECT * FROM to_tsquery('jiebacfg','难道不香吗?');
to_tsquery
---------------------------
'' & '难道' & '不香吗'
在输入句子的情况下,to_tsquery
会将句子分词,然后将其拼接为tsquery
。
我们总结一下 FTS 的使用:
接下来,我们以实践的角度来使用和学习一下 FTS。
假设某个应用有点,我们将通过 FTS 来实现它。
首先,我们新建数据表:
DROP TABLE IF EXISTS article;
CREATE TABLE article
(
id serial PRIMARY KEY,
title varchar(),
content text
);
id
是每篇的唯一标识,title
是,content
是,我们省略了其它信息。然后我们插入几条记录:
INSERT INTO article(id, title, content)
VALUES (, '科学和人文谁更有意义', '科学和人文谁更有意义,发生了会如何,不发生又会如何。 本人也是经过了深思熟虑,在每个日日夜夜思考这个问题。 一般来讲,我们都必须务必慎重的考虑考虑。 本人也是经过了深思熟虑,在每个日日夜夜思考这个问题。 马云曾经提到过,最大的挑战和突破在于用人,而用人最大的突破在于信任人。我希望诸位也能好好地体会这句话。 既然如此, 科学和人文谁更有意义,发生了会如何,不发生又会如何。 富勒在不经意间这样说过,苦难磨炼一些人,也毁灭另一些人。这启发了我, 塞内加曾经提到过,勇气通往天堂,怯懦通往地狱。这不禁令我深思。 '),
(, '编程的艺术','对我个人而言,编程的艺术不仅仅是重大的事件,还可能会改变我的人生。 编程的艺术,到底应该如何实现。 伏尔泰曾经提到过,坚持意志伟大的事业需要始终不渝的精神。这似乎解答了我的疑惑。 既然如何, 生活中,若编程的艺术出现了,我们就不得不考虑它出现了的事实。 我们不得不面对非常尴尬的事实,那就是, 莎士比亚曾经说过,抛弃时人,时间也抛弃他。这启发了我, 编程的艺术因何而发生? 要想清楚,编程的艺术,到底是一种怎么样的存在。 编程的艺术的发生,到底需要如何做到,不编程的艺术的发生,又会如何产生。 既然如此, 那么。'),
(, '生命在于创造','种困难的抉择下,本人思来想去,寝食难安。 带着这些问题,我们来审视一下生命在于创造。 我认为, 一般来说, 生命在于创造因何而发生? 可是,即使是这样,生命在于创造的出现仍然代表了一定的意义。 生命在于创造,到底应该如何实现。 问题的关键究竟为何? 生活中,若生命在于创造出现了,我们就不得不考虑它出现了的事实。 生命在于创造因何而发生? 莎士比亚曾经提到过,人的一生是短的,但如果卑劣地过这一生,就太长了。我希望诸位也能好好地体会这句话。');
有了和后,我们需要为每篇单独新建字段fts
用来表示每篇的 tsvector 字段,并且给 fts 字段创建 gin 索引,这样后面就可以通过该字段来了。
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);
在 语句中,我们首先为article
数据表新增了fts
字段,字段类型为tsvector
。有了该字段后,我们需要为该字段赋值,通过to_tsvector
我们将每篇的title
和content
分别向量化。
由于title
和content
的重要性不一样,的明显比数据更加重要,因此setweight
设置的权重为A
,而的权重为B
,A
的重要性大于B
。||
操作符合并向量后将结果赋给fts
。
到此,article 表中新增了 fts 字段,字段中是和词组的列表。最后为 fts 字段我们新建了索引 article_fts_gin_index 来加速我们的效率。
: || 操作符是 Postgre 的特点,表示连接、合并。
接下来,我们便可以使用全文了,条件是需包含问题
关键字,如下:
SELECT title FROM article WHERE fts @@ to_tsquery('问题');
title
----------------------
科学和人文谁更有意义
生命在于创造
Postgre 提供@@
操作符来,上面语句将问题
通过to_tsquery
转化为向量后,使用@@
来。从结果中可以看出,与问题
相关的有两篇。
注意: 在 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 向量的重建,由此比较完备的全文检索点也就完成了。
我们的全文实战到此就结束了,你完全可以按照这种模式改编成你自己的应用,让它炫酷的全文。