Heroku上的Rails 3.1中的Postgres重音不敏感LIKE搜索

如何在Rails中修改搜索查询的where / like条件:

find(:all, :conditions => ["lower(name) LIKE ?", "%#{search.downcase}%"])

无论重音如何,结果都匹配? (例如métro= metro)。 因为我使用的是utf8,所以我不能使用“to_ascii”。 生产正在Heroku上运行。

穷人的解决方案

如果您能够创建一个function,则可以使用此function。 我从这里开始编译列表并随着时间的推移添加到列表中。 它非常完整。 您甚至可能想要删除一些字符:

 CREATE OR REPLACE FUNCTION lower_unaccent(text) RETURNS text AS $func$ SELECT lower(translate($1 , '¹²³áàâãäåāăąÀÁÂÃÄÅĀĂĄÆćčç©ĆČÇĐÐèéêёëēĕėęěÈÊËЁĒĔĖĘĚ€ğĞıìíîïìĩīĭÌÍÎÏЇÌĨĪĬłŁńňñŃŇÑòóôõöōŏőøÒÓÔÕÖŌŎŐØŒř®ŘšşșߊŞȘùúûüũūŭůÙÚÛÜŨŪŬŮýÿÝŸžżźŽŻŹ' , '123aaaaaaaaaaaaaaaaaaacccccccddeeeeeeeeeeeeeeeeeeeeggiiiiiiiiiiiiiiiiiillnnnnnnooooooooooooooooooorrrsssssssuuuuuuuuuuuuuuuuyyyyzzzzzz' )); $func$ LANGUAGE sql IMMUTABLE; 

您的查询应该像这样工作:

 find(:all, :conditions => ["lower_unaccent(name) LIKE ?", "%#{search.downcase}%"]) 

对于左锚定搜索,您可以使用函数索引获得非常快速的结果:

 CREATE INDEX tbl_name_lower_unaccent_idx ON fest (lower_unaccent(name) text_pattern_ops); 

对于以下查询:

 SELECT * FROM tbl WHERE (lower_unaccent(name)) ~~ 'bob%' 

适当的解决方案

PostgreSQL 9.1+中 ,具有必要的权限,您可以:

 CREATE EXTENSION unaccent; 

它提供了一个函数unaccent() ,做你需要的(除了lower() ,只需要在需要时另外使用)。 阅读有关此扩展程序的手册 。
也适用于PostgreSQL 9.0,CREATE EXTENSION语法是9.1中的新function。

有关unaccent和索引的更多信息:

  • PostgreSQL是否支持“不区分重音”排序规则?

对于那些在为PostgreSQL添加unaccent扩展并且使用Rails应用程序时遇到问题的人来说,这是您需要创建的迁移:

 class AddUnaccentExtension < ActiveRecord::Migration def up execute "create extension unaccent" end def down execute "drop extension unaccent" end end 

当然,在rake db:migrate您将能够在查询中使用unaccent函数: unaccent(column) similar to ...unaccent(lower(column)) ...

首先,安装postgresql-contrib。 然后连接到数据库并执行:

 CREATE EXTENSION unaccent; 

为您的数据库启用扩展。

根据您的语言,您可能需要创建一个新规则文件(在我的案例中为greek.rules ,位于/usr/share/postgresql/9.1/tsearch_data ),或者只是附加到现有的unaccent.rules (非常简单)。

如果您创建自己的.rules文件,则需要将其设为默认值:

 ALTER TEXT SEARCH DICTIONARY unaccent (RULES='greek'); 

此更改是持久的,因此您无需重做它。

下一步是向模型添加方法以使用此函数。

一个简单的解决方案是在模型中定义一个函数。 例如:

 class Model < ActiveRecord::Base [...] def self.unaccent(column,value) a=self.where('unaccent(?) LIKE ?', column, "%value%") a end [...] end 

然后,我可以简单地调用:

 Model.unaccent("name","text") 

在没有模型定义的情况下调用相同的命令将如下所示:

 Model.where('unaccent(name) LIKE ?', "%text%" 

注意:上面的示例已经过测试,适用于postgres9.1,Rails 4.0,Ruby 2.0。

更新信息
通过@Henrik N的反馈修复了潜在的SQLi后门

有关您在StackExchange上搜索的2个问题: https ://serverfault.com/questions/266373/postgresql-accent-diacritic-insensitive-search

但是当你在Heroku上时,我怀疑这是一个很好的匹配(除非你有一个专门的数据库计划)。

在SO上也有这个: 从字符串中删除重音符号/变音符号,同时保留其他特殊字符 。

但这假设您的数据存储时没有任何重音。

我希望它会指出你正确的方向。

假设Foo是您要搜索的模型,而name是列。 结合Postgres 翻译和ActiveSupport的音译 。 你可以这样做:

 Foo.where( "translate( LOWER(name), 'âãäåāăąÁÂÃÄÅĀĂĄèééêëēĕėęěĒĔĖĘĚìíîïìĩīĭÌÍÎÏÌĨĪĬóôõöōŏőÒÓÔÕÖŌŎŐùúûüũūŭůÙÚÛÜŨŪŬŮ', 'aaaaaaaaaaaaaaaeeeeeeeeeeeeeeeiiiiiiiiiiiiiiiiooooooooooooooouuuuuuuuuuuuuuuu' ) LIKE ?", "%#{ActiveSupport::Inflector.transliterate("%qué%").downcase}%" )