文本搜索(~、~*、LIKE和ILIKE操作符)在对文本进行搜索操作时,缺少一些系统要求的必要属性:
- 及时是英文环境也没有语义支持;
由于要识别派生词并不是那么容易,因此正则表达式也不能满足要求。如,satisfies和satisfy,当使用正则表达式寻找satisfy时,并不会查询到包含satisfies的文档。用户可以使用OR搜索多种派生形式,但过程非常繁琐。并且有些词会有上千的派生词,因此容易出错。
- 没有对搜索结果的分类(排序)。当搜索出成千的文档时,查找效率很低。
- 由于没有索引的支持,每一次的搜索需要遍历所有的文档,整体搜索比较缓慢。
使用全文索引可以对文档进行预处理,并且可以使后续的搜索更快速。预处理过程包括:
- 将文档解析成记号:
为每个文档标记不同类别的记号是非常有必要的,例如:数字、文字、复合词、电子邮件地址,这样就可以做不同的处理。原则记号的类别依赖于具体的应用,但对于大多数的应用来说,可以使用一组预定义的记号类。
- 将记号转换为词位:
词位像记号一样是一个字符串,但它已经标准化处理,这样同一个词的不同形式是一样的。例如,标准化通常包括:将大写字母折成小写字母、删除后缀(如英语中的s或者es)。这将允许通过搜索找到同一个词的不同形式,不需要繁琐地输入所有可能的变形样式。同时,这一步通常会删除停用词。这些停用词通常因为太常见而对搜索无用。(总之,记号是文档文本的原片段,而词位被认为是有用的索引和搜索词。)GBase 8c使用词典执行这一步,且提供了各种标准的词典。
- 保存搜索优化后的预处理文档。
比如,每个文档可以呈现为标准化词位的有序组合。伴随词位,通常还需要存储词位位置信息以用于邻近排序。因此文档包含的查询词越密集其排序越高。
词典能够对词位如何标准化做到细粒度控制。使用合适的词典,可以定义不被索引的停用词。
数据类型tsvector用于存储预处理文档,tsquery用于存储查询条件,详细请参见控制文本搜索。为这些数据类型提供的函数和操作符请参见函数和操作符。其中最重要的是匹配运算符@@,将在基本文本匹配中介绍。
文档是全文搜索系统的搜索单元,例如:杂志上的一篇文章或电子邮件消息。文本搜索引擎必须能够解析文档,而且可以存储父文档的关联词素(关键词)。后续,这些关联词位用来搜索包含查询词的文档。
文档通常是一个数据库表中一行的文本字段,或者这些字段的可能组合(级联)。文档可能存储在多个表中或者需动态获取。换句话说,一个文档由被索引化的不同部分构成,因此无法存储为一个整体。比如:
gbase=# SELECT name || ' ' || age || ' ' || address || ' ' || salary AS document FROM company WHERE id = 2;
document
----------------------
Allen 25 Texas 15000
(1 row)
注意:实际上,在这些示例查询中,应该使用coalesce防止一个独立的NULL属性导致整个文档的NULL结果。
另外一种可能是:文档在文件系统中作为简单的文本文件存储。在这种情况下,数据库可以用于存储全文索引并且执行搜索,同时可以使用一些唯一标识从文件系统中检索文档。然而,从数据库外部检索文件需要拥有系统管理员权限或者特殊函数支持。因此,还是将所有数据保存在数据库中比较方便。同时,将所有数据保存在数据库中可以方便地访问文档元数据以便于索引和显示。
为了实现文本搜索目的,必须将每个文档减少至预处理后的tsvector格式。搜索和相关性排序都是在tsvector形式的文档上执行的。原始文档只有在被选中要呈现给用户时才会被当检索。因此,我们常将tsvector说成文档,但是很显然其实它只是完整文档的一种紧凑表示。
GBase 8c的全文搜索基于匹配操作符@@,它在一个tsvector(文档)匹配一个tsquery(查询)时返回true。例如:
gbase=# SELECT 'a fat cat sat on a mat and ate a fat rat'::tsvector @@ 'cat & rat'::tsquery;
?column?
----------
t
(1 row)
gbase=# SELECT 'fat & cow'::tsquery @@ 'a fat cat sat on a mat and ate a fat rat'::tsvector;
?column?
----------
f
(1 row)
tsquery可以包含搜索术语,并且可以使用 AND 、OR、NOT 以及 FOLLOWED BY 操作符结合多个术语。函数to_tsquery、plainto_tsquery以及phraseto_tsquery可用于将用户书写的文本转换为正确的tsquery。文本搜索匹配:
gbase=# SELECT to_tsvector('fat cats ate fat rats') @@ to_tsquery('fat & rat');
?column?
----------
f
(1 row)
@@操作符也支持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)。
在<->(FOLLOWED BY) tsquery操作符的帮助下搜索可能的短语,只有该操作符的参数的匹配是相邻的并且符合给定顺序时,该操作符才算是匹配。例如:
gbase=# SELECT to_tsvector('fatal error') @@ to_tsquery('fatal <-> error');
?column?
----------
t
(1 row)
gbase=# SELECT to_tsvector('error is not fatal') @@ to_tsquery('fatal <-> error');
?column?
----------
f
(1 row)
FOLLOWED BY 操作符还有一种更一般的版本,形式是<N>,其中N是一个表示匹配词位位置之间的差。<1>和<->相同,而<2>允许刚好一个其他词位出现在匹配之间,以此类推。当有些词是停用词时,phraseto_tsquery函数利用这个操作符来构造一个能够匹配多词短语的tsquery。例如:
gbase=# SELECT phraseto_tsquery('cats ate rats');
phraseto_tsquery
-----------------------------
'cats' <-> 'ate' <-> 'rats'
(1 row)
gbase=# SELECT phraseto_tsquery('the cats ate the rats');
phraseto_tsquery
-------------------------------------------------
'the' <-> 'cats' <-> 'ate' <-> 'the' <-> 'rats'
(1 row)
<0>可以被用来要求两个匹配同一个词的模式。
圆括号可以被用来控制tsquery操作符的嵌套。如果没有圆括号,|的计算优先级最低,然后从低到高依次是&、<->、!。
全文检索功能还可以做更多事情:忽略索引某个词(停用词),处理同义词和使用复杂解析,例如:不仅基于空格的解析。这些功能通过文本搜索分词器控制。GBase 8c支持多语言的预定义的分词器,并且可以创建分词器(gsql的\dF命令显示了所有可用分词器)。
在安装期间选择一个合适的分词器,并且在postgresql.conf中相应的设置default_text_search_config。如果为了整个集群使用同一个文本搜索分词器可以使用postgresql.conf中的值。如果需要在集群中使用不同分词器,可以使用ALTER DATABASE ... SET在任一数据库进行配置。用户也可以在每个会话中设置default_text_search_config。
每个依赖于分词器的文本搜索函数有一个可选的配置参数,用以明确声明所使用的分词器。仅当忽略这个参数的时候,才使用default_text_search_config。
为了更方便的建立自定义文本搜索分词器,可以通过简单的数据库对象建立分词器。GBase 8c文本搜索功能提供了四种类型与分词器相关的数据库对象:
- 文本搜索解析器将文档分解为词位,并且分类每个词位(例如:词和数字)。
- 文本搜索词典将词位转换成规范格式并且丢弃停用词。
- 文本搜索模板提供潜在的词典功能:一个词典指定一个模板,并且为模板设置参数。
- 文本搜索分词器选择一个解析器,并且使用一系列词典规范化语法分析器产生的词位。
在没有索引的情况下的全文检索:查询文档中每行title匹配friend:
gbase=# SELECT * FROM pgweb;
id | body | title | last_mod_date
----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------+---------------
3 | England is a country that is part of the United Kingdom. It shares land borders with Scotland to the north and Wales to the west. | England | 2010-01-01
5 | Russia, also officially known as the Russian Federation, is a sovereign state in northern Eurasia. | Russia | 2010-01-01
1 | China, officially the People's Republic of China(PRC), located in Asia, is the world's most populous state. | China | 2010-01-01
1 | China, officially the People's Republic of China(PRC), located in Asia, is the world's most populous state. | China | 2010-01-01
4 | Australia, officially the Commonwealth of Australia, is a country comprising the mainland of the Australian continent, the island of Tasmania, and numerous smaller islands. | Australia | 2010-01-01
8 | France, is a sovereign state comprising territory in western Europe and several overseas regions and territories. | France | 2010-01-01
9 | Italy officially the Italian Republic, is a unitary parliamentary republic in Europe. | Italy | 2010-01-01
10 | India, officially the Republic of India, is a country in South Asia. | India | 2010-01-01
13 | Mexico, officially the United Mexican States, is a federal republic in the southern part of North America. | Mexico | 2010-01-01
2 | America is a rock band, formed in England in 1970 by multi-instrumentalists Dewey Bunnell, Dan Peek, and Gerry Beckley. | America | 2010-01-01
6 | Japan is an island country in East Asia. | Japan | 2010-01-01
7 | Germany, officially the Federal Republic of Germany, is a sovereign state and federal parliamentary republic in central-western Europe. | Germany | 2010-01-01
11 | Brazil, officially the Federative Republic of Brazil,is the largest country in both South America and Latin America. | Brazil | 2010-01-01
12 | Canada is a country in the northern half of North America. | Canada | 2010-01-01
(14 rows)
gbase=# SELECT id, body, title FROM pgweb WHERE to_tsvector('english', body) @@to_tsquery('english', 'america');
id | body | title
----+-------------------------------------------------------------------------------------------------------------------------+---------
13 | Mexico, officially the United Mexican States, is a federal republic in the southern part of North America. | Mexico
2 | America is a rock band, formed in England in 1970 by multi-instrumentalists Dewey Bunnell, Dan Peek, and Gerry Beckley. | America
11 | Brazil, officially the Federative Republic of Brazil,is the largest country in both South America and Latin America. | Brazil
12 | Canada is a country in the northern half of North America. | Canada
(4 rows)
该查询还会返回friends和friendly等类似的结果。
上面的查询指定使用english配置参数。也可以忽略配置参数:
gbase=# SELECT id, body, title FROM pgweb WHERE to_tsvector(body) @@to_tsquery('america');
id | body | title
----+-------------------------------------------------------------------------------------------------------------------------+---------
13 | Mexico, officially the United Mexican States, is a federal republic in the southern part of North America. | Mexico
2 | America is a rock band, formed in England in 1970 by multi-instrumentalists Dewey Bunnell, Dan Peek, and Gerry Beckley. | America
11 | Brazil, officially the Federative Republic of Brazil,is the largest country in both South America and Latin America. | Brazil
12 | Canada is a country in the northern half of North America. | Canada
(4 rows)
这个查询将使用由default_text_search_config设置的配置。
可以创建一个GIN索引来加速文本搜索:
gbase=# CREATE INDEX pgweb_idx ON pgweb USING GIN(to_tsvector('english', body));
CREATE INDEX
to_tsvector()函数有两个版本。只输一个参数的版本和输两个参数的版本。只输一个参数时,系统默认采用default_text_search_config所指定的分词器。
注意:创建索引时必须使用to_tsvector的两参数版本。只有指定了分词器名称的全文检索函数才可以在索引表达式中使用。这是因为索引的内容必须不受default_text_search_config的影响,否则索引内容可能不一致。由于default_text_search_config的值可以随时调整,从而导致不同条目生成的tsvector采用了不同的分词器,并且没有办法区分究竟使用了哪个分词器。正确地转储和恢复这样的索引也是不可能的。
因为在上述创建索引中to_tsvector使用了两个参数,只有当查询时也使用了两个参数,且参数值与索引中相同时,才会使用该索引。也就是说,WHERE to_tsvector('english',body) @@ 'a & b' 可以使用索引,但WHERE to_tsvector(body) @@ 'a & b'不能使用索引。这确保只使用这样的索引——索引各条目是使用相同的分词器创建的。
索引中的分词器名称由另一列指定时可以创建更复杂的表达式索引,例如:
gbase=# CREATE INDEX pgweb_idx_2 ON pgweb USING GIN(to_tsvector(config_name, body));
CREATE INDEX
这里config_name是pgweb表中的一个列。可以在同一个索引中混合配置,同时记录配置所属的索引项。
索引可以连接列:
gbase=# CREATE INDEX pgweb_idx_3 ON pgweb USING GIN(to_tsvector('english', title || ' ' || body));
CREATE INDEX
另一种方法是创建一个单独的tsvector列来保存to_tsvector的输出。这个例子是title和body的连接,使用coalesce来保证当其他域为NULL时一个域仍然能留在索引中:
gbase=# ALTER TABLE pgweb ADD COLUMN textsearchable_index_col tsvector;
ALTER TABLE
gbase=# UPDATE pgweb SET textsearchable