Skip to content

PostgreSQL全文检索1

Jaimin Pan edited this page Jun 15, 2015 · 3 revisions

PostgreSQL全文检索

从PostgreSQL 8.3自带支持全文检索功能(原文),在之前的版本中需要安装配置tsearch2才能使用。

1. 全文检索类型(Text Search Types)

postgreSQL设计支持全文检索,提供两个数据类型(tsvector,tsquery),并且通过动态检索自然语言文档的集合,定位到最匹配的查询结果。

tsvector

一个tsvector的值是唯一分词的分类列表,把一话一句词格式化为不同的词条,在进行分词处理的时候tsvector会自动去掉分词中重复的词条,按照一定的顺序装入。例如

SELECT 'a fat cat sat on a mat and ate a fat rat'::tsvector;
                      tsvector
----------------------------------------------------
 'a' 'on' 'and' 'ate' 'cat' 'fat' 'mat' 'rat' 'sat'

从上面的例子可以看出 ,通过tsvector把一个字符串按照空格进行分词,分词的顺序是按照长短和字母来排序的。但是某些时候,我们为了让词条中包含空格或者符号,就需要对其使用引号。

SELECT $$the lexeme '    ' contains spaces$$::tsvector;
                 tsvector                  
-------------------------------------------
 'the' '    ' 'lexeme' 'spaces' 'contains'

为了使用引号,我们可以使用双$$符号来避免混淆。并且词条位置常量可以附属于每个词条,例如:

SELECT 'a:1 fat:2 cat:3 sat:4 on:5 a:6 mat:7 and:8 ate:9 a:10 fat:11 rat:12'::tsvector;
                                  tsvector
-------------------------------------------------------------------------------
 'a':1,6,10 'on':5 'and':8 'ate':9 'cat':3 'fat':2,11 'mat':7 'rat':12 'sat':4

这个位置信息通常就是当前文档中单词所处的位置,这个位置信息用于关注度的体现。位置信息常量的值的范围为1 到 16383。分词后,会把相同词条的位置记录到一个词条中(如上所示)。
词条通过权重可以使其所在位置促进它的标记。权重分为A,B,C,D,D为默认值可以不显示。

权重用于关系,体现文档结构是很有特色地。例如,通俗一点,就是相同的词条,但是词条所在位置的权重不一样,在一个文档中,标题和文本内容,在做全文检索排序功能时需要分配给这两个词不同的优先权,不同的权重标记。

理解tsvector类型是很重要的,不能只关注标准的应用。例如

select 'The Fat Rats'::tsvector;
      tsvector      
--------------------
 'Fat' 'The' 'Rats'

但是对于英文全文检索应用来说,上面的句子就是非标准化的,但是tsvector是不会知道的,为处理加工的文本应该通过使用to_tsvector函数来是之规格化,标注化的应用于搜索。

SELECT to_tsvector('english', 'The Fat Rats');         
   to_tsvector   
-----------------
 'fat':2 'rat':3

tsquery

顾名思义,tsquery表示的应该是查询相关的。tsquery是存储用于检索的词条。并且可以联合使用 boolean 操作符来连接,& (AND),| (OR),and ! (NOT)。使用括号()可以强制分为一组。

 SELECT 'fat & rat'::tsquery;
    tsquery    
---------------
 'fat' & 'rat'

SELECT 'fat & (rat | cat)'::tsquery;
          tsquery          
---------------------------
 'fat' & ( 'rat' | 'cat' )

SELECT 'fat & rat & ! cat'::tsquery;
        tsquery         
------------------------
 'fat' & 'rat' & !'cat'

同时,tsquery 在做搜索的时候,也可以使用权重,并且每个词都可以使用一个或者多个权重标记,这样在检索的时候,会匹配相同权重的信息。 跟上面的tsvector,相同tsquery也有一个to_tsquery函数。

全文检索的 document

document就是全文检索的搜索单元,在postgresql中全文检索匹配操作使用 @@ 操作符,如果一个 tsvector(document) 匹配到 tsquery(query)则返回true。

SELECT 'a fat cat sat on a mat and ate a fat rat'::tsvector @@ 'cat & rat'::tsquery;
 ?column?
----------
 t

我们在处理索引的时候还是要使用他们的函数如,

SELECT to_tsvector('fat cats ate fat rats') @@ to_tsquery('fat & rat');
 ?column? 
----------
 t

并且操作符 @@ 可以使用text作为tsvector和tsquery。如下操作符可以使使用的方法

  • tsvector @@ tsquery
  • tsquery @@ tsvector
  • text @@ tsquery
  • text @@ text 上面的前两种我们已经使用过了,后两种是 text @@ tsquery 等同于 to_tsvector(x) @@ y 而 text @@ text 等同于 to_tsvector(x) @@ plainto_tsquery(y)

2. 表和索引

前面介绍了如何在简单文本中进行全文检索匹配.下面部分将介绍如何检索表数据和使用索引.

检索一个表

在全文检索中不使用索引也是可以进行检索的,例如下面的简单例子,查询出 title 从所有 body 中包含 friend 的行。

SELECT title FROM pgweb
WHERE to_tsvector('english', body) @@ to_tsquery('english', 'friend');

复杂一点的例子,检索出最近的 10 个文档,在表中的 title 和 body 字段中包含 creat 和 table 的 titile。

SELECT title FROM pgweb
WHERE to_tsvector(title || body) @@ to_tsquery('create & table')
ORDER BY last_mod_date DESC LIMIT 10;

建立索引

我们可以通过创建gin索引来加速检索速度。例如

CREATE INDEX pgweb_idx ON pgweb USING gin(to_tsvector('english', body));

创建索引可以有多种方式。索引的创建甚至可以连接两个列:

CREATE INDEX pgweb_idx ON pgweb USING gin(to_tsvector('english', title || body));

另外的一种方式是创建一个单独的 tsvector 列,然后使用to_tsvector函数把需要索引字段的数据联合在一起,比如列title和body,并且使用函数coalesce来确保字段为NULL的可以建立索引。如下:

ALTER TABLE pgweb ADD COLUMN textsearchable_index_col tsvector;
UPDATE pgweb SET textsearchable_index_col =
    to_tsvector('english', coalesce(title,'') || coalesce(body,''));

然后,我们就可以创建倒排的索引

CREATE INDEX textsearch_idx ON pgweb USING gin(textsearchable_index_col);

索引创建完毕,我们就可以使用全文检索了。

SELECT title FROM pgweb
WHERE textsearchable_index_col @@ to_tsquery('create & table')
ORDER BY last_mod_date DESC LIMIT 10;

3. 控制全文检索(Controlling Text Search)

为了实现全文检索,我们需要把一个文档创建一个tsvector 格式,并且通过tsquery实现用户的查询。 因此,在查询中我们返回一个按照重要性排序的查询结果。

分析文档(Parsing Documents)

postgresql 中提供了to_tsvector函数把文档处理成tsvector数据类型。(前面已经介绍过了)
这个函数会将文本文档,分解成唯一的词条,并且还包括词条所在文本中的位置。(这里to_tsvector函数就不再多做介绍了)。
其实在做分词是很重要的步骤,分词的操作直接关系到你的后来检索的结果。(后面再重要描述)
在postgreSQL中默认的to_tsvector('english',)配置默认的是英语。
postgre 中还有一个函数 setweight ,要使用这个函数我们要引入一个概念,这个概念就是权重weight,什么是权重呢,字面上解释就是权衡一下哪个更重要,也就是说哪个更侧重一些。我们可以通过函数setweight来设置权重,postgre提供了四个A,B,C,D来分别表示不同权重级别,这个级别类型用来标记他们来自于文档中的不同部分,例如title和body。查询结果的关注度可以使用这个权重级别。如:

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');

搜索分析(Parsing Queries)

postgreSQL中提供了to_tsquery函数和plainto_tsquery函数,来处理分析搜索语句。

SELECT to_tsquery('english', 'The & Fat & Rats');
  to_tsquery   
---------------
 'fat' & 'rat'

在搜索中tsquery中可以使用权重(weight),在搜索词条中可以附加权重,并且匹配出来的查询结果也是必须在这个这个权重范围的。

SELECT to_tsquery('english', 'Fat | Rats:AB');
    to_tsquery    
------------------
 'fat' | 'rat':AB

从上面的例子可以看出,to_tsquery函数在处理查询文本的时候,查询文本的单个词之间要使用逻辑操作符(& (AND), | (OR) and ! (NOT))连接(或者使用括号)。例如 跟上面的例子相似

SELECT to_tsquery('english', 'Fat  Rats');

如果要使执行上面的操作,就会报语法错误。 然而plainto_tsquery函数却可以提供一个标准的tsquery,如上面的例子,plainto_tsquery会自动加上逻辑&操作符。

SELECT plainto_tsquery('english', 'Fat  Rats');
 plainto_tsquery 
-----------------
 'fat' & 'rat'

但是plainto_tsquery函数不能够识别逻辑操作符和权重标记。

SELECT plainto_tsquery('english', 'The Fat & Rats:C');
   plainto_tsquery   
---------------------
 'fat' & 'rat' & 'c'

查询结果关注度(Ranking Search Results)

相关度,就是试图测试衡量哪一个文档是检索中最关注的。因此我们把最匹配的文档现在在最前面。这样才能真正达到检索的准确度,postgresql提供两个相关的函数,ts_rank和ts_rank_cd. 这两个函数的语法是

ts_rank([ weights float4[], ] vector tsvector, query tsquery [, normalization integer ]) returns float4

ts_rank_cd([ weights float4[], ] vector tsvector, query tsquery [, normalization integer ]) returns float4

两个函数的第一个参数都是 权重(weight),在前面已经讲了权重的概念。
参数的格式为 {D-weight, C-weight, B-weight, A-weight},在使用函数的时候没有指定这个参数,postgre会默认指定参数为:{0.1, 0.2, 0.4, 1.0}
这个参数应该理解为:单前检索的关键词(词条)在当前这个检索文档中的位置,如果这个词条在这个当前的位置权重高,那么他的相关度的值也会高。
另外函数的最好一个参数是一个整型,这个参数是表示,这个参数指定文档大小影响相关度的程度。你可以指定一个或者多个例如(2|4)。

这些参数的定义

num means 意思
0 (the default) ignores the document length 表示跟长度大小没有关系
1 divides the rank by 1 + the logarithm of the document length 表示参数 关注度(rank)除以 文档长度的对数+1
2 divides the rank by the document length 表示 关注度 除以 文档的长度
4 divides the rank by the mean harmonic distance between extents (this is implemented only by ts_rank_cd) 表示 关注度 除以 文档长度的平均值,只能使用函数ts_rank_cd.
8 divides the rank by the number of unique words in document 表示 关注度 除以 文档中 唯一分词的数量
16 divides the rank by 1 + the logarithm of the number of unique words in document 表示关注度 除以 唯一分词数量的对数+1
32 divides the rank by itself + 1 表示 关注度 除以 本身+1

其他的一些特性

num means 意思
length(vector tsvector) returns integer 这个函数返回当前索引字段的分词长度,就是分词的个数
strip(vector tsvector) retzitor 这个函数返回当前索引字段的数据不包括词的位置