2016-01-06 49 views
2

我知道,当你使用includes并指定在加入表where条款,你应该使用.references比较.references要求包括对eager_load

例如:

# will error out or throw deprecation warning in logs 
users = User.includes(:orders).where("Orders.cost < ?", 20) 

在轨道4,5或以后,您将收到如下错误:

Mysql2::Error: Unknown column 'Orders.cost' in 'where clause': SELECT customers.* FROM customers WHERE (Orders.cost < 100)

或者您将收到弃用警告:

DEPRECATION WARNING: It looks like you are eager loading table(s) (one of: users, addresses) that are referenced in a string SQL snippet. For example:

Post.includes(:comments).where("comments.title = 'foo'") Currently, Active Record recognizes the table in the string, and knows to JOIN the comments table to the query, rather than loading comments in a separate query. However, doing this without writing a full-blown SQL parser is inherently flawed. Since we don't want to write an SQL parser, we are removing this functionality. From now on, you must explicitly tell Active Record when you are referencing a table from a string:

Post.includes(:comments).where("comments.title = 'foo'").references(:comments)

If you don't rely on implicit join references you can disable the feature entirely by setting config.active_record.disable_implicit_join_references = true. (

SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, "users"."email" AS t0_r2, "users"."created_at" AS t0_r3, "users"."updated_at" AS t0_r4, "addresses"."id" AS t1_r0, "addresses"."user_id" AS t1_r1, "addresses"."country" AS t1_r2, "addresses"."street" AS t1_r3, "addresses"."postal_code" AS t1_r4, "addresses"."city" AS t1_r5, "addresses"."created_at" AS t1_r6, "addresses"."updated_at" AS t1_r7 FROM "users" LEFT OUTER JOIN "addresses" ON "addresses"."user_id" = "users"."id" WHERE (addresses.country = 'Poland')

所以我们这样做:

# added .references(:orders) 
users = User.includes(:orders).where("Orders.cost < ?", 20).references(:orders) 

它执行得很好:

SELECT "users"."id"  AS t0_r0, 
    "users"."name"  AS t0_r1, 
    "users"."created_at" AS t0_r2, 
    "users"."updated_at" AS t0_r3, 
    "orders"."id"   AS t1_r0, 
    "orders"."cost"  AS t1_r1, 
    "orders"."user_id" AS t1_r2, 
    "orders"."created_at" AS t1_r3, 
    "orders"."updated_at" AS t1_r4 
FROM "users" 
LEFT OUTER JOIN "orders" 
ON "orders"."user_id" = "users"."id" 
WHERE (orders.cost < 20) 

我知道.includes仅仅是两个方法的包装:eager_loadpreload。我知道,因为我的查询以上(在这个例子中orders)做一个连接表的过滤器,includes很聪明,知道要挑eager_load实现超过preload因为preload不能处理这样的查询,因为preload连接不表。

这里是我困惑的地方。好的:所以在上面的那个查询中:引擎盖includes将使用eager_load实现。但请注意,当我为相同的查询明确使用eager_load(这是includes实质上是这样做的):我不需要使用.references!它运行查询并加载数据就好了。没有错误,没有折旧警告:

# did not specify .references(:orders), and yet no error and no deprecation warning 
users = User.eager_load(:orders).where("Orders.cost < ?", 20) 

而且它没有问题,执行完全相同的过程:

SELECT "users"."id"  AS t0_r0, 
    "users"."name"  AS t0_r1, 
    "users"."created_at" AS t0_r2, 
    "users"."updated_at" AS t0_r3, 
    "orders"."id"   AS t1_r0, 
    "orders"."cost"  AS t1_r1, 
    "orders"."user_id" AS t1_r2, 
    "orders"."created_at" AS t1_r3, 
    "orders"."updated_at" AS t1_r4 
FROM "users" 
LEFT OUTER JOIN "orders" 
ON "orders"."user_id" = "users"."id" 
WHERE (orders.cost < 20) 

这似乎很奇怪。为什么需要为includes版本的查询指定.references,而不需要为eager_load版本的查询指定.references?我在这里错过了什么?

回答

5

它归结为他们在弃用警告提问题:

Currently, Active Record recognizes the table in the string, and knows to JOIN the comments table to the query, rather than loading comments in a separate query. However, doing this without writing a full-blown SQL parser is inherently flawed. Since we don't want to write an SQL parser, we are removing this functionality.

在旧版本中,Rails的尝试是有关选择使用查询模式帮助,includes将使用preload策略,如果它可以,但是当它看起来像引用了连接表中的某些内容时,切换到eager_load策略。但是如果没有一个完整的SQL解析器计算出哪些表被实际引用,就像parsing XHTML with a Regex--你可以完成一些工作,但Rails无法在任何情况下正确地判断。试想一下:

User.includes(:orders).where("Orders.cost < 20") 

这是一个不错的,简单的例子,和Rails可以告诉你需要Orders加入。现在试试这个:

User.includes(:orders).where("id IN (select user_id from Orders where Orders.cost < 20)") 

这给了相同的结果,但加入Orders不必要的渲染子查询。这是一个人为的例子,我不知道Rails是否会决定是否需要加入第二个查询,但问题是有些情况下启发式可能会做出错误的决定。在这些情况下,Rails会执行不必​​要的连接,焚烧内存并减慢查询速度,或者不执行必要的连接,从而导致错误。

开发人员决定只向程序员询问是否需要连接,而不是维护启发式的非常糟糕的失败案例。你可以比Rails更容易地获得它(希望),当你错误的时候,很清楚要改变什么。

而不是添加references您可以切换到eager_load,但保持includesreferences分开允许其查询模式的实施灵活性。你可以想像得到.includes(:orders, :addresses).references(:orders),并且加载在第二个preload风格的查询中,因为在连接期间不需要它(尽管Rails实际上在连接中实际上只包含addresses)。当您使用eager_load时,您无需指定references,因为eager_load总是加入,其中preload总是会执行多个查询。所有references确实指示includes使用必要的eager_load策略并指定需要哪些表。