2016-01-06 47 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策略並指定需要哪些表。