基于redis的高性能全文搜索引擎-RediSearch

RedisearchRedis 之上实现了二级索引,但与其他 Redis 索引库不同,它不使用排序集等内部数据结构。

Redisearch通过全文搜索索引为原始的key-value数据添加数据结构,使得定向过滤数据的速度更快,更方便。

RediSearch是一个Redis模块,为Redis提供查询、二次索引和全文搜索。要使用RediSearch,首先要在Redis数据库上声明索引。然后可以使用搜索查询语言来查询该数据。RedSearch使用压缩的反向索引进行快速索引,占用内存少。RedSearch索引通过提供精确的短语匹配、模糊搜索和数字过滤等功能

Redisearch引入了几个新的概念,如:

  • index:可以理解为关系型数据库中的数据表
  • document:数据表中的数据
  • fields:索引字段,类似于数据表中的列,可用于检索

特性

  • 基于文档的多个字段全文索引
  • 高性能增量索引
  • 文档排序(由用户在索引时手动提供)
  • 在子查询之间使用 AND 或 NOT 操作符的复杂布尔查询
  • 可选的查询子句
  • 基于前缀的搜索
  • 支持字段权重设置
  • 自动完成建议(带有模糊前缀建议)
  • 精确的短语搜索
  • 在许多语言中基于词干分析的查询扩展
  • 支持用于查询扩展和评分的自定义函数
  • 将搜索限制到特定的文档字段
  • 数字过滤器和范围
  • 使用 Redis 自己的地理命令进行地理过滤
  • Unicode 支持(需要 UTF-8 字符集)
  • 检索完整的文档内容或只是 ID 的检索
  • 支持文档删除和更新与索引垃圾收集
  • 支持部分更新和条件文档更新
  • 原生支持中文搜索(RediSearch 使用Friso 中文分词库。这在很大程度上对用户是透明的,通常不需要额外的配置。)

安装

使用docker安装

为了测试方便,我这里使用docker快速搭建。

拉取镜像

docker pull redislabs/redisearch

容器运行

docker run -d -p 6666:6379 redislabs/redisearch:latest
查看容器运行

1
2
3
tony@UYYNOT:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4e8ac20b62fc redislabs/redisearch:latest "docker-entrypoint.s…" 46 hours ago Up 46 hours 0.0.0.0:6666->6379/tcp beautiful_davinci

连接redis

1
2
3
tony@UYYNOT:~$ redis-cli -h localhost -p 6666 --raw
localhost:6666> ping
PONG

注意: –raw按数据原有格式打印数据,可保证你的命令行支持中文显示

判断RediSearch是否安装成功

1
2
3
4
5
6
7
8
9
 > module list
1) 1) "name"
2) "ReJSON"
3) "ver"
4) "999999"
2) 1) "name"
2) "search"
3) "ver"
4) "20605"

返回数组存在“ft”或 “search”(不同版本),表明 RediSearch 模块已经成功加载。

使用

创建

创建索引

通过**FT.CREATE**命令创建具有指定名称的索引,每个索引可以包含任意多个字段,字段的类型可以是文本、数字、地理位置或标签:

语法
1
FT.CREATE 索引名 SCHEMA [field [WEIGHT n] [TEXT | NUMERIC | GEO | TAG] [SORTABLE]] [field ... ]

可选的 WEIGHT 用于为每个字段设置权重,如果不设置权重,则默认使用 1.0 作为权重。

TEXT 表示文本类型,NUMERIC表示数字类型,GEO表示地理位置,TAG 表示标签。

SORTABLE 可以将字段设置为可排序字段,带有该属性的字段可以在实施检索时通过 SORTBY 选项排序。

注意:RediSearch 支持每个模式最多 1024 个属性,其中最多 128 个可以是 TEXT 属性。在 32 位版本上,最多 64 个属性可以是 TEXT 属性。您拥有的属性越多,您的索引就越大,因为每增加 8 个属性,每个索引记录就需要一个额外的字节来编码。

示例

创建一个hash类型的article索引,用于存储标题、发布日期和文章类别,文档key前缀为article:

1
2
 > FT.CREATE article_idx ON HASH PREFIX 1 article: SCHEMA title TEXT SORTABLE create_time NUMERIC SORTABLE category TAG SORTABLE
"OK"
返回

如果正确执行, FT.CREATE 返回一个简单的字符串回复OK,否则返回一个错误回复。

添加文档至索引

在创建索引之后,通过执行 FT.ADD 命令将指定的文档添加到索引中

语法
1
FT.ADD index docId source FIELDS field value [field value]

最基本的FT.ADD命令接受indexdocIdsorce以及任意多个field-value键值对作为参数,其中:

index用于指定文档所属的索引,docId用于指定文档ID,sorce用于指定文档分值。文档分值可以介于 0.0 至于 1.0 之间,如果没有特殊要求则一般设置为 1.0
FIELDS选项之后的任意多个field-value键值对则用于指定文档包含的键值对。

创建文档上下文的过程可以理解为向表中插入数据,这里请注意字段名可以使用双引号但切记一定要用英文,这里之所以着重提出是因为有些编译器中文双引号和英文双引号用肉眼实在难以辨认否则会出现“Fields must be specified in FIELD VALUE pairs”(其实是将 当作内容处理了以至于缺少了字段)

示例
1
2
localhost:6666> FT.ADD article_idx article:0001 1.0 LANGUAGE "chinese" FIELDS title "1分钟看懂ChatGPT是啥" create_time 1676019115 category "计算机"
OK

其中001为文档ID,”1.0”为评分缺少此值会报”Could not parse document score”异常
language 指明使用的语言默认是英文编码 如果没有此标记存储是没有问题的但不可以通过中文字符查询

注意:在官方文档https://redis.io/commands/?group=search上已经看不到`FT.ADD`这个命令了,官方给我们做出的实例使用的是`redis`自身的哈希`hset`方式创建的(`1.x`版本中使用的是`FT.ADD`命令,`2.x`官方建议使用`hset`,但`FT.ADD`在`2.x`版本中并没有被废弃,依然可用,`redisearch-py`中封装的命令也是`FT.ADD`)。
创建索引后,任何带有article:前缀的document都会在创建时自动编制索引。
如:

1
HSET article:0002 title "伊朗总统主动上门,急忙要中国兑现4000亿美元,释放什么信号" create_time 1676020341 category "国外"

设置文档的语言

通过可选项 LANGUAGE 为文档设置相应的语言,以便RediSearch根据语言对文档做相应的处理:

1
FT.ADD index docId score [LANGUAGE lang] FIELDS filed value [field value ... ]

必须根据文档内容设置正确的语言,不正确的语言设置将导致文档无法被检索。

只索引而不存储文档

如果用户在执行 FT.ADD 命令时给定了可选项 NOSAVE ,那么命令只会为文档建立索引,并不会存储文档本身:

1
FT.ADD index docId score [NOSAVE] FIELDS field value [field value ... ]

单纯被索引而未被存储的文档在检索的时候只会返回文档的ID而不会返回文档本身。

修改

修改索引

向索引添加新属性。将属性添加到索引会导致任何未来的文档更新在索引和重新索引现有文档时使用新属性。

语法
1
FT.ALTER {index} [SKIPINITIALSCAN] SCHEMA ADD {attribute} {options} ...

必需的参数
index:是要创建的索引名称。
SKIPINITIALSCAN:如果设置,则不扫描和索引。
SCHEMA ADD {attribute} {options} …:在 SCHEMA 关键字之后,声明要添加的字段:

  • attribute是要添加的属性。
  • options是属性选项。有关详细信息。
示例
1
2
localhost:6666> FT.ALTER article_idx SCHEMA ADD views NUMERIC SORTABLE
OK
返回

如果正确执行, FT.ALTER 返回一个简单的字符串回复OK,否则返回一个错误回复。

修改文档

使用新文档代替旧文档

如果用户在执行 FT.ADD 命令时给定了可选的 REPLACE 选项,并且索引中存在于给定的ID相同的文档,那么命令将使用新文档代替旧文档:

1
FT.ADD index docId score [REPLACE] FIELDS field value [field value ... ]

部分更新文档
在一些情况下,我们可能只是想要更新已有文档中的某些字段,而不是替换整个文档,这时就需要用到 PARTIAL 选项:

1
FT.ADD index docId score [REPLACE PARTIAL] FIELDS field value [field value ... ]

在默认情况下,用户在定义索引的时候设定文档有多少个字段,添加文档的时候就需要提供多少个字段,但是在启用了 PARTIAL可选项并且旧文档已经存在的情况下,用户只需要给定想要更新的字段及其新值即可。

查看索引信息

在创建索引之后,用户可以通过执行**FT.INFO**命令查看索引的相关信息,比如索引包含的文档数量、索引包含的唯一字段数量、索引在内存中的分布信息等。

语法

1
FT.INFO index

我们查看下我们创建的电影索引信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
> FT.INFO idx:movie
1) "index_name"
2) "idx:movie"
3) "index_options"
4) (empty list or set)
5) "index_definition"
6) 1) "key_type"
2) "HASH"
3) "prefixes"
4) 1) "movie:"
5) "default_language"
6) "chinese"
7) "language_field"
8) "__language"
9) "default_score"
10) "1"
7) "attributes"
8) 1) 1) "identifier"
2) "id"
3) "attribute"
4) "id"
5) "type"
6) "TEXT"
7) "WEIGHT"
8) "1"
9) "SORTABLE"
2) 1) "identifier"
2) "genre"
3) "attribute"
4) "genre"
5) "type"
6) "TAG"
7) "SEPARATOR"
8) ","
……

查看所有索引

1
2
127.0.0.1:6666> FT._LIST
idx:movie

查询

获取单条数据
语法
1
FT.GET indexname docid
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
127.0.0.1:6666> FT.GET idx:movie movie:999999
votes
11111
rating
8.8
year
2015
genre
喜剧
language
chinese
description
在学生时代的初恋秋雅(王智 饰)的婚礼上,毕业后吃软饭靠老婆养的夏洛(沈腾 饰)假充大款……
alias
Goodbye Mr. Loser
lang
中文
name
夏洛特烦恼
country
美国
duration
8888
poster
www.aaa.com/bbb
date_released
2015-09-30
original_name
夏洛特烦恼
id
999999
获取多条数据
语法
1
FT.MGET indexname docid1 docid2 ……
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
127.0.0.1:6666> FT.MGET idx:movie movie:999999 movie:1292000
votes
11111
rating
8.8
year
2015
genre
喜剧
language
chinese
description
在学生时代的初恋秋雅(王智 饰)的婚礼上,。
alias
Goodbye Mr. Loser
lang
中文
name
夏洛特烦恼
country
美国
duration
8888
poster
www.aaa.com/bbb
date_released
2015-09-30
original_name
夏洛特烦恼
id
999999
rating
9.0
votes
662773
year
1999
genre
剧情
alias
搏击会(港) / 斗阵俱乐部(台) / 格斗俱乐部
description
杰克(爱德华•诺顿 饰)并因...
lang
英语
name
搏击俱乐部
country
美国
duration
8340
poster
https://wmdb.querydata.org/movie/poster/1604826594906-157e21.jpg
original_name
Fight Club
date_released
1999-09-10
id
1292000

SEARCH语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
FT.SEARCH index query
[NOCONTENT]
[VERBATIM] [NOSTOPWORDS]
[WITHSCORES]
[WITHPAYLOADS]
[WITHSORTKEYS]
[FILTER numeric_field min max [ FILTER numeric_field min max ...]]
[GEOFILTER geo_field lon lat radius m | km | mi | ft [ GEOFILTER geo_field lon lat radius m | km | mi | ft ...]]
[INKEYS count key [key ...]] [ INFIELDS count field [field ...]]
[RETURN count identifier [AS property] [ identifier [AS property] ...]]
[SUMMARIZE [ FIELDS count field [field ...]] [FRAGS num] [LEN fragsize] [SEPARATOR separator]]
[HIGHLIGHT [ FIELDS count field [field ...]] [ TAGS open close]]
[SLOP slop]
[TIMEOUT timeout]
[INORDER]
[LANGUAGE language]
[EXPANDER expander]
[SCORER scorer]
[EXPLAINSCORE]
[PAYLOAD payload]
[SORTBY sortby [ ASC | DESC]]
[LIMIT offset num]
[PARAMS nargs name value [ name value ...]]
[DIALECT dialect]

必需的参数
index
是索引名称。您必须首先使用创建索引FT.CREATE

query
是要搜索的文本查询。如果它不止一个单词,请将其放在引号中。

可选参数
NOCONTENT
返回文档 ID 而不是内容。如果 RediSearch 只是外部文档集合的索引,这很有用。

VERBATIM
不会尝试使用词干提取来扩展查询,而是逐字搜索查询词。

WITHSCORES
返回每个文档的相对内部分数。

RETURN {num} {identifier} AS {property} …
限制从文档返回的属性。num是关键字后面的属性数。如果num是 0,它的行为就像NOCONTENT. identifier是属性名称(对于哈希和 JSON)或 JSON 路径表达式(对于 JSON)。 property是结果中使用的可选名称。如果未提供,identifier则在结果中使用。

HIGHLIGHT …
格式化匹配文本的出现。有关详细信息,会突出显示(默认html<b>标签包括关键词)。

LANGUAGE {language}
在搜索查询扩展期间对提供的语言使用词干分析器。如果查询中文文档,设置chinese为正确标记查询词。默认为英语。如果发送了不受支持的语言,该命令将返回错误。有关语言列表

SCORER {scorer}
使用您定义的自定义评分函数。

SORTBY {attribute} [ASC|DESC]
按此属性的值对结果进行排序。这适用于文本和数字属性。所需的属性SORTBY应在索引中声明SORTABLE,以便以非常低的延迟可用。请注意,这会增加内存开销。

LIMIT first num
将结果限制为给定的偏移量和结果数。请注意,偏移量是零索引的。默认值为 0 10,即从第一个结果开始返回 10 个项目。您可以使用它LIMIT 0 0来计算结果集中文档的数量,而无需实际返回它们。

检索文档

通过执行 FT.SEARCH命令,用户可以根据给定的文本在索引中查找与之匹配的文档:

1
FT.SEARCH index query [LANGUAGE lang]

其中 index为被检索索引的名字,query指定查找的文本, 如果默认不是英语,则需要使用 LANGUAGE 指定语言。

只返回匹配的文档ID

如果用户在检索时只想获取被匹配文档的ID而不是文档本身,那么只需要在执行命令的时候提供 NOCONTENT 选项即可:

1
FT.SEARCH index query [NOCONTENT]
只返回指定的字段

在默认情况下,FT.SEARCH命令将返回文档包含的所有字段,但用户也可以通过 RETURN 选项让命令只返回指定的字段:

1
FT.SEARCH index query [RETURN num field ... ]

用户需要通过 num 参数指定自己想要返回的字段数量,并在之后给出具体的字段名。

只返回自定数值区间内的文档

使用 FILTER 选项,可以让 FT.SEARCH命令只返回结果文档中,指定数值字段符合给定区间的文档:

1
FT.SEARCH index query [FILTER numeric_field min max]

FILTER字段接受numeric_field 、min 和 max这3个参数作为输入,其中numeric_field参数用于指定文档中数值字段,而 min 和 max 参数则用于指定具体的数值区间。这个数值区间可以像 ZRANGE 之类的命令一样,使用 -inf 和 +inf 来指定无限值,或者使用左括号 ( 和 右括号 )

根据指定规则排序

通过 SORTBY 可选项,用户可以让 FT.SEARCH 命令根据指定字段的大小按顺序或逆序返回结果:

1
FT.SEARCH index query [SORTBY field [ASC | DESC]

默认采用 ASC[升序] 方式排序。

使用SORTBY排序的字段必须在创建索引的时候使用SORTABLE选项进行声明,否则SORTBY将得出错误的结果。

限制命令返回的结果数量

通过 LIMIT 选项,可以限制命令返回的结果数量:

1
FT.SEARCH index query [LIMIT offset num]
从索引中获取文档

用户可以通过FT.GET命令或者FT.MGET命令,从指定的索引中获取一个或多个文档:

1
2
FT.GET index docId
FT.MGET index docId [docId ... ]

查询语法

了解如何使用搜索查询语法

您可以使用以下规则对复杂查询使用简单的语法:

多词短语是标记列表,例如:red yellow blue 标识术语的交集 (AND),确切(固定)的短语用引号引起来,例如,”hello world”。

OR联合用竖线 (|)表示,例如,hello|hallo|shalom|hola

注意事项:

考虑示例中解析器行为的差异hello world | “goodbye” moon:
此查询被解释为搜索hello world 或 “goodbye” moon之一。
NOT表达式或子查询的否定用减号-表示,例如hello -world。还支持纯否定查询(例如,-foo)。-@title:(foo|bar)

考虑一个带有否定的简单查询-hello world:此查询被解释为 -hello AND world(仅hello被否定)。
要想否定(hello world),请将查询更新为-(hello world)

使用语法选择指定字段:hello @field:world

数值范围与语法为数值字段匹配:@field:[{min} {max}]

地理半径匹配地理字段的语法:@field:[{lon} {lat} {radius} {m|km|mi|ft}]

使用语法对矢量字段进行范围查询:@field:[VECTOR_RANGE {radius} $query_vec],其中query_vec作为查询参数给出(从 v2.6 开始)。

KNN 查询带有或不带语法预过滤的向量字段{filter_query}=>[KNN {num} @field $query_vec] (从 v2.4 开始)。

TAG字段过滤器:@field:{tag | tag | ...}

可选条款或条款:foo ~bar表示bar是可选的,但包含bar的文档排名更高。

术语的模糊匹配:%hello%指与其编辑距离为 1 的所有术语。

查询中的表达式可以用括号括起来以消除歧义,例如,(hello|hella) (world|werld)

上述的组合可以一起使用,例如,hello (world|foo) “bar baz” bbbb.

纯否定查询

v0.19.3 开始,可以有一个仅由否定表达式组成的查询,例如-helloor -(@title:(foo|bar))。结果是所有不包含查询词的文档。

警告
任何复杂的表达式都可以通过这种方式取反,但是,这里要注意:如果一个否定表达式的结果很少或没有结果,这相当于遍历索引中的所有文档并对其进行排序,这可能会很慢并且会导致高 CPU 消耗.

字段过滤

要指定查询匹配哪些字段,请在每个查询表达式或子表达式的表达式前加上@符号、字段名称和(冒号)符号。
如果字段修饰符位于多个单词或表达式之前,则它仅适用于低版本的相邻表达式。

我们来看下这个简单查询@name:James Brown:在这里,字段修饰符@name后跟两个词:James和Brown。

1.x中,此查询将被解释为*James Brown@name字段中查找
2.x中,此查询将被解释为
James@name字段中查找并且 Brown在任何文本字段中查找*。换句话说,它将被解释为(@name:James) Brown
2.x中,要实现 1.x 的默认行为,请将查询更新为@name:(James Brown)
如果字段修饰符位于括号内的表达式之前,则它仅适用于括号内的表达式。该表达式应该对指定字段有效,否则将被跳过。

要在多个字段上创建复杂的过滤,您可以组合多个修饰符。例如,如果您有洲、国家和年份类型,您可以使用以下查询搜索2022年至2023年亚洲中国或日本对应的document:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
localhost:6666> FT.SEARCH carIndex "@continent:亚洲 @country:{中国|日本} @year:[2022 2023]" LIMIT 0 3
34
aaabbbb000
language
chinese
year
2023
body
新疆男篮可以说是本赛季中途更换主帅之后表现最好的球队了,邱彪接手球队之后,还引进了杨文海。这个窗口期,新疆男篮再次更换了教练,长期在梯队执教,并且带队出战全运会的王建敏加盟,新疆男篮的目的非常明确,就是要给邱彪最大的帮助,全力冲击更好成绩。
country
中国
title
CBA最新消息!新疆男篮又换教练,琼斯拒绝归队,阿的江走马上任
views
9993
price
9980.25
continent
亚洲
car_0000441
year
2022
body

……

您可以将多个修饰符应用于相同的术语或分组的术语:
查询title或者body中国包含中国或者汽车且country为中国的document

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
127.0.0.1:6666> FT.SEARCH carIndex "@title|body:(中国|汽车) @country:{中国}" LIMIT 0 3 HIGHLIGHT LANGUAGE chinese
21
car_0000347
year
2022
body
从每月6日,到4日、3日、2日,比亚迪终于将产销快报提前至在2月1日当天发布,降维新能源<b>汽车</b>新品牌销量公布日,强势宣告其新能源<b>汽车</b>市场霸主之位。数据显示,在刚刚
country
中国
title
降维打击,比亚迪1月新能源乘用车销量超15万辆
views
7587
price
65560.33
continent
亚洲
……

数字过滤

如果模式中的字段定义为 NUMERIC,则可以在 Redis 请求中使用 FILTER 参数或通过在查询中指定过滤规则来使用它进行过滤。语法是@field:[{min} {max}],例如,@price:[100 200]

  • 可以在同一个查询中交叉或合并多个数字过滤器,无论是针对相同的字段还是不同的字段。

  • -inf,inf标识无穷小和无穷大。大于100表示为[(100 inf]

  • 包括数字过滤器。不包含边界使用(表示,例如,[(100 (200]

  • 可以通过在-过滤器前加上一个符号来否定数字过滤器。例如,返回 price 不等于于 100 的结果表示为:@title:foo -@price:[100 100]

示例

查询价格在10000到11000的doc,价格按降序排列

1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:6666> FT.SEARCH carIndex "@price:[10000, 11000]" SORTBY price DESC RETURN 2 title price
2
car_0000038
price
10599.24
title
魅族/吉利联手打造 无界汽车或将于2月20日亮相
car_0000287
price
10472.18
title
丰田混动专利多?本田比亚迪说我们不怕!| 吴佩频道

使用FILTER过滤价格

1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:6666> FT.SEARCH carIndex * FILTER price 10000 11000 SORTBY price DESC RETURN 2 title price
2
car_0000038
price
10599.24
title
魅族/吉利联手打造 无界汽车或将于2月20日亮相
car_0000287
price
10472.18
title
丰田混动专利多?本田比亚迪说我们不怕!| 吴佩频道

使用字段过滤查询浏览量等于9117的doc

1
2
3
4
5
6
7
127.0.0.1:6666> ft.search carIndex "@views:[9117 9117]" return 2 title views
1
car_0000051
title
【天天资讯】最长续航1150km,东风风光蓝电E5有望3月1日上市
views
9117

查询价格小于1133.49的doc,价格按升序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
127.0.0.1:6666> FT.SEARCH carIndex "@price:[-inf (1133.49]" RETURN 2 title price SORTBY price
5
car_0000356
price
433.01
title
PPE平台打造,奥迪RS6 e-tron或2024年亮相
car_0000412
price
753.79
title
雷诺-日产“新震荡”
car_0000226
price
893.46
title
马斯克确认 量产版特斯拉Cybertruck会有红色灯条
car_0000072
price
1009.84
title
新一代94GHz高频高性能超距毫米波雷达发布
car_0000142
price
1098.87
title
丰田皇冠2.0T 刷ECU程序 驾驶质感提升一个档次

使用FILTER过滤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
127.0.0.1:6666> FT.SEARCH carIndex * FILTER price -inf (1133.49 RETURN 2 title price SORTBY price
5
car_0000356
price
433.01
title
PPE平台打造,奥迪RS6 e-tron或2024年亮相
car_0000412
price
753.79
title
雷诺-日产“新震荡”
car_0000226
price
893.46
title
马斯克确认 量产版特斯拉Cybertruck会有红色灯条
car_0000072
price
1009.84
title
新一代94GHz高频高性能超距毫米波雷达发布
car_0000142
price
1098.87
title
丰田皇冠2.0T 刷ECU程序 驾驶质感提升一个档次

标签过滤

v0.91 开始,您可以使用一种称为TAG字段的特殊字段类型,在索引中使用更简单的标记化和编码。您无法使用一般的无字段搜索访问这些字段中的值。相反,您使用特殊语法:
@field:{ tag | tag | ...}
标签可以有多个词或包含字段分隔符(默认情况下)以外的其他标点符号。标记中的标点符号应使用反斜杠\转义。
请注意,同一子句中的多个标签会创建一个包含任一标签的文档联合。要创建包含所有标签的文档交集,您应该多次重复标签过滤器,例如:

1
2
3
4
5
# 返回必须包含以下3个国家(交集)的所有文档
@country:{中国} @country:{日本} @country:{韩国}

# 返回包含以下3个国家的所有文档
@country:{中国|日本|韩国}

标记从句可以组合成任何子句,用作否定表达式、可选表达式等。

聚合查询

语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
FT.AGGREGATE
{index_name:string}
{query_string:string}
[VERBATIM]
[LOAD {nargs:integer} {property:string} ...]
[GROUPBY
{nargs:integer} {property:string} ...
REDUCE
{FUNC:string}
{nargs:integer} {arg:string} ...
[AS {name:string}]
...
] ...
[SORTBY
{nargs:integer} {string} ...
[MAX {num:integer}] ...
] ...
[APPLY
{EXPR:string}
AS {name:string}
] ...
[FILTER {EXPR:string}] ...
[LIMIT {offset:integer} {num:integer} ] ...
[PARAMS {nargs} {name} {value} ... ]
详细参数

可以采用可变数量参数的参数以 的形式表示param {nargs} {property_1… property_N}。参数的第一个参数是参数后面的参数个数。这允许 RediSearch 在您的一个参数具有另一个参数的名称的情况下避免解析歧义。例如,要按名字、姓氏和国家/地区排序,可以指定SORTBY 6 firstName ASC lastName DESC country ASC.

index_name:执行查询的索引。

query_string:检索文档的基本过滤查询。

GROUPBY {nargs} {property}:根据一个或多个属性对管道中的结果进行分组。每个组应该至少有一个 reducer(一个处理组条目的函数),可以对它们进行计数或执行多个聚合操作。

REDUCE {func} {nargs} {arg} … [AS {name}]:使用reducer函数将每个组中的匹配结果变为为单个记录。例如,COUNT 将计算组中的记录数。

reducer 可以使用AS {name}可选参数拥有自己的属性名称。如果未给出名称,则生成的名称将是 reduce 函数的名称和组属性。例如,如果属性未给 COUNT_DISTINCT 一个名称@foo,则生成的名称将为count_distinct(@foo)。

SORTBY {nargs} {property} {ASC|DESC} [MAX {num}]:使用属性列表对结果进行排序。默认情况下,排序是升序的 。nargs是排序参数的个数,包括ASCDESC。例如:SORTBY 4 @foo ASC @bar DESC

APPLY {expr} AS {name}:对一个或多个属性应用一对一转换,并将结果作为新属性存储在管道中,或者使用此转换替换任何属性。expr是可用于对数值属性执行算术运算的表达式,或可根据属性类型(见下文)应用于属性的函数,或它们的任意组合。例如:APPLY "sqrt(@foo)/log(@bar) + 5" AS baz将为管道中的每条记录动态应用此表达式,并将结果存储为名为 baz 的新属性,该属性可以被管道中进一步的 APPLY / SORTBY / GROUPBY / REDUCE 操作引用。

LIMIT {offset} {num}。限制结果数以仅返回num从索引开始的结果offset(从零开始)

FILTER {expr}。使用与每个结果中的值相关的谓词表达式过滤结果。它们在查询后应用并与管道的当前状态相关。有关完整详细信息,请参阅下面的 FILTER 表达式。

PARAMS {nargs} {名称} {值}。定义一个或多个值参数。

支持的聚合函数
COUNT

统计每组的记录数

1
REDUCE COUNT 0
COUNT_DISTINCT

统计每组的记录数

1
REDUCE COUNT_DISTINCT 1 {property}
COUNT_DISTINCTISH

与 COUNT_DISTINCT 相同 - 但提供近似值而不是精确计数,代价是大组中的内存和 CPU 更少。

1
REDUCE COUNT_DISTINCTISH 1 {property}
SUM

返回组中给定属性的所有数值的总和。

1
REDUCE SUM 1 {property}
MIN

返回属性的最小值,无论它是字符串、数字还是 NULL。

1
REDUCE MIN 1 {property}
MAX

返回属性的最大值,无论是字符串、数字还是 NULL。

1
REDUCE MAX 1 {property}
AVG

返回数值属性的平均值

1
REDUCE AVG 1 {property}
STDDEV

返回组中数字属性的标准差

1
REDUCE STDDEV 1 {property}
APPLY可用函数
字段函数列表
FunctionDescriptionExample
exists(s)检查文档中是否存在字段。exists(@field)
数值函数列表
FunctionDescriptionExample
log(x)使用一个参数,返回 x 的自然对数(底为 e )log(@foo)
abs(x)返回数值表达式的绝对数abs(@foo-@bar)
ceil(x)将数字向上舍入到最接近的整数ceil(@foo/3.14)
floor(x)将数字向下舍入到最接近的整数floor(@foo/3.14)
log2(x)返回 x 以 2 为底的对数log2(2^@foo)
exp(x)返回 e 的 x 次幂,Ex, 其中 e = 2.718281… 是自然对数的基数。exp(@foo)
sqrt(x)返回 x 的平方根。sqrt(@foo)
字符串函数列表
FunctionDescriptionExample
upper(s)转换s为大写upper('hello world')
lower(s)转换s为小写lower("HELLO WORLD")
startswith(s1,s2)如果 s2 是 s1 的前缀则返回1,否则返回0startswith(@field, "company")
contains(s1,s2)返回 s2 在 s1 中出现的次数,没有则返回0。如果 s2 是空字符串,则返回length(s1) + 1contains(@field, "pa")
strlen(s)返回 s 的长度strlen(@t)
substr(s, offset, count)返回 s 的子字符串,从offset开始并具有count个字符。
如果 offset 为负数,则表示距字符串末尾的距离。
如果 count 为 -1,则表示“从 offset 开始的字符串的其余部分
substr("hello", 0, 3) substr("hello", -2, -1)
format( fmt, ...)使用以下参数fmt格式化字符串。
目前唯一支持的格式参数是%s,它适用于所有类型的参数。
format("Hello, %s, you are %s years old", @name, @age)
matched_terms([max_terms=100])以列表的形式返回与每条记录(最多 100 个)匹配的查询词。如果指定了限制,我们将根据查询顺序返回我们找到的前 N 个匹配项。matched_terms()
split(s, [sep=","], [strip=" "])用字符串 sep 中的任意字符拆分字符串,并去除 strip 中的任意字符。如果只指定了 s,我们用逗号和空格分隔。输出是一个数组。split(“foo,bar”)
日期/时间函数列表
FunctionDescription
timefmt(x, [fmt])返回基于数字时间戳 x 的格式化时间字符串。有关格式化选项,请参阅strftime
不指定fmt等同于%FT%TZ.(如:timefmt(1676426614, '%Y-%m-%d %H:%M:%S')会输出UTC时间2023-02-15 02:03:34)
parsetime(timesharing, [fmt])timefmt()的反向解析——使用给定的格式字符串解析时间格式
day(timestamp)将时间戳四舍五入到当天的开始(北京时间为当天8点,如2023-02-15 10:22:20会转为如2023-02-15 08:00:00
hour(timestamp)将时间戳四舍五入到当前小时的开始(如2023-02-15 10:22:20会转为如2023-02-15 10:00:00
minute(timestamp)将时间戳四舍五入到当前分钟的开始(如2023-02-15 10:22:20会转为如2023-02-15 10:22:00
month(timestamp)将时间戳四舍五入到当月的开始(如2023-02-15 10:22:20会转为如2023-02-01 08:00:00
dayofweek(timestamp)返回时间戳对应一周中的第几天(周日 = 0).
dayofmonth(timestamp)返回时间戳对应当前月份的第几天(1-31)
dayofyear(timestamp)返回时间戳对应当年的第几天 (0-365).
year(timestamp)返回时间戳对应的年份(如 2018).
monthofyear(timestamp)返回时间戳对应一年中的第几个月,月份从0开始如 (0 .. 11).
GEO函数列表
FunctionDescriptionExample
geodistance(field,field)以米为单位的返回距离。geodistance(@field1,@field2)
geodistance(field,"lon,lat")以米为单位的返回距离。geodistance(@field,"1.2,-3.4")
geodistance(field,lon,lat)以米为单位的返回距离。geodistance(@field,1.2,-3.4)
geodistance("lon,lat",field)以米为单位的返回距离。geodistance("1.2,-3.4",@field)
geodistance("lon,lat","lon,lat")以米为单位的返回距离。geodistance("1.2,-3.4","5.6,-7.8")
geodistance("lon,lat",lon,lat)以米为单位的返回距离。geodistance("1.2,-3.4",5.6,-7.8)
geodistance(lon,lat,field)以米为单位的返回距离。geodistance(1.2,-3.4,@field)
geodistance(lon,lat,"lon,lat")以米为单位的返回距离。geodistance(1.2,-3.4,"5.6,-7.8")
geodistance(lon,lat,lon,lat)以米为单位的返回距离。geodistance(1.2,-3.4,5.6,-7.8)
示例

以电影上映年份分组,获取每个年份最多的投票数,并给每个投票数+100。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 > FT.AGGREGATE idx:movie * GROUPBY 1 @year REDUCE MAX 1 @votes AS max_votes APPLY "@max_votes+100" AS after_votes LIMIT 0 3 SORTBY 2 @after_votes DESC
1) "86"
2) 1) "year"
2) "1994"
3) "max_votes"
4) "2170679"
5) "after_votes"
6) "2170779"
3) 1) "year"
2) "2001"
3) "max_votes"
4) "1770954"
5) "after_votes"
6) "1771054"
4) 1) "year"
2) "2010"
3) "max_votes"
4) "1602044"
5) "after_votes"
6) "1602144"
过滤表达式

FILTER 表达式使用与结果集中的值相关的谓词过滤结果。

FILTER 表达式在查询后进行评估,并与管道的当前状态相关。因此,它们可用于根据组计算修剪结果。请注意,过滤器没有索引,本身不会加快处理速度。

过滤器表达式遵循 APPLY 表达式的语法,并添加了条件==, !=, <, <=, >, >=。两个或多个谓词可以用逻辑 &&(AND) 和 ||(OR)组合。可以使用 !(NOT) 否定单个谓词。

例如,过滤所有用户名为foo且年龄小于20的结果表示为:

FT.AGGREGATE

1
FILTER "@name=='foo' && @age < 20"

可以添加几个过滤步骤,尽管在管道的同一阶段,将多个谓词组合到一个过滤步骤中会更有效。

示例
查询每个类型电影数量
1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6666> FT.AGGREGATE idx:movie * GROUPBY 1 @genre REDUCE COUNT 0 AS nums_genre
21
genre
犯罪
nums_genre
16
genre
惊悚
nums_genre
14
……
查询所有电影的平均评分
1
2
3
4
127.0.0.1:6666> FT.AGGREGATE idx:movie * GROUPBY 0 REDUCE AVG 1 RATING AS avg_rating
1
avg_rating
8.6776

删除

删除索引

当用户不再需要某个索引的时候,可以通过以下命令将其移除:

1
FT.DROP index [KEEPDOCS]

在默认情况下,FT.DROP命令在移除索引的同时也会删除索引中的所有文档,如果用户想要保留被删除的索引,那么只需要在执行命令的时候使用 KEEPDOCS 可选项即可。

删除文档

从索引中移除文档

当不再需要索引中的某个文档时,可以通过以下命令将其移除:

1
FT.DEL index docId

注意:单纯的执行FT.DEL只会将文档移除出索引,但是并不会删除被移除的文档。因此,还是可以使用FT.GET命令来获取被移除的文档。

如果想要在移除索引文档的同时将文档一并删除,就需要在执行命令的时候使用 DD (Delete Document)可选项:

1
FT.DEL index docId [DD]

索引别名操作

添加别名

1
2
127.0.0.1:6666>FT.ALIASADD xs student
"OK"

给索引student起个xs的别名,一个索引可以起多个别名

删除别名

1
2
127.0.0.1:6666>FT.ALIASDEL xs 
"OK"

参考文献:

https://redis.io/docs/stack/search/

https://blog.csdn.net/lingxi0726/article/details/106740357

https://blog.csdn.net/qq_16494381/article/details/123640165