【问题标题】:How to index a postgres table by name, when the name can be in any language?当名称可以是任何语言时,如何按名称索引 postgres 表?
【发布时间】:2012-10-01 15:15:33
【问题描述】:

我有一个大型 postgres 位置表(商店、地标等),用户可以通过各种方式进行搜索。当用户想要搜索一个地方的名称时,系统当前会这样做(假设搜索是在咖啡馆上):

lower(location_name) LIKE '%cafe%'

作为查询的一部分。这是非常低效的。禁止如此。我必须让它更快。我已经尝试在

上索引表
gin(to_tsvector('simple', location_name))

和搜索

(to_tsvector('simple',location_name) @@ to_tsquery('simple','cafe'))

效果很好,并且将搜索时间缩短了几个数量级。

但是,位置名称可以是任何语言,包括没有空格分隔的中文等语言。这个新系统找不到任何中文位置,除非我搜索确切的名称,而旧系统可以找到部分名称的匹配。

所以,我的问题是:我可以让它同时适用于所有语言,还是我走错了路?

【问题讨论】:

    标签: postgresql localization indexing tokenize string-comparison


    【解决方案1】:

    类似于@willglynn 已经发布的内容,我会考虑pg_trgm 模块。但最好使用 GiST 索引:

    CREATE INDEX tbl_location_name_trgm_idx
    USING gist(location_name gist_trgm_ops);
    

    gist_trgm_ops 运算符类通常忽略大小写,ILIKELIKE 一样快。 Quoting the source code:

    注意:IGNORECASE 宏表示三元组不区分大小写。

    我在这里使用COLLATE "C" - 这实际上不是特殊的排序规则(而是字节顺序),因为您的列中显然混合了各种排序规则。排序规则与排序或范围相关,对于基本的相似性搜索,您可以不用它。我会考虑为您的专栏设置COLLATE "C"

    此索引将为您的第一个简单形式的查询提供支持:

    SELECT * FROM tbl WHERE location_name ILIKE '%cafe%';
    
    • 非常快。
    • 保留查找部分匹配项的功能。
    • 添加了模糊搜索功能。
      查看% operatorset_limit()
    • GiST 索引对于使用LIMIT n 的查询来选择n 个“最佳”匹配项也非常快。您可以在上面的查询中添加:

    ORDER BY location_name <-> 'cafe'
    LIMIT 20
    

    详细了解“距离”运算符&lt;-&gt;in the manual here

    甚至:

    SELECT *
    FROM   tbl
    WHERE  location_name ILIKE '%cafe%'        -- exact partial match
    OR     location_name %     'cafe'          -- fuzzy match
    ORDER  BY 
           (location_name ILIKE 'cafe%') DESC  -- exact beginning first
          ,(location_name ILIKE '%cafe%') DESC -- exact partial match next
          ,(location_name <->   'cafe')        -- then "best" matches
          ,location_name                       -- break remaining ties (collation!)
    LIMIT  20;
    

    我在几个应用程序中使用类似的东西以获得(对我而言)令人满意的结果。当然,结合应用多个功能,它会变得有点慢。找到你的甜蜜点...

    您可以更进一步,为每种语言创建一个单独的partial index,并为每种语言使用匹配的排序规则:

    CREATE INDEX location_name_trgm_idx
    USING gist(location_name COLLATE "de_DE" gist_trgm_ops)
    WHERE location_name_language = 'German';
    
    -- repeat for each language
    

    这只是有用的,如果您只希望每个查询的特定语言的结果,并且在这种情况下会非常快。

    【讨论】:

      【解决方案2】:

      如果您想优化任意子字符串匹配,一种选择是使用the pg_tgrm module。添加索引:

      CREATE INDEX table_location_name_trigrams_key ON table
        USING gin (location_name gin_trgm_ops);
      

      这会将“Simple Cafe”分解为“sim”、“imp”、“mpl”等,并为每行中的每个 trigam 添加一个条目。然后,查询计划器可以自动将此索引用于子字符串模式匹配,包括:

      SELECT * FROM table WHERE location_name ILIKE '%cafe%';
      

      此查询将在索引中查找“caf”和“afe”,找到交集,获取这些行,然后根据您的模式检查每一行。 (最后的检查是必要的,因为“caf”和“afe”的交集匹配“simple cafe”和“unsafe scaffolding”,而“%cafe%”应该只匹配一个)。随着输入模式变得更长,索引会变得更有效,因为它可以排除更多行,但它仍然不如索引整个单词那么有效,所以不要期望性能会比to_tsvector 有所提高。

      注意的是,三元组对于三个字符以下的模式根本不起作用。这可能会或可能不会破坏您的应用程序。


      编辑:我最初将此作为评论添加。

      昨晚我大半睡着的时候,我有了另一个想法。创建一个cjk_chars 函数,它接受一个输入字符串,regexp_matches 整个 CJK Unicode 范围,并返回任何此类字符的数组,如果没有则返回 NULL。在cjk_chars(location_name) 上添加 GIN 索引。然后查询:

      WHERE CASE
        WHEN cjk_chars('query') IS NOT NULL THEN
          cjk_chars(location_name) @> cjk_chars('query')
          AND location_name LIKE '%query%'
        ELSE
          <tsvector/trigrams>
        END
      

      Ta-da,一元组!

      【讨论】:

      • 嗯...以单个汉字存储的信息量,我认为搜索仅包含两个字符的可能性不大。但是,由于我怀疑真正的答案将是不同技术的混合,我会记住这一点。
      • 昨晚我大半睡着的时候,我有了另一个想法。创建一个 cjk_chars 函数,它接受输入字符串 regexp_matches 整个 CJK Unicode 范围,并返回任何此类字符的数组,如果没有则返回 NULL。在cjk_chars(location_name) 上添加 GIN 索引。然后查询WHERE CASE WHEN cjk_chars('query') IS NOT NULL THEN cjk_chars(location_name) @&gt; cjk_chars('query') AND location_name LIKE '%input%' ELSE &lt;tsvector/trigrams&gt; END。 Ta-da,一元组!
      • 初步测试表明,cjk-unigrams 加上 ts_vectors 是一种非常好的方法。我非常喜欢它。我不知道是否还有其他需要介绍的边缘情况或字符集,但现在我可能知道足以自己处理它们。如果您将它添加到您的答案中,或者更好地为它创建一个新答案,我很乐意接受它。 :)
      【解决方案3】:

      对于多语言环境中的全文搜索,您需要将每个数据所在的语言与文本本身一起存储。然后,您可以使用 tsearch 函数的语言指定风格来获得正确的词干等。

      例如:

      CREATE TABLE location(
          location_name text, 
          location_name_language text
      );
      

      ...加上任何适当的约束,你可以写:

      CREATE INDEX location_name_ts_idx
      USING gin(to_tsvector(location_name_language, location_name));
      

      搜索:

      SELECT to_tsvector(location_name_language,location_name) @@ to_tsquery('english','cafe');
      

      无论您做什么,跨语言搜索都会出现问题。在实践中,我会使用多种匹配策略:我会将搜索词与simple 配置中的tsvectorsimple 中的tsvector 以及文本的存储语言进行比较。我也可能会使用 willglynn 建议的基于三元组的方法,然后我会统一显示结果,寻找常用术语。

      您可能会发现 Pg 的全文搜索过于有限,在这种情况下,您可能需要查看 Lucerne / Solr 之类的内容。

      见: *controlling full text search。 * tsearch dictionaries

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-09-09
        • 2011-01-26
        • 2020-09-09
        • 1970-01-01
        • 2014-11-01
        相关资源
        最近更新 更多