[ruby on rails] postgresql分词搜索 pg_jieba 和 zhparser 方案

一、 pg_jieba 方案

安装

brew install cmake
mkdir ~/tmp && cd ~/tmp && git clone https://github.com/jaiminpan/pg_jieba && cd pg_jieba
git submodule update --init --recursive
mkdir build && cd build
cmake -DCMAKE_PREFIX_PATH=/usr/local/opt/postgres ..
make install

测试

$ psql -d vapordb
psql (12.2)
Type "help" for help.

@vapordb=# CREATE EXTENSION pg_jieba;
CREATE EXTENSION
@vapordb=# SELECT * FROM to_tsvector('jiebacfg', '小明硕士毕业于中国科学院计算所,后在日本京都大学深造');
                                   to_tsvector
----------------------------------------------------------------------------------
 '中国科学院':5 '小明':1 '日本京都大学':10 '毕业':3 '深造':11 '硕士':2 '计算所':6
(1 row)

@vapordb=# \quit

在测试时,可以感觉到 jieba 的第一次分词有明显的延迟和卡顿,可以通过 Postgresq 预加载 jieba 的动态库和配置文件改善(/usr/local/var/postgres/postgresql.conf)。

#------------------------------------------------------------------------------
# CUSTOMIZED OPTIONS
#------------------------------------------------------------------------------

# Add settings for extensions here
# pg_jieba
shared_preload_libraries = 'pg_jieba.so'  # (change requires restart)
# default_text_search_config='pg_catalog.simple'; default value
default_text_search_config='jiebacfg'; uncomment to make 'jiebacfg' as default

二、 zhparser 方案

mac 安装 scws

brew install scws
scws -v
  • intel 安装在/usr/local 下
  • M1安装在/opt/homebrew 下,M1需要把相关文件拷贝给/usr/local

下载词典文件

mkdir -p /usr/local/etc/scws
curl "http://www.xunsearch.com/scws/down/scws-dict-chs-utf8.tar.bz2" | tar xvjf -
mv dict.utf8.xdb /usr/local/etc/scws/  #需要装在 /usr/local 下

测试效果

scws -c utf8 -d /usr/local/etc/scws/dict.utf8.xdb -r /usr/local/opt/scws/etc/rules.utf8.ini -M 9 "PostgreSQL 自带有一个简易的全文检索引擎"
PostgreSQL 自带 自 带 有 一个 一 个 简易 简 易 的 全文检索 全文 检索 全 文 检 索 引擎 引 擎
+--[scws(scws-cli/1.2.3)]----------+
| TextLen:   52                  |
| Prepare:   0.0007    (sec)     |
| Segment:   0.0002    (sec)     |
+--------------------------------+

安装 zhparser

mkdir ~/tmp && cd ~/tmp
git clone https://github.com/amutu/zhparser.git && cd zhparser
which pg_config
/usr/local/opt/libpq/bin/pg_config
PG_CONFIG=/usr/local/opt/libpq/bin/pg_config make && make install

测试 zhparser

$ psql -d vapordb
psql (12.2)
Type "help" for help.

CREATE EXTENSION zhparser;
#CREATE EXTENSION
CREATE TEXT SEARCH CONFIGURATION zhcfg (PARSER = zhparser);  
#CREATE TEXT SEARCH CONFIGURATION
#  添加名词(n)、动词(v)、形容词(a)、成语(i)、叹词(e)、习用语(l)和简称(j)七种分词策略:
ALTER TEXT SEARCH CONFIGURATION zhcfg ADD MAPPING FOR n,v,a,i,e,l,j WITH simple;
#ALTER TEXT SEARCH CONFIGURATION
SELECT to_tsvector('zhcfg', '人生苦短,我用 Python');
               to_tsvector
------------------------------------------
 'python':5 '人生':1 '用':4 '短':3 '苦':2
(1 row)

@vapordb=# \quit
  • 高亮
Post.select("content, ts_headline('zhcfg', content, '父亲', 'StartSel=<mark>, StopSel=</mark>, HighlightAll=true, MaxWords=35, MinWords=3, ShortWord=3, MaxFragments=0, FragmentDelimiter======') mark_content").where("to_tsvector('zhcfg', content) @@ plainto_tsquery('zhcfg', ?)", '父亲')
   									 ts_headline
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
 昏黄的路灯,照着他的<mark>父亲</mark>,他偎在那个墙角,身下垫着不知从哪里拣来的破纸箱。此刻,他正把身上的棉衣裹了又裹,而自己高中时围过的围巾,紧紧地缠在<mark>父亲</mark>头上。
(1 row)
  • StartSel, StopSel:该字符串分隔文档中出现的查询词,以区别于其他摘录词。 如果它们含有空格或逗号,你必须用双引号字符串。
  • MaxWords, MinWords:这些数字决定最长和最短的标题输出。
  • ShortWord:这个长度或更短的词在标题的开始和结束被丢弃。三个默认值消除了常见英语文章。
  • HighlightAll:布尔标志;如果为真,整个文档将作为标题,忽略了前面的三个参数。
  • MaxFragments:要显示的文本摘录或片段的最大数量。默认值零选择非片段标题的生成方法。 一个大于零的值选择基于片段的标题生成。此方法查找文本片段与尽可能多的查询词并在查询词周围延伸这些片段。 作为查询词的结果接近每一片段中间,每边都有词。每个片段至多是MaxWords ,并且长度为ShortWord或更短的词在每一个片段开始和结束被丢弃。 如果不是所有的查询词在文档中找到,则文档中开头的MinWords单片段将被显示。
  • FragmentDelimiter:当一个以上的片段显示时,通过字符串分隔这些片段。
  • 默认:StartSel=<b>, StopSel=</b>, MaxWords=35, MinWords=15, ShortWord=3, HighlightAll=FALSE, MaxFragments=0, FragmentDelimiter=" … "
  • zhcfg的一些配置
# 查看安装的解析器
select * from pg_ts_parser;
# 查看安装的全文搜索插件
select * from pg_ts_config;
# 查看zhparser支持的字典类型
select * from pg_catalog.ts_token_type('zhparser');  
# 查看已配置的字典映射
\dF+ zhcfg  
select * from pg_ts_config_map where mapcfg=(select oid from pg_ts_config where cfgname='zhcfg');
# 查看用到的字典类型
select ts_debug('zhcfg','三一') ;
可以查看到分词的token类型 如果不在之前的mapping内的话是不会被分词的

# 添加字典映射
ALTER TEXT SEARCH CONFIGURATION zhcfg ADD MAPPING FOR n,v,a,i,e,l,j WITH simple; #常用
ALTER TEXT SEARCH CONFIGURATION zhcfg ADD MAPPING FOR a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z WITH simple; #全部添加
# 字典对应意思
(97,  a, "adjective,        形容词")
(98,  b, "differentiation,  区别词")
(99,  c, "conjunction,      连词")
(100, d, "adverb,           副词")
(101, e, "exclamation,      感叹词")
(102, f, "position,         方位词")
(103, g, "root,             词根")
(104, h, "head,             前连接成分")
(105, i, "idiom,            成语")
(106, j, "abbreviation,     简称")
(107, k, "tail,             后连接成分")
(108, l, "tmp,              习用语")
(109, m, "numeral,          数词")
(110, n, "noun,             名词")
(111, o, "onomatopoeia,     拟声词")
(112, p, "prepositional,    介词")
(113, q, "quantity,         量词")
(114, r, "pronoun,          代词")
(115, s, "space,            处所词")
(116, t, "time,             时语素")
(117, u, "auxiliary,        助词")
(118, v, "verb,             动词")
(119, w, "punctuation,      标点符号")
(120, x, "unknown,          未知词")
(121, y, "modal,            语气词")
(122, z, "status,           状态词")

# 删除字典映射
ALTER TEXT SEARCH CONFIGURATION zhcfg DROP MAPPING FOR m;
# 忽略所有的标点等特殊符号
set zhparser.punctuation_ignore = on;
# 全部单字复合
set zhparser.multi_zall = on;
# 散字二元复合
set zhparser.multi_duality = on;
# 闲散文字自动以二字分词法聚合
set zhparser.seg_with_duality = on;
# 短词复合
set zhparser.multi_short = on;
# 重要单字复合
set zhparser.multi_zmain = on;
  • 自定义词库
    所有的自定义词都放在了 zhprs_custom_word 表里面,默认添加的自定义词的词性是 (120, x, “unknown, 未知词”),可能用到的 sql 包含但不局限于如下:
# 添加自定义词
insert into zhparser.zhprs_custom_word values('资金压力');
# 自定义词库也支持停止词功能,例如我们不希望词语'这是'单独作为一个分词,同样可以在自定义词库中插入对应的词语和控制符停止特定分词:
insert into zhparser.zhprs_custom_word(word, attr) values('这是','!');
# 删除自定义词
delete from zhparser.zhprs_custom_word where word = '安全高效';

# 添加/删除自定义分词之后需要执行以下命令才能使词库生效
select sync_zhprs_custom_word();

# 查询已存在的自定义词库
select * from zhparser.zhprs_custom_word;
  • 阿里云自定义词库
-- 初始的分词结果
SELECT to_tsquery('testzhcfg', '保障房资金压力');
-- 往自定义分词词典里面插入新的分词
insert into pg_ts_custom_word values ('保障房资');
-- 删除分词
delete from pg_ts_custom_word where word = '非金镶';
-- 使新的分词生效
select zhprs_sync_dict_xdb();
-- 退出此连接
\c
-- 重新查询,可以得到新的分词结果
SELECT to_tsquery('testzhcfg', '保障房资金压力');

阿里云rds自带pg_jieba和zhparser扩展,使用前需要先按要求设置参数shared_preload_libraries,然后创建扩展就行 阿里云rds中文分词链接

三、对比

两种方案效果上差不多.

$ psql -d vapordb
psql (12.2)
Type "help" for help.

@vapordb=# SELECT * FROM to_tsvector('jiebacfg', '小明硕士毕业于中国科学院计算所,后在日本京都大学深造');
                                   to_tsvector
----------------------------------------------------------------------------------
 '中国科学院':5 '小明':1 '日本京都大学':10 '毕业':3 '深造':11 '硕士':2 '计算所':6
(1 row)

@vapordb=# SELECT * FROM to_tsvector('zhcfg', '小明硕士毕业于中国科学院计算所,后在日本京都大学深造');
                                to_tsvector
---------------------------------------------------------------------------
 '中国科学院计算所':4 '小明':1 '日本京都大学':5 '毕业':3 '深造':6 '硕士':2
(1 row)

@vapordb=# \quit

四、如何使用

对于全文检索,有两种使用方式,大家可以权衡自己的内容进行选择。

  1. 在搜索的时候进行分词,然后搜索对应的字段。
  2. 提前把表中需要检索的字段进行分词,保存到一个新的字段中,再在这个字段上建立索引进行搜。

两种方案就是时间和空间的取舍:第一种方式创建索引简单,存储空间少,但是比较慢。第二种方案由于预先进行了分词并存储,浪费了空间,但是时间上肯定用得少。创建索引也有两种方案:gin 索引和 rum 索引。

第一种

创建索引:

CREATE INDEX idx_xxxx ON xxxx_table USING gin(to_tsvector('jiebacfg',
COALESCE(xx_field, '') || COALESCE(xxx_field, '')));

查询:

EXPLAIN ANALYSE SELECT * FROM xxxx_table
        WHERE to_tsvector('jiebacfg', COALESCE(xx_field, '') || COALESCE(xxx_field, '')) @@
        to_tsquery('jiebacfg', '关键字或者句子');

第二种

创建 tsv 字段和索引

ALTER TABLE xxxx_table ADD COLUMN tsv tsvector;
UPDATE xxxx_table SET tsv_field = to_tsvector('jiebacfg', COALESCE(xx_field, '') || COALESCE(xxx_field, ''));
CREATE INDEX idx_xxxx ON xxxx_table USING gin(tsv_field);

查询:

EXPLAIN ANALYSE SELECT * FROM xxxx_table WHERE tsv_field @@ to_tsquery('jiebacfg', '关键词或者句子');

当然因为是预先分词保存,所以需要在 update 的时候藉由 触发器 来更新 tsv 字段,。

CREATE TRIGGER tsvector_update BEFORE INSERT OR UPDATE
       ON xxxx_table FOR EACH ROW  EXECUTE PROCEDURE tsvector_update_trigger('tsv_field', 'jiebacfg', 'xx_field', 'xxx_field');

rum 索引

使用 rum 索引类似, 但是 rum 引擎默认是没有安装的,需要自己编译,暂时先不用了。

CREATE INDEX idx_xxxx ON xxxx_table USING rum(tsv_field rum_tsvector_ops);

另外 rum 还支持相似度的查询:

SELECT * FROM to_tsvector('jiebacfg', '小明硕士毕业于中国科学院计算所,后在日本京都大学深造');
SELECT * FROM rum_ts_distance(to_tsvector('jiebacfg', '小明硕士毕业于中国科学院计算所,后在日本京都大学深造') , to_tsquery('计算所'));
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用方法:打开scws-1.2.3\win32里的解决方案文件,里面包括了scws和zhparser,另外一个是scws的php扩展不用编译 zhparserPostgresql中进行中文分词的常用扩展,网上的相关资料很多,安装的教程也不少,但大多数是linux的,并没有windows的安装介绍。原因有两个方面,一个是本身像这种数据库服务器一般都是linux系统的,另外一个比较致命,zhparser本身并没提供windows环境下的编译工程,连依赖库scws的readme里也建议在linux环境下使用,或者用cygwin或mingw一类工具。对于博主这种只使用windows环境(其实Postgresql也是刚接触),连makefile也搞不明白的真是头大。好在万变不离其中,编译环境只是工具,只要搞清楚个中原理(看代码),移植到windows+VS的环境应该也不是难事(当然这个也只是在轻量级的库下面适用)。下面进入正题: 干货在这里 ① 编译scws:由于 zhparser是基于scws(scws是简易中文分词系统的缩写,它的原理其实很简单,基于词典,将文本中的内容按照词典进行分词,提取关键字等。)做的分词,因此先要编译scws,网上下载下来的源码有vs2008的版本,因此,直接编译,后来出现头文件无法加入的错误,将相关文件编码方式修改成unicode解决(利用notepad++ 编码->转换为UTF-8编码)。 ② 编译zhparser:由于zhparser只提供了linux下面的makefile文件,想着用Mingw去编译,后来因为postgresql所在目录有空格,导致编译过程也失败。只好到vs2008里自建一个工程,建好工程后,首先把scws的头文件和库文件加进去,然后把postgresql的头文件和库文件加进去,设置项目属性为生成dll文件。 ③ 扩展安装:编译成功以后,在postgresql下面运行:create extension zhparser; 提示找不到control文件,在下载的源码文件夹里找到该文件,放到指定目录,再次运行sql,提示找不到dll文件,将dll文件放到指定目录,再运行,提示找不到zhprs_start函数,看来是dll没有正常explort出函数,修改源代码中的相关函数声明,再次运行后,显示成功。但zhparser源文件下其实还有很多文件,包括一个词典文件和ini配制文件,还不知道应该放在何处。 ④ zhparser运行环境配制:运行测试sql语句,发现并没有实现分词,想起来应该是词典位置不正确,到zhparser源码中去搜寻,发现如下代码,看来是放到tsearch_data目录下面,用同样的方法找到ini文件的目录,将它们都放进去,再次运行测试代码,得到正确结果。 测试代码: REATE EXTENSION zhparser; -- make test configuration using parser CREATE TEXT SEARCH CONFIGURATION testzhcfg (PARSER = zhparser); ALTER TEXT SEARCH CONFIGURATION testzhcfg ADD MAPPING FOR n,v,a,i,e,l WITH simple; select to_tsvector('testzhcfg','南京市长江大桥');

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值