PostgreSQL自带有一个简易的全文检索引擎,能够实现小规模数据量的全文检索功能。本文咱们将引导介绍一下这个功能,对于小数据量的搜索这个功能是足够使用的,而无需搭建额外的ES等重量级的全文检索服务器。html
PG的全文检索操做符是@@
,当一个tsvector
(文档)和tsquery
(条件)匹配时返回true
,而且先后顺序无影响:github
SELECT 'a fat cat sat on a mat and ate a fat rat'::tsvector @@ 'cat & rat'::tsquery; ?column? ---------- t SELECT 'fat & cow'::tsquery @@ 'a fat cat sat on a mat and ate a fat rat'::tsvector; ?column? ---------- f
PostgreSQL 10开始,
jsonb
也支持全文检索了。web
和普通的SQL查询同样,只要在WHERE
条件中使用这个符号就表明使用全文检索条件筛选文档了。如:sql
SELECT title FROM pgweb WHERE to_tsvector('english', body) @@ to_tsquery('english', 'friend');
@@
操做符支持隐式转换,对于text
类型能够无需强类型转换(::tsvector
或to_tsvector(config_name, text)
),因此这个操做符实际支持的参数类型是这样的:json
tsvector @@ tsquery tsquery @@ tsvector text @@ tsquery text @@ text
NOTE: 实际使用中,建议使用
to_tsvector(config_name, text)
和to_tsquery(config_name, text)
这种显式强类型转换的形式。由于若是使用隐式转换或::tsvector
这种默认参数转换,将使用default_text_search_config配置分词,该配置默认是pg_catalog.simple
,可能对于绝大多数查询场景并不适用。所以推荐使用to_tsvector()
和to_tsquery()
函数显式强类型转换,而且指明分词字典,已实现更精确的查询需求。服务器
tsquery查询条件并非简单的正则,而是一组搜索术语,使用而且使用布尔操做符&
(AND)、|
(OR)和!
(NOT)来组合它们,还有短语搜索操做符<->
(FOLLOWED BY)。更详细的语法参见此文档。app
此外,PostgreSQL还提供了两个相对简化的版本plainto_tsquery
和phraseto_tsquery
。函数
plainto_tsquery
(plainto_tsquery([ config regconfig, ] querytext text) returns tsquery
)用户将未格式化的text通过分词以后,插入&
符号转为tsquery
:post
SELECT plainto_tsquery('english', 'The Fat Rats'); plainto_tsquery ----------------- 'fat' & 'rat'
phraseto_tsquery
(phraseto_tsquery([ config regconfig, ] querytext text) returns tsquery
)行为和plainto_tsquery
行为相似,可是分词以后不是插入&
而是<->
(FOLLOWED BY):
SELECT phraseto_tsquery('english', 'The Fat Rats'); phraseto_tsquery ------------------ 'fat' <-> 'rat'
使用索引能够加快全文检索的速度。对于全文检索来讲,可选的索引类型是GIN
(通用倒排索引)和GIST
(通用搜索树),官方文档更推荐使用GIN索引。建立一个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
列,为这个列建立索引:
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);
NOTE:
- 建立一个基于
GIN
(通用倒排索引)的索引,column必须是tsvector类型。因此须要对列进行显式类型转换。- 使用了
to_tsvector()
函数的双参数版本指定了全文检索配置,所以必须使用to_tsvector()
相同全文检索配置才能命中索引。比即,WHERE to_tsvector('english', body) @@ 'a & b'
可使用该索引,但WHERE to_tsvector(body) @@ 'a & b'
不能。- 在使用一个单独的列来存储
tsvector
表示时,有必要建立一个触发器在title
或body
改变时保证tsvector
列为当前值。详见文档。- GIN 索引只存储
tsvector
值的词(词位),而且不存储它们的权重标签。所以, 在使用涉及权重的查询时须要一次在表行上的从新检查。
除了普通的ORDER BY
条件以外,PostgreSQL为全文检索提供了两个可选的排序函数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
,以便实现基于权重
的排序。
SELECT title, ts_rank_cd(textsearch, query) AS rank FROM apod, to_tsquery('neutrino|(dark & matter)') query WHERE query @@ textsearch ORDER BY rank DESC LIMIT 10; title | rank -----------------------------------------------+---------- Neutrinos in the Sun | 3.1 The Sudbury Neutrino Detector | 2.4 A MACHO View of Galactic Dark Matter | 2.01317 Hot Gas and Dark Matter | 1.91171 The Virgo Cluster: Hot Plasma and Dark Matter | 1.90953 Rafting for Solar Neutrinos | 1.9 NGC 4650A: Strange Galaxy and Dark Matter | 1.85774 Hot Gas and Dark Matter | 1.6123 Ice Fishing for Cosmic Neutrinos | 1.6 Weak Lensing Distorts the Universe | 0.818218
此外,对于PostgreSQL 9.6以上的版本还可使用RUM index排序。(注意,这个是扩展,默认不包含)。
参考文档: http://www.postgres.cn/docs/10/textsearch-controls.html#TEXTSEARCH-RANKING
PostgreSQL默认的分词字典中并不包含中文分词字典,所以咱们必须手工引入。目前一个比较好的项目是zhparser,同时这个插件也是阿里云的RDS默认包含的。安装和启用没什么好说的。值得一提的是分词配置参数。
在CREATE EXTENSION
以后,必须配置分词参数才能正确进行分词和查找,不然什么都查不到。官方文档提供的一个配置策略是:
CREATE TEXT SEARCH CONFIGURATION testzhcfg (PARSER = zhparser); ALTER TEXT SEARCH CONFIGURATION testzhcfg ADD MAPPING FOR n,v,a,i,e,l WITH simple;
n,v,a,i,e,l
这几个字母分别表示一种token策略,只启用了这几种token mapping,其他则被屏蔽。具体支持的参数和含义能够用\dFp+ zhparser
显示:
postgres=# \dFp+ zhparser Text search parser "public.zhparser" Method | Function | Description -----------------+-----------------+------------- Start parse | zhprs_start | Get next token | zhprs_getlexeme | End parse | zhprs_end | Get headline | prsd_headline | (internal) Get token types | zhprs_lextype | Token types for parser "public.zhparser" Token name | Description ------------+------------------------ a | adjective,形容词 b | differentiation,区别词 c | conjunction,连词 d | adverb,副词 e | exclamation,感叹词 f | position,方位词 g | root,词根 h | head,前链接成分 i | idiom,成语 j | abbreviation,简称 k | tail,后链接成分 l | tmp,习用语 m | numeral,数词 n | noun,名词 o | onomatopoeia,拟声词 p | prepositional,介词 q | quantity,量词 r | pronoun,代词 s | space,处所词 t | time,时语素 u | auxiliary,助词 v | verb,动词 w | punctuation,标点符号 x | unknown,未知词 y | modal,语气词 z | status,状态词 (26 rows)
WITH simple
表示词典使用的是内置的simple词典,即仅作小写转换。根据须要能够灵活定义词典和token映射,以实现屏蔽词和同义词归并等功能。
好比咱们看下面这个例子:
-- 如下这个全文检索配置来源于zhparser文档的范例 ALTER TEXT SEARCH CONFIGURATION zhparser ADD MAPPING FOR n,v,a,i,e,l WITH simple; postgres=# SELECT to_tsquery('zhparser', '江淮杜伏威'); to_tsquery -------------------- '杜' & '伏' & '威' (1 row)
能够看到江淮
这个词组在查询的时候被忽略了,咱们启用j
(abbreviation,简称)再看看结果:
postgres=# ALTER TEXT SEARCH CONFIGURATION zhparser ADD MAPPING FOR j WITH simple; ALTER TEXT SEARCH CONFIGURATION postgres=# SELECT to_tsquery('zhparser', '江淮杜伏威'); to_tsquery ----------------------------- '江淮' & '杜' & '伏' & '威' (1 row)
因此实际使用中要设置合理的token types,过少将致使搜索结果不许确,过多将致使性能降低。此外,还有一些诸如短词复合: zhparser.multi_short = f
这一类的控制分词结果的选项,根据实际使用酌情开启。