【问题标题】:Active Record - Chain Queries with ORActive Record - 使用 OR 的链式查询
【发布时间】:2015-12-06 22:02:51
【问题描述】:

导轨:4.1.2

数据库:PostgreSQL

对于我的一个查询,我使用了来自 textacular gem 和 Active Record 的方法。如何使用“OR”而不是“AND”链接以下一些查询:

people = People.where(status: status_approved).fuzzy_search(first_name: "Test").where("last_name LIKE ?", "Test")

我想将最后两个作用域(fuzzy_search 和之后的where)用“OR”而不是“AND”链接在一起。所以我想检索所有被批准的人(他们的名字类似于“测试”或姓氏包含“测试”)。我已经为此苦苦挣扎了很长一段时间,所以任何帮助将不胜感激!

【问题讨论】:

标签: ruby-on-rails postgresql ruby-on-rails-4 rails-activerecord


【解决方案1】:

我研究了fuzzy_search,发现它会被翻译成这样的:

SELECT "people".*, COALESCE(similarity("people"."first_name", 'test'), 0) AS "rankxxx" 
FROM "people" 
WHERE (("people"."first_name" % 'abc'))  
ORDER BY "rankxxx" DESC

也就是说,如果您不关心保留顺序,它只会按 WHERE (("people"."first_name" % 'abc')) 过滤结果

知道了这一点,现在您可以简单地编写具有类似功能的查询:

People.where(status: status_approved)
      .where('(first_name % :key) OR (last_name LIKE :key)', key: 'Test')

如果您要订购,请注明您希望在加入2个条件后的订单。

【讨论】:

  • 感谢您的回答!这很有帮助!我更喜欢在查询中使用散列条件,因为它们更简洁,更容易修改,但如果必须,我将使用原始 SQL。 fuzzy_search 使用“rankxxx”字段将结果从最相关到​​最不相关排序。但是,每次运行查询时,“rank”之后的“xxx”部分似乎是一个唯一的数字(可能是时间戳)。你知道我如何按“rankxxx”排序结果吗?
  • 我宁愿说你不能,gem使用随机数的原因是为了防止以后有人使用它。您可以使用与他们相同的选择(如COALESCE(similarity ...) AS <your variable>),但不建议这样做。在评估这两个条件时,您将面临麻烦。如果您在这种情况下关心订单,最好的选择是使用 activerecord 选择 2 次,然后汇总您想要的任何结果。您知道这不会对性能造成太大影响,而且更易于阅读和维护。
  • 感谢您的回复。我不相信执行两个查询会有所帮助,因为我不会得到排名。第一个查询的结果将是唯一按等级排序的结果。我已经复制了生成的查询并对其进行了修改,以便它可以与find_by_sql 方法一起使用。我添加了变量转义以防止 SQL 注入。我现在唯一的问题是,使用原始 SQL,我没有得到唯一的记录。我曾尝试在 SELECT 语句后添加 DISTINCT 关键字,但无济于事。
  • 我已经成功了!我希望 DISTINCT 关键字正常工作,但现在我使用uniq 方法在查询完成后过滤掉所有重复记录。我将在接下来的几天内发布我的答案。我基本上不得不使用原始 SQL,我不想这样做。
【解决方案2】:

几天后,我想出了解决方案!这是我所做的:

这是我想用 OR 链接在一起的查询:

people = People.where(status: status_approved).fuzzy_search(first_name: "Test").where("last_name LIKE ?", "Test")

正如 Hoang Phan 所建议的,当您查看控制台时,会生成以下 SQL:

SELECT "people".*, COALESCE(similarity("people"."first_name", 'test'), 0) AS "rank69146689305952314"
FROM "people"
WHERE "people"."status" = 1 AND (("people"."first_name" % 'Test')) AND (last_name LIKE 'Test')  ORDER BY "rank69146689305952314" DESC

然后我挖掘了 textacular gem 并发现了排名是如何生成的。我在 textacular.rb 文件中找到了它,然后使用它制作了 SQL 查询。我还将连接最后两个条件的“AND”替换为“OR”:

# Generate a random number for the ordering
rank = rand(100000000000000000).to_s

# Create the SQL query
sql_query = "SELECT people.*, COALESCE(similarity(people.first_name, :query), 0)" + 
            " AS rank#{rank} FROM people" +
            " WHERE (people.status = :status AND" +
            " ((people.first_name % :query) OR (last_name LIKE :query_like)))" + 
            " ORDER BY rank#{rank} DESC"

在引用表和字段时,我去掉了 SQL 查询中的所有引号,因为当我将它们保留在那里时,即使我使用单引号,它也会给我错误消息。

然后,我使用find_by_sql 方法检索数组中的People 对象ID。符号(:status:query:query_like)用于防止 SQL 注入,因此我相应地设置了它们的值:

# Retrieve all the IDs of People who are approved and whose first name and last name match the search query.
# The IDs are sorted in order of most relevant to the search query.
people_ids = People.find_by_sql([sql_query, query: "Test", query_like: "%Test%", status: 1]).map(&:id)

我得到的是数组中的 ID 而不是 People 对象,因为 find_by_sql 返回的是 Array 对象而不是 CollectionProxy 对象,因为通常会返回,所以我不能使用 ActiveRecord 查询方法,例如where 在这个数组上。使用 ID,我们可以执行另一个查询以获取 CollectionProxy 对象。但是,有一个问题:如果我们只是简单地运行People.where(id: people_ids),ID的顺序将不会被保留,所以我们所做的所有相关性排名都是徒劳的。

幸运的是,有一个名为 order_as_specified 的不错的 gem,它允许我们按照 ID 的特定顺序检索所有 People 对象。尽管 gem 可以工作,但我没有使用它,而是编写了一小行代码来制作可以保持顺序的条件。

order_by = people_ids.map { |id| "people.id='#{id}' DESC" }.join(", ")

如果我们的 people_ids 数组是 [1, 12, 3],它将创建以下 ORDER 语句:

"people.id='1' DESC, people.id='12' DESC, people.id='3' DESC"

我从this comment 了解到,以这种方式编写 ORDER 语句会保留顺序。

现在,剩下的就是从 ActiveRecord 中检索 People 对象,确保指定顺序。

people = People.where(id: people_ids).order(order_by)

然后就做到了!我不必担心删除任何重复的 ID,因为 ActiveRecord 会在您运行 where 命令时自动执行此操作。

我了解此代码的可移植性不是很好,如果修改了 people 表的任何列,则需要进行一些更改,但它运行良好,并且根据控制台似乎只执行一个查询。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-22
    • 2013-02-27
    • 1970-01-01
    相关资源
    最近更新 更多