PostgreSQL - 全文检索内置及自定义ranking算法介绍 与案例


标签

PostgreSQL , 全文检索 , ranking


背景

《用PostgreSQL 做实时高效 搜索引擎 - 全文检索、模糊查询、正则查询、相似查询、ADHOC查询》

《排序算法》这个章节实际上介绍了PostgreSQL的ranking算法。

tsvector将文档分为4层结构:标题、作者、摘要、内容。对这四个层级,用户可以设定对应的weight,用于ranking的计算。同时用户可以设定ranking的修正掩码。

但是只有四个层级,远远不能满足业务需求,那么PostgreSQL实现更多层级,更精细的ranking算法呢?

如何自定义ranking呢?

例如在电商行业中,我们可能存储了每个店铺的标签,每个标签旁边可能是一个系数,这个系数可能是动态调整的。在搜索的时候,商店的某些标签被命中了,可能搜索到了几百万个店铺,但是最后要根据权重算出应该如何排序,并得到其中的1万个店铺。

这种情况,如何精细化排序就体现出来了。

例子1 - tsvector向量

1、店铺标签表:

create table tbl (
  shop_id int8 primary key,   -- 店铺 ID
  tags text                   -- 多值类型,标签1:评分1,标签2:评分2,.....
);

对tags字段,使用UDF索引,存储标签的数组或tsvector索引。

如果使用tsvector,主要是便于使用PostgreSQL的全文检索的语法,包含、不包含、距离等。

tags例如

国民_足浴:0.99,国民_餐饮:0.1,娱乐_KTV:0.45

2、标签权值表:

create table tbl_weight (
  tagid int primary key,   -- 标签ID
  tagname name,            -- 标签名
  desc text,               -- 标签描述
  weight float8            -- 标签权值
);    

create index idx_tbl_weight_1 on tbl_weight (tagname);

3、文本转标签数组、tsvector的UDF

create or replace function text_to_tsvector(text) returns tsvector as $$
  select array_to_tsvector(array_agg(substring(id,'(.+):'))) from unnest(regexp_split_to_array($1, ',')) as t(id);
$$ language sql strict immutable;    

postgres=# select text_to_tsvector('abc:1.1,bc:100,c:293');
 text_to_tsvector
------------------
 'abc' 'bc' 'c'
(1 row)

创建TSVECTOR表达式索引

create index idx_tbl_1 on tbl using gin (text_to_tsvector(tags));

4、取出命中标签权值的UDF

postgres=# select substring('bc:1.1,abc:100,c:293','[^,]?abc:([\d\.]+)') ;
 substring
-----------
 100
(1 row)    

postgres=# select substring('abc:1.1,bc:100,c:293','[^,]?abc:([\d\.]+)') ;
 substring
-----------
 1.1
(1 row)

未命中则返回NULL

postgres=# select substring('abc:1.1,bc:100,c:293','[^,]?adbc:([\d\.]+)') ;
 substring
-----------    

(1 row)    

postgres=# select substring('abc:1.1,bc:100,c:293','[^,]?adbc:([\d\.]+)') is null;
 ?column?
----------
 t
(1 row)

5、全文检索

参考 《用PostgreSQL 做实时高效 搜索引擎 - 全文检索、模糊查询、正则查询、相似查询、ADHOC查询》

select

6、精排

计算ranking可能是结合 “命中的标签、评分、标签本身的权值” 根据算法得到一个ranking值。

根据4得到命中标签的评分,根据命中标签从tbl_weight得到对应的权值。

将算法封装到UDF,最后得到RANKING。

ranking算法的UDF函数内容略,请根据业务的需要编写对应算法,伪代码如下。

create or replace function cat_ranking(tsquery) returns float8 as $$
declare  

begin
  for each x in array (contains_element) loop
    search hit element's score.
    search hit element's weight.
    cat ranking and increment
  end loop;
  return res;
end;
$$ language plpgsql strict;

7、

7.1 删除标签与对应的评分:

regexp_replace 函数。

7.2 追加标签与对应的评分:

concat函数。

7.3 修改元素评分:

regexp_replace 函数。

以上都可以使用正则表达式来操作。

例子2 - 多维数组

使用数组来存储标签和权值,实际上在编程上会比使用tsvector更简单。

首先需要介绍一些用到的数组函数

根据元素求位置,根据标签,求它的位置,根据这个位置从score[]得到它的SCORE。  

postgres=# select array_position(array[1,2,null,null,2,2,3,1],null);
 array_position
----------------
              3
(1 row)  

postgres=# select array_positions(array[1,2,null,null,2,2,3,1],null);
 array_positions
-----------------
 {3,4}
(1 row)  

postgres=# select array_positions(array[1,2,null,null,2,2,3,1],2);
 array_positions
-----------------
 {2,5,6}
(1 row)  

求某个位置的元素  

array[i]  

postgres=# select (array[1,2,null,null,2,2,3,1])[1];
 array
-------
     1
(1 row)  

postgres=# select (array[1,2,null,null,2,2,3,1])[3];
 array
-------  

(1 row)  

postgres=# select (array[1,2,null,null,2,2,3,1])[5];
 array
-------
     2
(1 row)  

追加元素  

array_append  

替换元素  

array_replace  

删除某个元素  

array_remove,注意如果有一样的元素,都会被删掉(如果有一样的score,就的注意,需要用删除位置来删除元素)  

postgres=# select array_remove(array[1,2,null,null,2,2,3,1],2);
   array_remove
-------------------
 {1,NULL,NULL,3,1}
(1 row)  

删除某个位置的元素,  

postgres=# create or replace function array_remove(anyarray,int[]) returns anyarray as $$
  select array(select $1[i] from (select id from generate_series(1,array_length($1,1)) t(id) where id <> all( $2) ) t(i))
$$ language sql strict;
CREATE FUNCTION
postgres=# select array_remove(array[1,2,null,null,2,2,3,1],array[1,2]);
    array_remove
---------------------
 {NULL,NULL,2,2,3,1}
(1 row)  

postgres=# select array_remove(array[1,2,null,null,2,2,3,1],array[3,5]);
   array_remove
------------------
 {1,2,NULL,2,3,1}
(1 row)

1、店铺标签表:

create table tbl (
  shop_id int8 primary key,   -- 店铺 ID
  tags text[],                -- 数组,标签1,标签2,.....
  scores float8[]             -- 数组,评分1,评分2,.....
);     

create index idx_tbl_1 on tbl using gin(tags);
国民_足浴,国民_餐饮,娱乐_KTV  

0.99,0.1,0.45

2、标签权值表:

create table tbl_weight (
  tagid int primary key,   -- 标签ID
  tagname name,            -- 标签名
  desc text,               -- 标签描述
  weight float8            -- 标签权值
);    

create index idx_tbl_weight_1 on tbl_weight (tagname);

3、包含、不包含、相交的数组查询。

https://www.postgresql.org/docs/10/static/functions-array.html

4、精排算法,与例子1类似。自定义UDF即可。

使用array简化了开发工作量,不需要使用正则表达式,效率也会提高。

参考

https://www.postgresql.org/docs/10/static/functions-matching.html

时间: 2024-09-26 16:13:37

PostgreSQL - 全文检索内置及自定义ranking算法介绍 与案例的相关文章

PostgreSQL 10 内置分区 vs pg_pathman

标签 PostgreSQL , 内置分区 , pg_pathman , perf , 性能 , 锁 背景 PostgreSQL 10内置分区的性能不如pg_pathman分区插件的性能.有非常大的优化空间,那么是什么导致了分区的性能问题呢? 编译PostgreSQL 10.0 1.编译.打开debug CFLAGS="-g -ggdb -fno-omit-frame-pointer" ./configure --prefix=/home/digoal/pgsql10.0 CFLAGS=

微信内置浏览器私有接口WeixinJSBridge介绍

这篇文章主要介绍了微信内置浏览器私有接口WeixinJSBridge介绍,本文讲解了发送给好友.分享函数.隐藏工具栏.隐藏三个点按钮等功能,需要的朋友可以参考下     微信网页进入,右上角有三个小点,没错,我们用到的就是它!我们只要通过将小点列表下的按钮进行自定义,就可以随心所欲的分享我们自己的内容了. 注意:(WeixinJSBridge只能在微信内打开的网页有效) 按钮一之------发送给好友  代码如下: function sendMessage(){ WeixinJSBridge.o

PostgreSQL 在路上的特性 - 远离触发器, 拥抱内置分区

之前分享过阿里云RDS PG支持分区表的文章https://yq.aliyun.com/articles/113今天要给大家带来另一个好消息,PostgreSQL 社区版本终于要有集成的分区表特性了,再也不用为写分区触发器烦恼了.(很多人认为PG现有的分区表用法是"惨无人道"的(除了管理不方便,性能也是个问题),就像是一粒老鼠屎,坏了一锅汤.社区终于要把老鼠屎请出去了.) 如果你不care性能,可以看看我以前写的一个通用的分区表触发器函数,一个函数打天下http://blog.163.

C# 2.0新特性探究之模拟泛型和内置算法

算法 在C#2.0中,匿名方法.IEnumerable接口和匿名方法的合作,使很多的编程任务变得非常的简单,而且写出来的程序非常的优美. 比如,我们可以写出如下的代码: List<Book> thelib = Library.getbooks(); List<Book> found = thelib.FindAll(delegate(Book curbook) { if (curbook.isbn.StartsWith("...")) return true;

C# 2.0的模拟泛型和内置算法

在C#2.0中,匿名方法.IEnumerable接口和匿名方法的合作,使很多的编程任务变得非常的简单,而且写出来的程序非常的优美. 比如,我们可以写出如下的代码: List<Book> thelib = Library.getbooks(); List<Book> found = thelib.FindAll(delegate(Book curbook) { if (curbook.isbn.StartsWith("...")) return true; ret

自定义设置win8内置SkyDrive存储位置的方法

  SkyDrive是一项完美地与系统相结合的云存储服务,也是微软提供给用户便利的一项服务.但是在windows8.1中内置的SkyDrive系统默认的存储位置,让有些用户觉得用起来一点都不方便,想换一个存储的位置.那么我们应该怎么样,才能达到目的呢? 自定义设置win8内置SkyDrive存储位置的方法介绍: 打开"这台电脑" 左侧导航栏右键点击skydrive图标,选取"属性" 打开属性菜单后,找到"位置"标签,下方地址栏填写新的存储位置,或

Hive内置运算函数,自定义函数(UDF)和Transform

4.Hive函数 4.1内置运算符 内容较多,见<Hive官方文档>   4.2内置函数 内容较多,见<Hive官方文档> https://cwiki.apache.org/confluence/display/Hive/LanguageManual+UDF   测试各种内置函数的快捷方法: 1.创建一个dual表 create table dual(id string); 2.load一个文件(一行,一个空格)到dual表 hive> load data local inp

通过PHP的内置函数,通过DES算法对数据加密和解密_php技巧

由于项目的需要,要写一个能生成"授权码"的类(授权码主要包含项目使用的到期时间),生成的授权码将会写入到一个文件当中,每当项目运行的时候,会自动读取出文件中的密文,然后使用唯一的"密钥"来调用某个函数,对密文进行解密,从中解读出项目的使用到期时间. 之前,自己有先试着写了下,主要是base64+md5+反转字符串.算法太过简单,很容易被破解,而且也没有能过做到"密钥"在加解密中的重要性,故而舍之. 后来,查找了相关资料,发现,原来PHP中内置了一

浏览器扩展系列————给MSTHML添加内置脚本对象【包括自定义事件】

原文:浏览器扩展系列----给MSTHML添加内置脚本对象[包括自定义事件] 使用场合:          在程序中使用WebBrowser或相关的控件如:axWebBrowser等.打开本地的html文件时,可以在html的脚本中使用自己在.net中定义的类,实现与Internet Explorer server的互操作.此外也可以在充分利用html在设计界面方面高效,简单的同时,也可以实现一些复杂的特性. 实现: Codepublic class ScriptEvent     {