我知道当你使用includes
并where
在连接表上指定一个子句时,你应该使用.references
例:
# will error out or throw deprecation warning in logs users = User.includes(:orders).where("Orders.cost < ?", 20)
在rails 4或更高版本中,您将收到如下错误:
Mysql2 ::错误:'where子句'中的未知列'Orders.cost':SELECT customers.*FROM customers WHERE(Orders.cost <100)
或者您将收到弃用警告:
弃用警告:您似乎急于加载在字符串SQL片段中引用的表(用户,地址之一).例如:
Post.includes(:comments).where("comments.title ='foo'")目前,Active Record识别字符串中的表,并且知道将comments表连接到查询,而不是在单独的查询中加载注释.但是,在不编写完整的SQL解析器的情况下执行此操作本身就存在缺陷.由于我们不想编写SQL解析器,因此我们将删除此功能.从现在开始,当您从字符串引用表时,必须明确告诉Active Record:
Post.includes(:comments).where("comments.title ='foo'").references(:comments)
如果您不依赖隐式连接引用,则可以通过设置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 ,"地址"."id"AS t1_r0,"地址"."user_id"AS t1_r1,"地址"."country"AS t1_r2,"地址"."street"AS t1_r3,"地址"."postal_code"AS t1_r4 ,"地址"."city"AS t1_r5,"地址"."created_at"AS t1_r6,"地址"."updated_at"AS t1_r7 FROM"users"LEFT OUTER JOIN"地址"ON"地址"."user_id"="用户"."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_load
和preload
.我知道,因为我上面的查询是在连接表上进行过滤(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 )
这看起来很奇怪.为什么.references
需要为includes
查询版本指定,.references
而不需要为eager_load
查询版本指定?我在这里错过了什么?
它归结为他们在弃用警告中提到的问题:
目前,Active Record识别字符串中的表,并且知道将comments表连接到查询,而不是在单独的查询中加载注释.但是,在不编写完整的SQL解析器的情况下执行此操作本身就存在缺陷.由于我们不想编写SQL解析器,因此我们将删除此功能.
在旧版本中,Rails的尝试是有关选择使用查询模式有帮助的,includes
会使用preload
的策略,如果它可以,但切换到eager_load
策略时,它看起来就像你在引用连接表的东西.但是,如果没有一个完整的SQL语法分析程序搞清楚什么表实际上是引用,它像一个正则表达式解析XHTML -你可以得到一些事情做好,但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
,但保持includes
和references
分离允许其查询模式的实现灵活性.您可以想象.includes(:orders, :addresses).references(:orders)
并addresses
加载了第二种preload
查询,因为在连接期间不需要它(尽管Rails实际上只包括addresses
在连接中).您不需要指定references
何时使用,eager_load
因为eager_load
始终加入,preload
总是进行多次查询.所有这些references
都是指示includes
使用必要的eager_load
策略并指定需要哪些表.