【问题标题】:Full text search for compound words复合词全文检索
【发布时间】:2021-09-24 20:00:34
【问题描述】:

我正在研究 PostgreSQL 全文搜索,想知道是否可以搜索复合词的第二部分。

它有办法得到'芝士蛋糕'作为结果当我搜索“蛋糕”时?

-- 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'));

如果我搜索“蛋糕”或“列表”,我没有得到任何结果。

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 * from table1;
 id |    col1    |   col1_tsv    
----+------------+---------------
  1 | Blacklist  | 'blacklist':1
  2 | Cheesecake | 'cheesecak':1

【问题讨论】:

    标签: sql database postgresql indexing full-text-search


    【解决方案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') || ':*');
    

    但我会考虑使用 trigram index。见:

    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”应该在字符串的末尾。但 trigram 索引也支持这一点:

    SELECT col1 FROM table1
    WHERE  col1 LIKE '%cake%';
    

    db小提琴here

    文本搜索索引通常比三元组索引要小得多,因此要快一些。还有很多其他细微的差别...

    【讨论】:

      【解决方案2】:

      如果您想正确处理复合并且对子字符串匹配不感兴趣,我认为您需要thesaurus dictionary。对于您要搜索的每个复合材料,您必须添加类似的条目

      cheesecak : cheesecak chees cak
      blacklist : blacklist black list
      

      这样,您可以保留原始单词并添加其部分。

      烦人,但没有自动检测复合材料的方法。例如,“havelock”与“lock”无关,“haberdasher”不需要“dash”。

      【讨论】:

        【解决方案3】:

        对于这种情况有一个解决方案:您需要一个 Hunspell 字典来表示您想要支持的语言。这些词典还必须定义复合词规则。如果满足这些要求,Postgres 可以将复合词分解成它们的组件并索引它们,以便它们变得可查找。

        我在这里举一个德语的例子,其中使用了许多复合词:

        1. 首先我们需要一个合适的带有复合词规则的Hunspell 字典。经过一段时间的研究,我找到了一个:https://github.com/vpikulik/hunspell_de_compounds。如果它的*.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 并找到该记录。不幸的是,经过几个小时的研究,我还没有找到合适的英语词典。这是开源社区可以活跃起来的地方......

        【讨论】:

        • 感谢 SommerEngineering 的回答,我还没有检查你的建议,但也许我可以用英文字典和 aff 文件帮助你:cgit.freedesktop.org/libreoffice/dictionaries/tree/en 但我还没有检查这是否可行(也我不知道 aff 和 affix 的区别)
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-05-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-01-11
        相关资源
        最近更新 更多