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}%" )