您好, 欢迎来到 !    登录 | 注册 | | 设为首页 | 收藏本站

实战6:PostgreSQL全文检索功能实战

本小节,我们一起来学习 Postgre 中的一大杀器——FTS(Full Text Search,全文检索)。

提到全文,你是否立刻想到了大名鼎鼎的LuceneElasticsearch。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我们将每篇的titlecontent分别向量化。

由于titlecontent的重要性不一样,的明显比数据更加重要,因此setweight设置的权重为A,而的权重为BA的重要性大于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 向量的重建,由此比较完备的全文检索点也就完成了。

我们的全文实战到此就结束了,你完全可以按照这种模式改编成你自己的应用,让它炫酷的全文。


联系我
置顶