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

时间:2012-02-11 19:24:56

标签: ruby-on-rails postgresql rails-postgresql unaccent

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

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

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

5 个答案:

答案 0 :(得分:28)

穷人的解决方案

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

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()除外,如果需要,只需另外使用)。阅读manual about this extension 也可用于 PostgreSQL 9.0 ,但CREATE EXTENSION语法是9.1中的新功能。

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

答案 1 :(得分:14)

对于那些在为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)) ...

答案 2 :(得分:3)

首先,安装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后门

答案 3 :(得分:2)

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

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

SO上也有这个:Removing accents/diacritics from string while preserving other special chars

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

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

答案 4 :(得分:0)

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

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