当名称可以是任何语言时,如何按名称索引postgres表?

时间:2012-10-11 02:42:28

标签: postgresql localization indexing tokenize string-comparison

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

lower(location_name) LIKE '%cafe%'

作为查询的一部分。这非常低效。这是非常的。我必须加快速度。我试过将表格编入索引

gin(to_tsvector('simple', location_name))

并搜索

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

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

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

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

3 个答案:

答案 0 :(得分:4)

如果要优化任意子串匹配,一个选项是使用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”的交叉点都匹配“简单咖啡馆”和“不安全的脚手架”,而“%cafe%”应该只匹配一个)。随着输入模式变得更长,索引变得更有效,因为它可以排除更多行,但它仍然不如索引整个单词那样有效,因此不要期望性能提高to_tsvector

Catch是,对于三个字符以下的模式,三元组根本不起作用。这可能是也可能不是您申请的交易破坏者。


修改:我最初将其添加为评论。

昨晚,当我大部分时间睡着时,我又想到了另一个想法。创建一个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,unigrams!

答案 1 :(得分:2)

对于多语言环境中的全文搜索,您需要将每个数据所在的语言存储在其自身的文本旁边。然后,您可以使用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');

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

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

请参阅: * controlling full text search。 * tsearch dictionaries

答案 2 :(得分: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&#34;最佳&#34;火柴。您可以添加到上面的查询:

ORDER BY location_name <-> 'cafe'
LIMIT 20

详细了解&#34;距离&#34; operator <-> 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

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