我有一个大的postgres位置(商店,地标等)表,用户可以通过各种方式搜索。当用户想要搜索某个地点的名称时,系统当前会这样做(假设搜索是在咖啡馆):
lower(location_name) LIKE '%cafe%'
作为查询的一部分。这非常低效。这是非常的。我必须加快速度。我试过将表格编入索引
gin(to_tsvector('simple', location_name))
并搜索
(to_tsvector('simple',location_name) @@ to_tsquery('simple','cafe'))
效果很好,并且将搜索时间减少了几个数量级。
但是,位置名称可以是任何语言,包括中文等不是以空格分隔的语言。除非我搜索确切的名称,否则这个新系统无法找到任何中文位置,而旧系统可以找到与部分名称匹配的内容。
所以,我的问题是:我可以让它同时适用于所有语言,还是我走错了路?
答案 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建议的基于三元组的方法,然后我统一显示结果,寻找常用术语。
答案 2 :(得分:1)
与@willglynn已经发布的内容类似,我会考虑pg_trgm模块。但最好使用 GiST 索引:
CREATE INDEX tbl_location_name_trgm_idx
USING gist(location_name gist_trgm_ops);
gist_trgm_ops
运算符类通常会忽略大小写,ILIKE
与LIKE
一样快。 Quoting the source code:
注意:IGNORECASE宏意味着三元组不区分大小写。
我在这里使用COLLATE "C"
- 实际上没有特殊的排序规则(字节顺序),因为你的列中显然有各种排序规则。整理与排序或范围相关,对于基本相似性搜索,您可以不使用它。我会考虑为您的专栏设置COLLATE "C"
。
此索引将为您的第一个简单形式的查询提供支持:
SELECT * FROM tbl WHERE location_name ILIKE '%cafe%';
%
operator和set_limit()
。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
这只会有用,如果您只需要每个查询特定语言的结果,并且在这种情况下非常快。