16

我使用 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,我需要更换LIKEforILIKE来解决问题:

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 教程:通过示例学习 Rails - 作者 Michael Hartl,其中最终的Gemfile显示了两个数据库......好吧,令人失望......

4

7 回答 7

50

问题的根源在这里:

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

这是个坏主意™。您将不断遇到不兼容问题 - 或者更糟:在损坏完成之前不会意识到一些问题。
使用相同的 RDBMS (PostgreSQL) 进行开发和生产,省去无谓的麻烦。


当您遇到不幸的设置时,有一个简单的解决方法:

lower(style) LIKE lower(?)

适用于两个平台。

  • lower()如果您提供小写搜索模式,您可以删除右手。

  • 在标准 SQLitelower(X)中,仅折叠 ASCII 字母。有关更多信息,我引用了 SQLite 手册中的核心函数一章:

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

    强调我的。

  • PostgreSQLlower(X)开箱即用地使用 UTF-8。


作为一个受欢迎的副作用,您可以在 PostgreSQL 中使用表达式上的索引来加速该查询,这将比使用和上的基本索引更快。 lower(style)ILIKEstyle

此外,从 PostgreSQL 9.1 开始,您可以使用带有pg_trgm扩展名的 GIN 或 GIST 索引来加速任何 查询 - 三元组不区分大小写。此相关答案中的详细说明和链接:LIKEILIKE

于 2012-07-01T01:29:01.833 回答
9

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

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

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

于 2012-07-04T05:41:46.073 回答
4

不,没有“红宝石方式”来搜索数据库——Ruby on Rails(特别是ActiveRecord)包含用于在 ActiveRecord 支持的 RDB 上执行 CRUD 操作的辅助方法,但是使用 LIKE 进行搜索的方法并没有比示例更好的方法你提供的。

Rails 文档中与此讨论相关的部分将是ActiveRecord::FinderMethods

作为旁注,find(:all)您可以只做all ,而不是做。

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

Person.exists?(['name LIKE ?', "%#{query}%"])

使用上述方法是非常安全的。

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

Client.where("first_name LIKE '%#{params[:first_name]}%'")
于 2012-06-30T20:03:59.127 回答
2

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

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

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

于 2012-06-28T16:42:55.357 回答
2

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

有了它,您可以使用 DSL 编写查询,该 DSL 比原始 sql 更简单、更清晰、更易读,并且 gem 将处理它到特定于所用 RDBMS 的 SQL 的转换。

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

于 2012-06-30T23:26:05.193 回答
2

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

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

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如果您需要更多自定义。

于 2012-07-02T22:10:57.467 回答
1

在数据库上搜索使用LIKE可能会很痛苦。当然,它不会使用成本过高的索引。

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

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

于 2012-07-01T01:03:41.883 回答