简体   繁体   English

活动记录-带有OR的链式查询

[英]Active Record - Chain Queries with OR

Rails: 4.1.2 滑轨:4.1.2

Database: PostgreSQL 数据库:PostgreSQL

For one of my queries, I am using methods from both the textacular gem and Active Record. 对于我的查询之一,我正在使用textacular gem和Active Record中的方法。 How can I chain some of the following queries with an "OR" instead of an "AND": 如何将以下某些查询链接为“或”而不是“与”:

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

I want to chain the last two scopes ( fuzzy_search and the where after it) together with an "OR" instead of an "AND." 我想用“ OR”而不是“ AND”将最后两个范围( fuzzy_search及其fuzzy_searchwhere )链接在一起。 So I want to retrieve all People who are approved AND (whose first name is similar to "Test" OR whose last name contains "Test"). 因此,我想检索所有已批准AND(其名字类似于“ Test”或其姓氏包含“ Test”的人)。 I've been struggling with this for quite a while, so any help would be greatly appreciated! 我已经为此苦苦挣扎了很长时间了,因此任何帮助将不胜感激!

I digged into fuzzy_search and saw that it will be translated to something like: 我深入研究了模糊搜索,发现它将被翻译成类似以下内容的内容:

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

That says if you don't care about preserving order, it will just filter the result by WHERE (("people"."first_name" % 'abc')) 也就是说,如果您不关心保留顺序,它将仅按WHERE (("people"."first_name" % 'abc'))过滤结果

Knowing that and now you can simply write the query with similar functionality: 知道了这一点,现在您可以简单地编写具有类似功能的查询:

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

In case you want order, please specify what would you like the order will be after joining 2 conditions. 如果您想订购,请指定加入两个条件后订购的商品。

After a few days, I came up with the solution! 几天后,我想出了解决方案! Here's what I did: 这是我所做的:

This is the query I wanted to chain together with an OR: 这是我想与OR链接在一起的查询:

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

As Hoang Phan suggested, when you look in the console, this produces the following SQL: 正如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

I then dug into the textacular gem and found out how the rank is generated. 然后,我挖出了织纹宝石,并发现了等级的产生方式。 I found it in the textacular.rb file and then crafted the SQL query using it. 我在textacular.rb文件中找到了它,然后使用它编写了SQL查询。 I also replaced the "AND" that connected the last two conditions with an "OR": 我还用“ OR”替换了连接最后两个条件的“ AND”:

# 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"

I took out all of quotation marks in the SQL query when referring to tables and fields because it was giving me error messages when I kept them there and even if I used single quotes. 在引用表和字段时,我将SQL查询中的所有引号都删除了,因为当我将它们保留在其中时,即使使用单引号,它也会给我错误消息。

Then, I used the find_by_sql method to retrieve the People object IDs in an array. 然后,我使用find_by_sql方法检索数组中的People对象ID。 The symbols ( :status , :query , :query_like ) are used to protect against SQL injections, so I set their values accordingly: 符号( :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)

I get the IDs and not the People objects in an array because find_by_sql returns an Array object and not a CollectionProxy object, as would normally be returned, so I cannot use ActiveRecord query methods such as where on this array. 我得到ID而不是数组中的People对象,因为find_by_sql返回的是Array对象,而不是通常返回的CollectionProxy对象,因此我无法使用ActiveRecord查询方法,例如该数组上的where Using the IDs, we can execute another query to get a CollectionProxy object. 使用这些ID,我们可以执行另一个查询来获取CollectionProxy对象。 However, there's one problem: If we were to simply run People.where(id: people_ids) , the order of the IDs would not be preserved, so all the relevance ranking we did was for nothing. 但是,存在一个问题:如果仅运行People.where(id: people_ids) ,则ID的顺序将不会保留,因此我们所做的所有相关性排名都没有用。

Fortunately, there's a nice gem called order_as_specified that will allow us to retrieve all People objects in the specific order of the IDs. 幸运的是,有一个名为order_as_specified的漂亮gem,它使我们可以按ID的特定顺序检索所有People对象。 Although the gem would work, I didn't use it and instead wrote a short line of code to craft conditions that would preserve the order. 尽管该gem可以工作,但我没有使用它,而是编写了一小段代码来编写可以保留订单的条件。

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

If our people_ids array is [1, 12, 3] , it would create the following ORDER statement: 如果我们的people_ids数组为[1, 12, 3] people_ids [1, 12, 3] ,它将创建以下ORDER语句:

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

I learned from this comment that writing an ORDER statement in this way would preserve the order. 我从此注释中学到,以这种方式编写ORDER语句将保留顺序。

Now, all that's left is to retrieve the People objects from ActiveRecord, making sure to specify the order. 现在,剩下的就是从ActiveRecord检索People对象,并确保指定顺序。

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

And that did it! 做到了! I didn't worry about removing any duplicate IDs because ActiveRecord does that automatically when you run the where command. 我不必担心删除任何重复的ID,因为ActiveRecord在运行where命令时会自动执行。

I understand that this code is not very portable and would require some changes if any of the people table's columns are modified, but it works perfectly and seems to execute only one query according to the console. 我知道这段代码不是很容易移植,如果修改了people表的任何列,都需要进行一些更改,但是它可以正常工作,并且似乎只能根据控制台执行一个查询。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM