全文搜索复合词

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 可以将复合词分解成它们的组成部分并为它们编制索引,以便它们可以找到。

我在这里展示德语的例子,其中使用了很多复合词:

  1. 首先我们需要一个合适的Hunspell字典,里面有复合词规则。经过一段时间的研究,我找到了一个:https://github.com/vpikulik/hunspell_de_compounds。如果 Hunspell 词典的 *.aiff 文件包含像 compoundwords controlled _.

    这样的行,您可以看到 Hunspell 词典定义了复合词规则
  2. 将文件扩展名重命名为 *.affix*.dict。 Postgres 期望它是这样的。

  3. Postgres 希望 Hunspell 词典是 UTF8 编码的。因此,我用 Sublime Text 打开 *.affix*.dict 文件,为这两个文件调用 FileSave with EncodingUTF-8

  4. 您需要将这两个文件复制到数据库机器(或容器等)。在数据库机器上打开一个终端,并将文件移动到正确的位置:

    destination=$(echo $(pg_config --sharedir)/tsearch_data)
    mv de_DE.affix $destination
    mv de_DE.dict $destination
    

    此处,pg_config --sharedir 生成 Postgres 安装的共享目录。字典的目标是 tsearch_data 子目录。

  5. 连接到您的数据库(本地或远程),例如在本地通过 psql 命令。

  6. 现在,我们在 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;
      
  7. 完成。我们可以使用 ts_debug 命令测试它是否有效:

    SELECT * FROM ts_debug('german_hunspell', 'Wettersystemsimulationssoftware');
    

    这里,Wettersystemsimulationssoftware是德语合成词。它被分成 wettersystemsimulationsoftware。当用户搜索时对于 system,Postgres 会找到这个条目。

  8. 为了使用我们的配置,您必须为任何 to_tsvectorto_tsquerywebsearch_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 并找到该记录。不幸的是,经过几个小时的研究,我没有找到合适的英语词典。这是开源社区可以活跃起来的地方...