全文搜索
如何在PostgreSQL中使用全文搜索功能。
Postgres 内置了处理全文搜索
查询的函数。这就像是 Postgres 内部的"搜索引擎"。
准备工作
在本指南中,我们将使用以下示例数据:
id | 书名 | 作者 | 描述 |
---|---|---|---|
1 | 《慢吞吞的小狗》 | Janette Sebring Lowrey | 小狗比其他大动物行动更慢 |
2 | 《彼得兔的故事》 | Beatrix Potter | 兔子吃了一些蔬菜 |
3 | 《嘟嘟小火车》 | Gertrude Crampton | 小玩具火车怀揣大梦想 |
4 | 《绿鸡蛋和火腿》 | Dr. Seuss | 山姆改变了食物偏好,开始吃颜色异常的食物 |
5 | 《哈利·波特与火焰杯》 | J.K. Rowling | 学校第四年开始,重大戏剧性事件接踵而至 |
使用方法
本指南将介绍以下函数:
to_tsvector()
将数据转换为可搜索的词元。to_tsvector()
表示"转换为文本搜索向量"。例如:
12select to_tsvector('green eggs and ham');-- 返回结果:'egg':2 'green':1 'ham':4
这些词元统称为"文档",Postgres 可以用于比较操作。
to_tsquery()
将查询字符串转换为匹配用的词元。to_tsquery()
表示"转换为文本搜索查询"。
这个转换步骤很重要,因为我们通常需要对关键词进行"模糊匹配"。
例如,当用户搜索 eggs
而列中的值是 egg
时,我们可能仍希望返回匹配结果。
匹配操作符:@@
@@
符号是全文搜索的"匹配"操作符。它返回 to_tsvector
结果与 to_tsquery
结果之间的所有匹配项。
请看以下示例:
123select *from bookswhere title = 'Harry';
上面的等号操作符(=
)对匹配内容要求非常"严格"。在全文搜索场景中,我们可能想查找所有"哈利波特"书籍,因此可以重写上述示例:
123select *from bookswhere to_tsvector(title) @@ to_tsquery('Harry');
基础全文查询
搜索单列
查找所有description
列包含单词big
的books
记录:
1234567select *from bookswhere to_tsvector(description) @@ to_tsquery('big');
多列搜索
目前没有直接使用 JavaScript 或 Dart 搜索多列的方法,但您可以通过在数据库中创建计算列来实现。
查找所有 description
或 title
包含单词 little
的 books
:
1234567select *from bookswhere to_tsvector(description || ' ' || title) -- 合并列时务必包含空格分隔! @@ to_tsquery('little');
匹配所有搜索词
要查找 description
字段同时包含 little
和 big
这两个单词的所有 books
,我们可以使用 &
符号:
1234567select *from bookswhere to_tsvector(description) @@ to_tsquery('little & big'); -- 在搜索查询中使用 & 表示 AND
匹配任意搜索词
要查找 description
字段包含 little
或 big
任意一个单词的所有 books
,请使用 |
符号:
1234567select *from bookswhere to_tsvector(description) @@ to_tsquery('little | big'); -- 在搜索查询中使用 | 表示 OR 逻辑
注意搜索 big
时会包含 bigger
(或 biggest
等)的结果。
部分匹配搜索
部分匹配搜索在您需要查找数据中子字符串匹配时特别有用。
实现部分匹配搜索
您可以使用 :*
语法配合 to_tsquery()
函数。以下示例展示了如何搜索所有以"Lit"开头的书籍标题:
1select title from books where to_tsvector(title) @@ to_tsquery('Lit:*');
通过RPC扩展功能
为了使部分搜索功能能够通过API访问,您可以将搜索逻辑封装在存储过程中。
创建此函数后,您可以使用对应平台的SDK从应用程序中调用它。以下是一个示例:
1234567create or replace function search_books_by_title_prefix(prefix text)returns setof books AS $$begin return query select * from books where to_tsvector('english', title) @@ to_tsquery(prefix || ':*');end;$$ language plpgsql;
此函数接收一个前缀参数,并返回所有标题包含以该前缀开头的单词的书籍。:*
操作符用于在to_tsquery()
函数中表示前缀匹配。
处理查询中的空格
当搜索词需要包含短语或多个单词时,您可以使用+
作为空格占位符来连接单词:
1select * from search_books_by_title_prefix('Little+Puppy');
创建索引
现在我们已经实现了全文搜索功能,接下来让我们创建一个索引
。这将允许Postgres预先"构建"文档,使得在执行查询时无需临时创建。这将显著提升我们的查询速度。
可搜索列
让我们在books
表中新建一个名为fts
的列,用于存储title
和description
列的可搜索索引。
我们可以使用Postgres的一个特殊功能——生成列,来确保每当title
和description
列的值发生变化时,索引都会自动更新。
123456789alter table booksadd column fts tsvector generated always as (to_tsvector('english', description || ' ' || title)) stored;create index books_fts on books using gin (fts); -- 生成索引select id, ftsfrom books;
使用新列进行搜索
现在我们已经创建并填充了索引,可以使用与之前相同的技术进行搜索:
123456select *from bookswhere fts @@ to_tsquery('little & big');
查询操作符
请参阅 PostgreSQL: 文本搜索函数与操作符
了解可用于执行更高级全文查询
的其他查询操作符,例如:
邻近操作符: <->
邻近符号可用于搜索相隔特定"距离"的词语。例如,查找短语 big dreams
,其中"big"的匹配项紧跟着"dreams"的匹配项:
123456select *from bookswhere to_tsvector(description) @@ to_tsquery('big <-> dreams');
我们还可以使用 <->
查找彼此间隔特定距离的词语。例如查找相隔2个词以内的 year
和 school
:
123456select *from bookswhere to_tsvector(description) @@ to_tsquery('year <2> school');
否定操作符:!
否定符号可用于查找_不包含_特定搜索词的短语。例如,要查找包含单词big
但不包含little
的记录:
123456select *from bookswhere to_tsvector(description) @@ to_tsquery('big & !little');