Rails:为什么where(id:objects)有效?

0ll*_*cks 3 ruby-on-rails

我有以下声明:

Customer.where(city_id: cities)
Run Code Online (Sandbox Code Playgroud)

这导致以下SQL语句:

SELECT customers.* FROM customers WHERE customers.city_id IN (SELECT cities.id FROM cities...
Run Code Online (Sandbox Code Playgroud)

这是预期的行为吗?它在某处记录了吗?我不会使用上面的Rails代码,而是使用以下之一:

Customer.where(city_id: cities.pluck(:id))
Run Code Online (Sandbox Code Playgroud)

要么

Customer.where(city: cities)
Run Code Online (Sandbox Code Playgroud)

这导致完全相同的SQL语句.

AJF*_*day 7

AREL查询库允许您将ActiveRecord对象作为快捷方式传递.然后它会将其主键属性传递给它用于联系数据库的SQL.

在查找多个对象时,AREL库将尝试在尽可能少的数据库往返中查找信息.它通过将您正在进行的查询作为一组条件来执行此操作,直到检索对象为止.

这种方式效率低下:

users = User.where(age: 30).all
#                           ^^^ get all these users from the database
memberships = Membership.where(user_id: users)
#                                       ^^^^^ This will pass in each of the ids as a condition
Run Code Online (Sandbox Code Playgroud)

基本上,这种方式会发出两个SQL语句:

select * from users where age = 30;
select * from memberships where user_id in (1, 2, 3);
Run Code Online (Sandbox Code Playgroud)

其中每个都涉及在应用程序和数据之间的网络端口上调用,然后通过同一端口传回.


这会更有效:

users = User.where(age: 30)
# This is still a query object, it hasn't asked the database for the users yet.
memberships = Membership.where(user_id: users)
# Note: this line is the same, but users is an AREL query, not an array of users
Run Code Online (Sandbox Code Playgroud)

它将构建一个单独的嵌套查询,因此它只需要对数据库进行一次往返.

select * from memberships 
where user_id in (
  select id from users where age = 30
);
Run Code Online (Sandbox Code Playgroud)

所以,是的,这是预期的行为.它有点像Rails魔术,它旨在提高您的应用程序的性能,而无需了解它的工作原理.


还有一些很酷的优化,比如你打电话first或last代替all它,它只会检索一条记录.

User.where(name: 'bob').all
# SELECT "USERS".* FROM "USERS" WHERE "USERS"."NAME" = 'bob'
User.where(name: 'bob').first
# SELECT "USERS".* FROM "USERS" WHERE "USERS"."NAME" = 'bob' AND ROWNUM <= 1
Run Code Online (Sandbox Code Playgroud)

或者,如果您设置了一个订单,然后调用最后一个,它将反转订单,然后只获取列表中的最后一个(而不是抓取所有记录,只给出最后一个记录).

User.where(name: 'bob').order(:login).first
# SELECT * FROM (SELECT "USERS".* FROM "USERS" WHERE "USERS"."NAME" = 'bob' ORDER BY login) WHERE ROWNUM <= 1
User.where(name: 'bob').order(:login).first
# SELECT * FROM (SELECT "USERS".* FROM "USERS" WHERE "USERS"."NAME" = 'bob' ORDER BY login DESC) WHERE ROWNUM <= 1
# Notice, login DESC
Run Code Online (Sandbox Code Playgroud)


Ser*_*sev 5

它为什么有效?

ActiveRecord查询构建器中的某些内容非常智能,可以看到如果传递数组或查询/条件,则需要构建IN子句.

这记录在哪里?

是的,http://guides.rubyonrails.org/active_record_querying.html#hash-conditions

2.3.3子集条件

如果要使用IN表达式查找记录,可以将数组传递给条件哈希:

Client.where(orders_count: [1,3,5])
Run Code Online (Sandbox Code Playgroud)

此代码将生成如下SQL:

SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5))
Run Code Online (Sandbox Code Playgroud)