44

我在基于 RoR 的网站上的搜索功能有点问题。我有很多带有一些代码的产品。此代码可以是任何字符串,例如“AB-123-lHdfj”。现在我使用ILIKE运算符来查找产品:

Product.where("code ILIKE ?", "%" + params[:search] + "%")

它工作正常,但找不到带有“AB123-lHdfj”或“AB123lHdfj”等代码的产品。

我该怎么办?可能是 Postgres 有一些字符串规范化功能,或者其他一些可以帮助我的方法?

4

2 回答 2

61

Postgres 提供了一个具有几个字符串比较功能的模块,例如 soundex 和 metaphone。但是你会想要使用levenshtein编辑距离功能。

Example:

test=# SELECT levenshtein('GUMBO', 'GAMBOL');
 levenshtein
-------------
           2
(1 row)

是两个词之间的2编辑距离。当您将其应用于多个单词并按编辑距离结果排序时,您将获得您正在寻找的模糊匹配类型。

试试这个查询示例:(当然使用您自己的对象名称和数据)

SELECT * 
FROM some_table
WHERE levenshtein(code, 'AB123-lHdfj') <= 3
ORDER BY levenshtein(code, 'AB123-lHdfj')
LIMIT 10

这个查询说:

给我来自 some_table 的所有数据的前 10 个结果,其中代码值与输入 'AB123-lHdfj' 之间的编辑距离小于 3。您将返回代码值在 3 个字符以内的所有行AB123-lHdfj'...

注意:如果您收到如下错误:

function levenshtein(character varying, unknown) does not exist

使用以下命令安装fuzzystrmatch扩展:

test=# CREATE EXTENSION fuzzystrmatch;
于 2011-10-11T17:35:02.150 回答
49

保罗告诉过你levenshtein()。这是一个非常有用的工具,但是对于大表来说它也很慢。它必须计算每一行与搜索词的Levenshtein 距离。这很昂贵并且不能使用索引。“加速”变体levenshtein_less_equal()对于长字符串更快,但在没有索引支持的情况下仍然很慢。

如果您的要求与示例所建议的一样简单,您仍然可以使用LIKE. 只需将-搜索词%中的任何内容替换为WHERE子句中的内容即可。所以而不是:

WHERE code ILIKE '%AB-123-lHdfj%'

采用:

WHERE code ILIKE '%AB%123%lHdfj%'

或者,动态地:

WHERE code ILIKE '%' || replace('AB-123-lHdfj', '-', '%') || '%'

%in LIKEpatterns 代表 0-n 个字符。或_仅用于一个字符。或者使用正则表达式进行更智能的匹配:

WHERE code ~* 'AB.?123.?lHdfj'

.?... 0 或 1 个字符

或者:

WHERE code ~* 'AB\-?123\-?lHdfj'

\-?... 0 或 1 个破折号

您可能希望转义LIKE或正则表达式模式中的特殊字符。看:


如果您的实际问题更复杂并且您需要更快的解决方案,那么有多种选择,具体取决于您的要求:

  • 当然,还有全文搜索。但这在您的情况下可能是矫枉过正。

  • 更可能的候选者是与附加模块pg_trgm 进行三元组匹配。看:

    可以与LIKE, ILIKE,~~*PostgreSQL 9.1 之后的组合使用。
    在这种情况下也很有趣:该模块的similarity()函数或%运算符。

  • 最后但并非最不重要的一点是,您可以实现一个手工编织的解决方案,该解决方案具有标准化要搜索的字符串的功能。例如,您可以转换AB1-23-lHdfj--> ab123lhdfj,将其保存在附加列中,并使用以相同方式转换的术语进行搜索。

    或者在表达式上使用索引而不是冗余列。(涉及的功能必须是IMMUTABLE。)可能与pg_tgrm上面的结合。

模式匹配技术概述:

于 2011-10-12T23:28:13.210 回答