活动记录 - 带OR的链查询

时间:2015-12-06 22:02:51

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

Rails: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_searchwhere之后)与" OR"连在一起。而不是" AND。"所以我想检索所有被批准的人(其名字与&#34相似;测试"或其姓氏包含"测试")。我已经挣扎了很长一段时间,所以任何帮助都会非常感激!

2 个答案:

答案 0 :(得分:0)

我深入研究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个条件后指定您希望订单的内容。

答案 1 :(得分:0)

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

这是我想与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.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个对象,通常会返回,所以我无法在此数组上使用where等ActiveRecord查询方法。使用ID,我们可以执行另一个查询来获取CollectionProxy对象。但是,有一个问题:如果我们只是简单地运行People.where(id: people_ids),那么ID的顺序将不会被保留,因此我们所做的所有相关性排名都是无用的。

幸运的是,有一个名为order_as_specified的好宝石,它允许我们按照ID的特定顺序检索所有People个对象。虽然宝石可以使用,但我并没有使用它,而是编写了一小段代码来制作可以保留订单的条件。

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表的任何列,则需要进行一些更改,但是它运行正常并且根据控制台似乎只执行一个查询