全文搜索复合词
Full text search for compound words
我正在研究 PostgreSQL 全文搜索,想知道是否可以搜索复合词的第二部分。
当我搜索 'cake' 时,有没有办法得到 'Cheesecake' 的结果?
-- Lets have a table like this:
CREATE TABLE IF NOT EXISTS table1(
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
col1 TEXT,
col1_tsv TSVECTOR
);
CREATE INDEX IF NOT EXISTS col1_index ON table1 USING gin(col1_tsv);
-- Insert some data into it:
INSERT INTO table1 (col1, col1_tsv)
VALUES ('Blacklist', TO_TSVECTOR('Blacklist')),('Cheesecake', TO_TSVECTOR('Cheesecake'));
如果我搜索 'cake' 或 'list',我找不到任何结果。
SELECT col1 FROM table1 WHERE col1_tsv @@ to_tsquery('english', 'list');
SELECT col1 FROM table1 WHERE col1_tsv @@ to_tsquery('english', 'cake');
用ts_lexize检查:
select ts_lexize('english_stem','Blacklist');
select ts_lexize('english_stem','Cheesecake');
输出:
ts_lexize
-------------
{blacklist}
(1 row)
ts_lexize
-------------
{cheesecak}
(1 row)
按设计工作,但有没有办法只通过搜索蛋糕来获得芝士蛋糕?
(我不是这个意思)
select * from table1 where col1 like '%cake%';
当我select整个table把Cheesecake也切成Cheesecake.
select * from table1;
id | col1 | col1_tsv
----+------------+---------------
1 | Blacklist | 'blacklist':1
2 | Cheesecake | 'cheesecak':1
全文搜索能够前缀匹配。参见:
但这只适用于左锚搜索。您的模式是右锚定的。
您可以在反向字符串上建立索引并使用反向模式进行搜索:
CREATE INDEX table1_col1_rtsv_idx ON table1 USING gin (TO_TSVECTOR('simple', reverse(col1)));
那么这个带有前缀搜索的查询可以使用新的索引:
SELECT col1 FROM table1
WHERE to_tsvector('simple', reverse(col1))
@@ to_tsquery('simple', reverse('cake') || ':*');
但我会考虑使用 三字母索引。参见:
- PostgreSQL LIKE query performance variations
CREATE INDEX table1_col1_gin_trgm_idx ON table1 USING gin (col1 gin_trgm_ops);
查询:
SELECT col1 FROM table1
WHERE col1 LIKE '%cake';
值得注意的是,模式是 '%cake'
,而不是 '%cake%'
,如果“cake”应该在字符串的末尾。但三元组索引也支持这一点:
SELECT col1 FROM table1
WHERE col1 LIKE '%cake%';
db<>fiddle here
文本搜索索引通常比三字母索引小得多 - 因此速度更快一些。还有很多其他细微差别...
如果你想正确处理复合并且对子字符串匹配不感兴趣,我认为你需要一个thesaurus dictionary。对于要搜索的每个复合材料,您必须添加
之类的条目
cheesecak : cheesecak chees cak
blacklist : blacklist black list
这样,您可以保留原始单词并添加其部分。
烦人,但没有自动检测复合材料的方法。例如,“havelock”与“lock”无关,“haberdasher”不需要“dash”。
对于这种情况有一个解决方案:您需要一个 Hunspell 字典来表示您想要支持的语言。这些词典还必须定义复合词规则。如果满足这些要求,Postgres 可以将复合词分解成它们的组成部分并为它们编制索引,以便它们可以找到。
我在这里展示德语的例子,其中使用了很多复合词:
首先我们需要一个合适的Hunspell字典,里面有复合词规则。经过一段时间的研究,我找到了一个:https://github.com/vpikulik/hunspell_de_compounds。如果 Hunspell 词典的 *.aiff
文件包含像 compoundwords controlled _
.
这样的行,您可以看到 Hunspell 词典定义了复合词规则
将文件扩展名重命名为 *.affix
和 *.dict
。 Postgres 期望它是这样的。
Postgres 希望 Hunspell 词典是 UTF8 编码的。因此,我用 Sublime Text 打开 *.affix
和 *.dict
文件,为这两个文件调用 File
、Save with Encoding
、UTF-8
。
您需要将这两个文件复制到数据库机器(或容器等)。在数据库机器上打开一个终端,并将文件移动到正确的位置:
destination=$(echo $(pg_config --sharedir)/tsearch_data)
mv de_DE.affix $destination
mv de_DE.dict $destination
此处,pg_config --sharedir
生成 Postgres 安装的共享目录。字典的目标是 tsearch_data
子目录。
连接到您的数据库(本地或远程),例如在本地通过 psql
命令。
现在,我们在 Postgres 中创建 (a) 我们自己的文本搜索字典和 (b) 我们自己的文本搜索配置。我们都称它们为 german_hunspell
。这是代码:
我们删除一个之前创建的配置+字典。为了以防万一,我们想重复这个过程,例如因为我们想用另一个字典。
DROP TEXT SEARCH DICTIONARY german_hunspell CASCADE;
我们创建字典:
CREATE TEXT SEARCH DICTIONARY german_hunspell
(TEMPLATE = ispell, DictFile = de_DE, AffFile = de_DE, Stopwords = german);
这里,DictFile = de_DE
,Postgres 需要一个文件de_DE.dict
;对于 AffFile = de_DE
Postgres 需要一个文件 de_DE.affix
.
我们通过从 Postgres 中提供的 german
配置派生来创建一个新的文本搜索配置:
CREATE TEXT SEARCH CONFIGURATION german_hunspell (COPY = german);
接下来,我们修改之前创建的配置。我们定义 Postgres 应该对各种单词使用我们的新配置。如果我们的 Hunspell 词典没有任何特定单词的规则,我们会将请求转发给德语的默认词干分析器:
ALTER TEXT SEARCH CONFIGURATION german_hunspell
ALTER MAPPING FOR asciiword, asciihword, hword_asciipart,
word, hword, hword_part WITH german_hunspell, german_stem;
完成。我们可以使用 ts_debug
命令测试它是否有效:
SELECT * FROM ts_debug('german_hunspell', 'Wettersystemsimulationssoftware');
这里,Wettersystemsimulationssoftware
是德语合成词。它被分成 wetter
、system
、simulation
、software
。当用户搜索时对于 system
,Postgres 会找到这个条目。
为了使用我们的配置,您必须为任何 to_tsvector
、to_tsquery
、websearch_to_tsquery
等命令指定它。这里有几个例子:
SELECT to_tsvector('german_hunspell', 'content goes here');
SELECT to_tsquery('german_hunspell', 'query goes here');
SELECT websearch_to_tsquery('german_hunspell', 'query goes here');
...
它也适用于大多数(任何?)语言,例如通过使用 C#,只要驱动程序允许您指定要使用的配置。某些驱动程序(如 C# 驱动程序)使用二进制接口与数据库进行通信。在这种情况下,您不能通过其名称来寻址配置,例如german_hunspell
。相反,您必须像这样查询它的 OID:
SELECT oid from pg_catalog.pg_ts_config where cfgname = 'german_hunspell';
然后,您可以缓存此 OID 并使用它。
您可以使用任何您想要的语言重复此过程。不幸的是,没有适用于所有可用语言的 Hunspell 词典。我想要一本英语词典。假设我的记录包含单词 Spaceship
,那么我希望用户能够搜索 Ship
并找到该记录。不幸的是,经过几个小时的研究,我没有找到合适的英语词典。这是开源社区可以活跃起来的地方...
我正在研究 PostgreSQL 全文搜索,想知道是否可以搜索复合词的第二部分。
当我搜索 'cake' 时,有没有办法得到 'Cheesecake' 的结果?
-- Lets have a table like this:
CREATE TABLE IF NOT EXISTS table1(
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
col1 TEXT,
col1_tsv TSVECTOR
);
CREATE INDEX IF NOT EXISTS col1_index ON table1 USING gin(col1_tsv);
-- Insert some data into it:
INSERT INTO table1 (col1, col1_tsv)
VALUES ('Blacklist', TO_TSVECTOR('Blacklist')),('Cheesecake', TO_TSVECTOR('Cheesecake'));
如果我搜索 'cake' 或 'list',我找不到任何结果。
SELECT col1 FROM table1 WHERE col1_tsv @@ to_tsquery('english', 'list');
SELECT col1 FROM table1 WHERE col1_tsv @@ to_tsquery('english', 'cake');
用ts_lexize检查:
select ts_lexize('english_stem','Blacklist');
select ts_lexize('english_stem','Cheesecake');
输出:
ts_lexize
-------------
{blacklist}
(1 row)
ts_lexize
-------------
{cheesecak}
(1 row)
按设计工作,但有没有办法只通过搜索蛋糕来获得芝士蛋糕? (我不是这个意思)
select * from table1 where col1 like '%cake%';
当我select整个table把Cheesecake也切成Cheesecake.
select * from table1;
id | col1 | col1_tsv
----+------------+---------------
1 | Blacklist | 'blacklist':1
2 | Cheesecake | 'cheesecak':1
全文搜索能够前缀匹配。参见:
但这只适用于左锚搜索。您的模式是右锚定的。
您可以在反向字符串上建立索引并使用反向模式进行搜索:
CREATE INDEX table1_col1_rtsv_idx ON table1 USING gin (TO_TSVECTOR('simple', reverse(col1)));
那么这个带有前缀搜索的查询可以使用新的索引:
SELECT col1 FROM table1
WHERE to_tsvector('simple', reverse(col1))
@@ to_tsquery('simple', reverse('cake') || ':*');
但我会考虑使用 三字母索引。参见:
- PostgreSQL LIKE query performance variations
CREATE INDEX table1_col1_gin_trgm_idx ON table1 USING gin (col1 gin_trgm_ops);
查询:
SELECT col1 FROM table1
WHERE col1 LIKE '%cake';
值得注意的是,模式是 '%cake'
,而不是 '%cake%'
,如果“cake”应该在字符串的末尾。但三元组索引也支持这一点:
SELECT col1 FROM table1
WHERE col1 LIKE '%cake%';
db<>fiddle here
文本搜索索引通常比三字母索引小得多 - 因此速度更快一些。还有很多其他细微差别...
如果你想正确处理复合并且对子字符串匹配不感兴趣,我认为你需要一个thesaurus dictionary。对于要搜索的每个复合材料,您必须添加
之类的条目cheesecak : cheesecak chees cak
blacklist : blacklist black list
这样,您可以保留原始单词并添加其部分。
烦人,但没有自动检测复合材料的方法。例如,“havelock”与“lock”无关,“haberdasher”不需要“dash”。
对于这种情况有一个解决方案:您需要一个 Hunspell 字典来表示您想要支持的语言。这些词典还必须定义复合词规则。如果满足这些要求,Postgres 可以将复合词分解成它们的组成部分并为它们编制索引,以便它们可以找到。
我在这里展示德语的例子,其中使用了很多复合词:
首先我们需要一个合适的Hunspell字典,里面有复合词规则。经过一段时间的研究,我找到了一个:https://github.com/vpikulik/hunspell_de_compounds。如果 Hunspell 词典的
这样的行,您可以看到 Hunspell 词典定义了复合词规则*.aiff
文件包含像compoundwords controlled _
.将文件扩展名重命名为
*.affix
和*.dict
。 Postgres 期望它是这样的。Postgres 希望 Hunspell 词典是 UTF8 编码的。因此,我用 Sublime Text 打开
*.affix
和*.dict
文件,为这两个文件调用File
、Save with Encoding
、UTF-8
。您需要将这两个文件复制到数据库机器(或容器等)。在数据库机器上打开一个终端,并将文件移动到正确的位置:
destination=$(echo $(pg_config --sharedir)/tsearch_data) mv de_DE.affix $destination mv de_DE.dict $destination
此处,
pg_config --sharedir
生成 Postgres 安装的共享目录。字典的目标是tsearch_data
子目录。连接到您的数据库(本地或远程),例如在本地通过
psql
命令。现在,我们在 Postgres 中创建 (a) 我们自己的文本搜索字典和 (b) 我们自己的文本搜索配置。我们都称它们为
german_hunspell
。这是代码:我们删除一个之前创建的配置+字典。为了以防万一,我们想重复这个过程,例如因为我们想用另一个字典。
DROP TEXT SEARCH DICTIONARY german_hunspell CASCADE;
我们创建字典:
CREATE TEXT SEARCH DICTIONARY german_hunspell (TEMPLATE = ispell, DictFile = de_DE, AffFile = de_DE, Stopwords = german);
这里,
DictFile = de_DE
,Postgres 需要一个文件de_DE.dict
;对于AffFile = de_DE
Postgres 需要一个文件de_DE.affix
.我们通过从 Postgres 中提供的
german
配置派生来创建一个新的文本搜索配置:CREATE TEXT SEARCH CONFIGURATION german_hunspell (COPY = german);
接下来,我们修改之前创建的配置。我们定义 Postgres 应该对各种单词使用我们的新配置。如果我们的 Hunspell 词典没有任何特定单词的规则,我们会将请求转发给德语的默认词干分析器:
ALTER TEXT SEARCH CONFIGURATION german_hunspell ALTER MAPPING FOR asciiword, asciihword, hword_asciipart, word, hword, hword_part WITH german_hunspell, german_stem;
完成。我们可以使用
ts_debug
命令测试它是否有效:SELECT * FROM ts_debug('german_hunspell', 'Wettersystemsimulationssoftware');
这里,
Wettersystemsimulationssoftware
是德语合成词。它被分成wetter
、system
、simulation
、software
。当用户搜索时对于system
,Postgres 会找到这个条目。为了使用我们的配置,您必须为任何
to_tsvector
、to_tsquery
、websearch_to_tsquery
等命令指定它。这里有几个例子:SELECT to_tsvector('german_hunspell', 'content goes here'); SELECT to_tsquery('german_hunspell', 'query goes here'); SELECT websearch_to_tsquery('german_hunspell', 'query goes here'); ...
它也适用于大多数(任何?)语言,例如通过使用 C#,只要驱动程序允许您指定要使用的配置。某些驱动程序(如 C# 驱动程序)使用二进制接口与数据库进行通信。在这种情况下,您不能通过其名称来寻址配置,例如
german_hunspell
。相反,您必须像这样查询它的 OID:SELECT oid from pg_catalog.pg_ts_config where cfgname = 'german_hunspell';
然后,您可以缓存此 OID 并使用它。
您可以使用任何您想要的语言重复此过程。不幸的是,没有适用于所有可用语言的 Hunspell 词典。我想要一本英语词典。假设我的记录包含单词 Spaceship
,那么我希望用户能够搜索 Ship
并找到该记录。不幸的是,经过几个小时的研究,我没有找到合适的英语词典。这是开源社区可以活跃起来的地方...