全文搜索
Postgres有内置的功能来处理 全文搜索
查询。这就像Postgres中的一个 搜索引擎
。
在本指南中,我们将使用以下示例数据:
id
title
author
description
1
The Poky Little Puppy
Janette Sebring Lowrey
Puppy is slower than other, bigger animals.
2
The Tale of Peter Rabbit
Beatrix Potter
Rabbit eats some vegetables.
3
Tootle
Gertrude Crampton
Little toy train has big dreams.
4
Green Eggs and Ham
Dr. Seuss
Sam has changing food preferences and eats unusually colored food.
5
Harry Potter and the Goblet of Fire
J.K. Rowling
Fourth year of school starts, big drama ensues.
create table books (
id serial primary key,
title text,
author text,
description text
);
insert into books (title, author, description)
values
('The Poky Little Puppy','Janette Sebring Lowrey','Puppy is slower than other, bigger animals.'),
('The Tale of Peter Rabbit','Beatrix Potter','Rabbit eats some vegetables.'),
('Tootle','Gertrude Crampton','Little toy train has big dreams.'),
('Green Eggs and Ham','Dr. Seuss','Sam has changing food preferences and eats unusually colored food.'),
('Harry Potter and the Goblet of Fire','J.K. Rowling','Fourth year of school starts, big drama ensues.');
我们在本指南中要介绍的函数是:
to_tsvector()
link 将你的数据转换为可搜索的 “标记”。to_tsvector()
代表 to text search vector
。例如:
select to_tsvector('green eggs and ham')
-- Returns 'egg':2 'green':1 'ham':4
这些令牌统称为 “文件”,Postgres可以用它来进行比较。
to_tsquery()
link 将查询字符串转换为 令牌
来匹配。to_tsquery()
代表 to text search query
。
这个转换步骤很重要,因为我们要对关键词进行 “模糊匹配”。
例如,如果用户搜索 eggs
,而某一列的值是 eggs
,我们可能仍然希望返回一个匹配。
Match: @@
link @@
符号是全文搜索的 “匹配 “符号。它返回to_tsvector
结果和to_tsquery
结果之间的任何匹配。
以下面的例子为例:
select *
from books
where title = 'Harry';
const { data, error } = await supabase.from('books').select().eq('title', 'Harry')
final result = await client
.from('books')
.select()
.eq('title', 'Harry')
.execute();
上面的等于符号(=
)对其匹配的内容非常 严格
。在全文搜索的情况下,我们可能想找到所有Harry Potter
的书,所以我们可以重写上面的例子:
select *
from books
where to_tsvector(title) @@ to_tsquery('Harry');
const { data, error } = await supabase.from('books').select().textSearch('title', `'Harry'`)
final result = await client
.from('books')
.select()
.textSearch('title', "'Harry'")
.execute();
查找所有 description
中包含big
单词的 books
:
select
*
from
books
where
to_tsvector(description)
@@ to_tsquery('big');
const { data, error } = await supabase.from('books').select().textSearch('description', `'big'`)
final result = await client
.from('books')
.select()
.textSearch('description', "'big'")
.execute();
id
title
author
description
3
Tootle
Gertrude Crampton
Little toy train has big dreams.
5
Harry Potter and the Goblet of Fire
J.K. Rowling
Fourth year of school starts, big drama ensues.
查找所有description
或title
包含little
单词的books
:
select
*
from
books
where
to_tsvector(description || ' ' || title) -- concat columns, but be sure to include a space to separate them!
@@ to_tsquery('little');
id
title
author
description
1
The Poky Little Puppy
Janette Sebring Lowrey
Puppy is slower than other, bigger animals.
3
Tootle
Gertrude Crampton
Little toy train has big dreams.
为了找到所有description
包含 little
和 big
两个单词的 books
,我们可以使用&
符号:
select
*
from
books
where
to_tsvector(description)
@@ to_tsquery('little & big'); -- use & for AND in the search query
const { data, error } = await supabase
.from('books')
.select()
.textSearch('description', `'little' & 'big'`)
final result = await client
.from('books')
.select()
.textSearch('description', "'little' & 'big'")
.execute();
id
title
author
description
3
Tootle
Gertrude Crampton
Little toy train has big dreams.
查找所有description
包含little
或big
中任何一个单词的books
,使用|
符号。
select
*
from
books
where
to_tsvector(description)
@@ to_tsquery('little | big'); -- use | for OR in the search query
const { data, error } = await supabase
.from('books')
.select()
.textSearch('description', `'little' | 'big'`)
final result = await client
.from('books')
.select()
.textSearch('description', "'little' | 'big'")
.execute();
id
title
author
description
1
The Poky Little Puppy
Janette Sebring Lowrey
Puppy is slower than other, bigger animals.
3
Tootle
Gertrude Crampton
Little toy train has big dreams.
注意:搜索 big
如何包括 bigger
(或 biggest
等等)的结果。
现在我们有了全文搜索的功能,让我们来创建一个 索引
。这将允许Postgres预先 建立
文件,这样就不需要在我们执行查询的时候创建这些文件。这将使我们的查询速度大大加快。
我们在 books
表内创建一个新的列fts
来存储title
和description
列的可搜索索引。
我们可以使用Postgres的一个特殊功能,叫做生成的列 以确保当title
和description
列的值发生变化时,该索引会被更新。
alter table
books
add column
fts tsvector generated always as (to_tsvector('english', description || ' ' || title)) stored;
create index books_fts on books using gin (fts); -- generate the index
select id, fts
from books;
id
fts
1
‘anim’:7 ‘bigger’:6 ’littl’:10 ‘poki’:9 ‘puppi’:1,11 ‘slower’:3
2
’eat’:2 ‘peter’:8 ‘rabbit’:1,9 ’tale’:6 ‘veget’:4
3
‘big’:5 ‘dream’:6 ’littl’:1 ’tootl’:7 ’toy’:2 ’train’:3
4
‘chang’:3 ‘color’:9 ’eat’:7 ’egg’:12 ‘food’:4,10 ‘green’:11 ‘ham’:14 ‘prefer’:5 ‘sam’:1 ‘unus’:8
5
‘big’:6 ‘drama’:7 ’ensu’:8 ‘fire’:15 ‘fourth’:1 ‘goblet’:13 ‘harri’:9 ‘potter’:10 ‘school’:4 ‘start’:5 ‘year’:2
使用新列进行查询 link 现在我们已经创建并填充了我们的索引,我们可以使用与之前相同的技术来查询:
select
*
from
books
where
fts @@ to_tsquery('little & big');
const { data, error } = await supabase.from('books').select().textSearch('fts', `'little' & 'big'`)
final result = await client
.from('books')
.select()
.textSearch('fts', "'little' & 'big'")
.execute();
id
title
author
description
fts
3
Tootle
Gertrude Crampton
Little toy train has big dreams.
‘big’:5 ‘dream’:6 ’littl’:1 ’tootl’:7 ’toy’:2 ’train’:3
访问 PostgreSQL: 文本搜索功能和操作符 来了解更多的查询运算符,你可以用它来做更高级的 全文查询
,例如:
近似符: <->
link 近似符号对于搜索相隔一定 “距离 “的术语非常有用。
例如,要找到 big dreams
这个短语,在 big
的匹配后面紧跟着dreams
的匹配。
select
*
from
books
where
to_tsvector(description) @@ to_tsquery('big <-> dreams');
const { data, error } = await supabase
.from('books')
.select()
.textSearch('description', `'big' <-> 'dreams'`)
final result = await client
.from('books')
.select()
.textSearch('description', "'big' <-> 'dreams'")
.execute();
我们还可以使用<->
来查找彼此之间有一定距离的单词。例如,找到year
和school
间隔2个词之间的距离。
select
*
from
books
where
to_tsvector(description) @@ to_tsquery('year <2> school');
const { data, error } = await supabase
.from('books')
.select()
.textSearch('description', `'year' <2> 'school'`)
final result = await client
.from('books')
.select()
.textSearch('description', "'year' <2> 'school'")
.execute();
否定符号可以用来查找不包含搜索词的短语。
例如,找到有big
字但没有little
字的记录:
select
*
from
books
where
to_tsvector(description) @@ to_tsquery('big & !little');
const { data, error } = await supabase
.from('books')
.select()
.textSearch('description', `'big' & !'little'`)
final result = await client
.from('books')
.select()
.textSearch('description', "'big' & !'little'")
.execute();