SQLite3“LIKE”或PostgreSQL“ILIKE”的通用Ruby解决方案?

时间:2012-06-28 16:32:38

标签: ruby-on-rails sqlite postgresql search pattern-matching

我使用SQLite3进行开发,使用PostgreSQL进行部署。但是,我面临以下问题:

使用SQLite3进行简单搜索:

def self.search(search)
    if search
      find(:all, :conditions => ["style LIKE ? OR construction LIKE ?", "%#{search}%", "%#{search}%"])
    else
      find(:all)
    end
end

但是,它不适用于PostgreSQL,我需要替换LIKE ILIKE来解决问题:

def self.search(search)
    if search
      find(:all, :conditions => ["style ILIKE ? OR construction ILIKE ?", "%#{search}%", "%#{search}%"])
    else
      find(:all)
    end
end

是否有“Ruby方式”在任何数据库中进行这些搜索?

编辑 - 根据您的回答,我不相信我会找到一个通用的Ruby解决方案。

我已关注Ruby on Rails Tutorial: Learn Rails by Example - by Michael Hartl,最终Gemfile显示两个数据库......好吧,令人失望......

7 个答案:

答案 0 :(得分:45)

问题的根源位于此处:

  

我使用SQLite3进行开发,使用PostgreSQL进行部署。

这是一个坏主意™。你将继续遇到不相容的情况 - 或者更糟糕的是:在完成损害之前没有意识到这一点 使用相同的RDBMS(PostgreSQL)进行开发和生产,为您节省无意义的麻烦。


当您遇到不幸的设置时,有一个简单修复

lower(style) LIKE lower(?)

适用于两个平台。

  • 如果您提供小写搜索模式,则可以放弃右侧lower()

  • 在标准SQLite lower(X)中只折叠ASCII字母。更多信息,请引用Core Functions in the SQLite manual一章:

      

    lower(X)函数返回带有all的字符串X的副本   ASCII字符转换为小写。默认内置lower()   函数仅适用于ASCII字符。要进行大小写转换   非ASCII字符,加载ICU扩展名

    强调我的。

  • PostgreSQL lower(X)可与开箱即用的UTF-8配合使用。


作为一个受欢迎的副作用,您可以使用index on the expression lower(style) 加速在PostgreSQL中查询该查询,这比使用ILIKEstyle上的基本索引。

此外,自PostgreSQL 9.1起,您可以使用pg_trgm extension的GIN或GIST索引来加速任何 LIKEILIKE查询 - 三元组是大小写不敏感的。相关答案中的详细说明和链接:

答案 1 :(得分:9)

我认为Arel是解决这个问题的最好方法。它被Rails用于活动记录并且与数据库无关。您的代码在sqlite3或postgres中的工作方式相同,这似乎适合您的情况。使用匹配方法将自动切换到postgres环境中的ilike。 例如:

users=User.arel_table
User.where(users[:style].matches("%#{search}%").or(users[:construction].matches("%#{search}%")))

您可以从github获取更多信息:https://github.com/rails/arel/

答案 2 :(得分:5)

不,搜索数据库没有“ruby方式” - Ruby on Rails(特别是ActiveRecord)包含帮助方法,用于在ActiveRecord支持的RDB上执行CRUD操作,但是没有更好的方法使用LIKE搜索您提供的示例。

与此讨论相关的Rails文档部分为ActiveRecord::FinderMethods

作为旁注,您可以find(:all)而不是Person.exists?(['name LIKE ?', "%#{query}%"])

Rails文档使用与用于执行LIKE语句相同的语法,即:

where

使用上述方法非常安全。

下面这样的语句不安全的原因是因为params[:first_name]字符串直接传递到数据库查询而没有任何清理,这使得数据库可以被利用(即{{1}中的简单撇号可能会破坏整个查询并使数据库易受攻击 - 特别是SQL注入)。在上面的示例中,ActiveRecord可以清理您传递给查询的参数。

Client.where("first_name LIKE '%#{params[:first_name]}%'")

答案 3 :(得分:2)

不幸的是,我不认为你会找到一个很好的解决方案。您可以使用的唯一其他语法,而不是:conditions,是使用.where子句:

where(["style ILIKE ? OR construction ILIKE ?", "%#{search}%", "%#{search}%"])

不幸的是,正如您可能已经意识到的那样,这种替代语法会遇到完全相同的问题。这只是您在生产环境中拥有截然不同的开发环境时遇到的烦恼之一 - SQLite和PostgresSQL之间还存在其他相当大的差异。我建议只在你的开发机器上安装Postgres并使用它。它将使开发变得更加容易,并使您的代码更加清晰。

答案 4 :(得分:2)

虽然在生产和开发中使用不同的数据库不是一个好习惯,但使用squeel gem仍然是一个好例子:https://github.com/ernie/squeel/

有了它,您可以使用DSL编写查询,这些DSL比原始sql更容易且更清晰可读,并且gem将处理它对特定于所使用的RDBMS的SQL的翻译。

Ryan Bates有一个很好的视频:http://railscasts.com/episodes/354-squeel

答案 5 :(得分:2)

我曾经遇到过同样的问题,这是我的解决方案:

我写了一个小lib,可以为每个数据库使用该函数:

class AdapterSpecific

  class << self
    def like_case_insensitive
      case ActiveRecord::Base.connection.adapter_name
      when 'PostgreSQL'
        'ILIKE'
      else
        'LIKE'
      end
    end    

    def random
      #something
    end
  end

在模型中使用它:

def self.search(search)
    if search
      find(:all, :conditions => ["style #{AdapterSpecific.like_case_insensitive} :query OR construction #{AdapterSpecific.like_case_insensitive} :query", {:query => "%#{search}%"}])
    else
      find(:all)
    end
end

自编写以来,我将部署数据库迁移到Postgres,因为这是一个更好的设置,原因有几个(参见其他答案)。

您也可以考虑使用Postgres的全文搜索,以提高文字搜索的效率,如果您需要更多自定义,请参阅texticle了解基本实现,或pg_search

答案 6 :(得分:1)

使用LIKE进行搜索可能会对数据库造成痛苦。当然,它不会使用一个成本过高的指数。

更长的答案:我建议在开发中使用Postgres(放弃sqlite3),然后通过Postgres'style, construction类型在所有可搜索字段tsvector上使用全文索引。

使用索引时Postgres中的全文搜索非常快。