TL; DR:此问题在https://github.com/skensell/SO-question-example有自己的示例应用,您可以使用它自行调试。我已经在这个问题上给了一次赏金,但我并不相信(或者我不理解)最重要的回答者的推理。我将再次给予此奖励,因为它给我带来了很大的挫折感。
原始问题
我有一个模型User
,它有一个像这样的关联:
has_many :avatars, -> { order([:sort_order => :asc,:created_at => :asc])}
我有一个端点,用于搜索用户并设置一个@users
变量供视图使用。这是我在调试器中找到的幽灵部分:
@users.first.avatars[0..2].map(&:id)
# => [2546, 2547, 2548]
# This is the correct order.
@users.to_a.first.avatars[0..2].map(&:id)
# => [2548, 2546, 2547]
# Wrong order.
这里发生了什么?
唯一的区别是to_a
。我甚至试图忽略to_a
,但我认为它被jbuilder隐式调用,因为我将它设置为json数组。
也许我搜索User
的方式与它有关?我使用了几个包含和连接。
更新
在这里,我可以向您展示rails控制台中这种奇怪行为的简单示例。看来,包含......参考是罪犯,但我不知道为什么或如何。
User.order(id: :desc)
.includes(:avatars, :industries)
.where(industries: {id: [5]})
.references(:industries)
.limit(5).to_a.second.avatars.map(&:id)
# => [2751, 2748, 2749]
# Wrong order.
User.order(id: :desc)
.includes(:avatars, :industries)
.where(industries: {id: [5]})
.references(:industries)
.limit(5).second.avatars.map(&:id)
# => [2748, 2749, 2751]
# Correct order.
我可以验证这些查询是否引用同一个用户,并且标记为正确顺序的那个查询确实是正确的sort_order
和created_at
(这是关联指定排序的方式)。
更新2
附加是请求的SQL日志。我将不相关的字段更改为" OMITTED"我使用' ...'。
替换了34个不相关的用户字段>> User.order(id: :desc).includes(:avatars, :industries).where(industries: {id: [5]}).references(:industries).limit(5).to_a.second.avatars.map(&:id)
SQL (18.5ms) SELECT DISTINCT "users"."id", "users"."id" AS alias_0 FROM "users" LEFT OUTER JOIN "avatars" ON "avatars"."user_id" = "users"."id" LEFT OUTER JOIN "user_professions" ON "user_professions"."user_id" = "users"."id" LEFT OUTER JOIN "industries" ON "industries"."id" = "user_professions"."industry_id" WHERE "industries"."id" IN (5) ORDER BY "users"."id" DESC LIMIT 5
SQL (8.3ms) SELECT "users"."id" AS t0_r0, "users"."OMITTED" AS t0_r1, "users"."OMITTED" AS t0_r2, ... AS t0_r36, "avatars"."id" AS t1_r0, "avatars"."user_id" AS t1_r1, "avatars"."avatar" AS t1_r2, "avatars"."created_at" AS t1_r3, "avatars"."updated_at" AS t1_r4, "avatars"."OMITTED" AS t1_r5, "avatars"."OMITTED" AS t1_r6, "avatars"."sort_order" AS t1_r7, "industries"."id" AS t2_r0, "industries"."name" AS t2_r1, "industries"."created_at" AS t2_r2, "industries"."updated_at" AS t2_r3 FROM "users" LEFT OUTER JOIN "avatars" ON "avatars"."user_id" = "users"."id" LEFT OUTER JOIN "user_professions" ON "user_professions"."user_id" = "users"."id" LEFT OUTER JOIN "industries" ON "industries"."id" = "user_professions"."industry_id" WHERE "industries"."id" IN (5) AND "users"."id" IN (1526, 945, 927, 888, 884) ORDER BY "users"."id" DESC
=> [2751, 2748, 2749]
>> User.order(id: :desc).includes(:avatars, :industries).where(industries: {id: [5]}).references(:industries).limit(5).second.avatars.map(&:id)
SQL (0.9ms) SELECT DISTINCT "users"."id", "users"."id" AS alias_0 FROM "users" LEFT OUTER JOIN "avatars" ON "avatars"."user_id" = "users"."id" LEFT OUTER JOIN "user_professions" ON "user_professions"."user_id" = "users"."id" LEFT OUTER JOIN "industries" ON "industries"."id" = "user_professions"."industry_id" WHERE "industries"."id" IN (5) ORDER BY "users"."id" DESC LIMIT 1 OFFSET 1
SQL (0.8ms) SELECT "users"."id" AS t0_r0, "users"."OMITTED" AS t0_r1, "users"."OMITTED" AS t0_r2, ... AS t0_r36, "avatars"."id" AS t1_r0, "avatars"."user_id" AS t1_r1, "avatars"."avatar" AS t1_r2, "avatars"."created_at" AS t1_r3, "avatars"."updated_at" AS t1_r4, "avatars"."OMITTED" AS t1_r5, "avatars"."OMITTED" AS t1_r6, "avatars"."sort_order" AS t1_r7, "industries"."id" AS t2_r0, "industries"."name" AS t2_r1, "industries"."created_at" AS t2_r2, "industries"."updated_at" AS t2_r3 FROM "users" LEFT OUTER JOIN "avatars" ON "avatars"."user_id" = "users"."id" LEFT OUTER JOIN "user_professions" ON "user_professions"."user_id" = "users"."id" LEFT OUTER JOIN "industries" ON "industries"."id" = "user_professions"."industry_id" WHERE "industries"."id" IN (5) AND "users"."id" IN (945) ORDER BY "users"."id" DESC
=> [2748, 2749, 2751]
>>
在这里,我附上一个日志,显示有问题的用户的头像(id,sort_order和created_at),这样你就可以看到订单应该是确定性的。
>> User.find(945).avatars.pluck(:id,:sort_order,:created_at)
User Load (5.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 945]]
(0.2ms) SELECT "avatars"."id", "avatars"."sort_order", "avatars"."created_at" FROM "avatars" WHERE "avatars"."user_id" = $1 ORDER BY "avatars"."sort_order" ASC, "avatars"."created_at" ASC [["user_id", 945]]
=> [[2748, 0, Fri, 13 Nov 2015 00:32:53 UTC +00:00], [2749, 0, Fri, 13 Nov 2015 00:47:02 UTC +00:00], [2751, 0, Fri, 13 Nov 2015 00:48:05 UTC +00:00]]
另外,我使用的是Rails 4.1.4和Ruby 2.1.10。
更新3
我在这里创建了一个示例应用:https://github.com/skensell/SO-question-example。在这个示例应用中,更奇怪的是to_a
并不重要。即使只有includes... references
,我的订单也会错误。
答案 0 :(得分:5)
@users.first.avatars[0..2].map(&:id)
# => [2546, 2547, 2548]
@users.to_a.first.avatars[0..2].map(&:id)
# => [2548, 2546, 2547]
- 这里发生了什么?
没有错。
根据Guide Retrieving a Single Object Section: 1.1.3 #first
第一种方法查找按主键排序的第一条记录 (默认)
SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1
以 @users.to_a
updated_at
订单返回的下一个方法
希望这对你有帮助!
答案 1 :(得分:2)
包括考虑父表的顺序,只有当它导致连接查询时才会检索其记录。即,在上述情况下,将跳过头像订单,并且当包含查询导致加入时将使用用户订单。您可以向用户添加默认范围并确认。
如果您仍希望按照定义的头像订单对user.avatars进行排序,则需要将include替换为联接。请注意,使用join将检索重复的用户记录。
按预期检索数据的工作解决方案是使用连接并包含在一起。
Loading development environment (Rails 4.1.4)
2.2.0 :001 > User.count
(0.1ms) SELECT COUNT(*) FROM "users"
=> 2
2.2.0 :002 > User.pluck :id, :name
(0.2ms) SELECT "users"."id", "users"."name" FROM "users"
=> [[1, "John"], [2, "Jill"]]
2.2.0 :003 > User.first.industries.pluck :id, :name
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
(0.2ms) SELECT "industries"."id", "industries"."name" FROM "industries" INNER JOIN "user_industries" ON "industries"."id" = "user_industries"."industry_id" WHERE "user_industries"."user_id" = ? [["user_id", 1]]
=> [[1, "Art"], [2, "Music"]]
2.2.0 :004 > User.last.industries.pluck :id, :name
User Load (1.4ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT 1
(0.2ms) SELECT "industries"."id", "industries"."name" FROM "industries" INNER JOIN "user_industries" ON "industries"."id" = "user_industries"."industry_id" WHERE "user_industries"."user_id" = ? [["user_id", 2]]
=> [[1, "Art"]]
2.2.0 :005 > User.first.avatars.pluck :id, :sort_order
User Load (0.4ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
(0.3ms) SELECT "avatars"."id", "avatars"."sort_order" FROM "avatars" WHERE "avatars"."user_id" = ? ORDER BY "avatars"."sort_order" ASC, "avatars"."created_at" ASC [["user_id", 1]]
=> [[1, 0], [3, 1], [2, 2]]
2.2.0 :006 > User.last.avatars.pluck :id, :sort_order
User Load (4.1ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT 1
(0.2ms) SELECT "avatars"."id", "avatars"."sort_order" FROM "avatars" WHERE "avatars"."user_id" = ? ORDER BY "avatars"."sort_order" ASC, "avatars"."created_at" ASC [["user_id", 2]]
=> [[4, 5], [6, 6], [5, 7]]
2.2.0 :007 > ap User.joins(:avatars, :industries).where(industries: {id: [1]}).references(:industries).count
(0.2ms) SELECT COUNT(*) FROM "users" INNER JOIN "avatars" ON "avatars"."user_id" = "users"."id" INNER JOIN "user_industries" ON "user_industries"."user_id" = "users"."id" INNER JOIN "industries" ON "industries"."id" = "user_industries"."industry_id" WHERE "industries"."id" IN (1)
6
=> nil
2.2.0 :008 > ap User.joins(:avatars, :industries).where(industries: {id: [1]}).references(:industries).uniq.count
(0.3ms) SELECT DISTINCT COUNT(DISTINCT "users"."id") FROM "users" INNER JOIN "avatars" ON "avatars"."user_id" = "users"."id" INNER JOIN "user_industries" ON "user_industries"."user_id" = "users"."id" INNER JOIN "industries" ON "industries"."id" = "user_industries"."industry_id" WHERE "industries"."id" IN (1)
2
=> nil
2.2.0 :009 > ap User.joins(:industries).where(industries: {id: [1]}).references(:industries).count
(0.3ms) SELECT COUNT(*) FROM "users" INNER JOIN "user_industries" ON "user_industries"."user_id" = "users"."id" INNER JOIN "industries" ON "industries"."id" = "user_industries"."industry_id" WHERE "industries"."id" IN (1)
2
=> nil
2.2.0 :010 > User.joins(:industries).where(industries: {id: [1]}).references(:industries).each{|user| ap user.avatars }
User Load (0.3ms) SELECT "users".* FROM "users" INNER JOIN "user_industries" ON "user_industries"."user_id" = "users"."id" INNER JOIN "industries" ON "industries"."id" = "user_industries"."industry_id" WHERE "industries"."id" IN (1)
Avatar Load (0.2ms) SELECT "avatars".* FROM "avatars" WHERE "avatars"."user_id" = ? ORDER BY "avatars"."sort_order" ASC, "avatars"."created_at" ASC [["user_id", 1]]
[
[0] #<Avatar:0x007ff03f8ab448> {
:id => 1,
:user_id => 1,
:sort_order => 0,
:created_at => Tue, 04 Oct 2016 07:05:36 UTC +00:00,
:updated_at => Tue, 04 Oct 2016 07:05:44 UTC +00:00
},
[1] #<Avatar:0x007ff03ec7e4e0> {
:id => 3,
:user_id => 1,
:sort_order => 1,
:created_at => Tue, 04 Oct 2016 07:05:40 UTC +00:00,
:updated_at => Tue, 04 Oct 2016 07:05:40 UTC +00:00
},
[2] #<Avatar:0x007ff03ec7e2d8> {
:id => 2,
:user_id => 1,
:sort_order => 2,
:created_at => Tue, 04 Oct 2016 07:05:38 UTC +00:00,
:updated_at => Tue, 04 Oct 2016 07:05:42 UTC +00:00
}
]
Avatar Load (0.2ms) SELECT "avatars".* FROM "avatars" WHERE "avatars"."user_id" = ? ORDER BY "avatars"."sort_order" ASC, "avatars"."created_at" ASC [["user_id", 2]]
[
[0] #<Avatar:0x007ff03f9121e8> {
:id => 4,
:user_id => 2,
:sort_order => 5,
:created_at => Tue, 04 Oct 2016 07:05:44 UTC +00:00,
:updated_at => Tue, 04 Oct 2016 07:05:48 UTC +00:00
},
[1] #<Avatar:0x007ff03f911fe0> {
:id => 6,
:user_id => 2,
:sort_order => 6,
:created_at => Tue, 04 Oct 2016 07:05:48 UTC +00:00,
:updated_at => Tue, 04 Oct 2016 07:05:48 UTC +00:00
},
[2] #<Avatar:0x007ff03f911dd8> {
:id => 5,
:user_id => 2,
:sort_order => 7,
:created_at => Tue, 04 Oct 2016 07:05:46 UTC +00:00,
:updated_at => Tue, 04 Oct 2016 07:05:48 UTC +00:00
}
]
=> [#<User id: 1, name: "John", created_at: "2016-10-04 07:05:40", updated_at: "2016-10-04 07:05:40">, #<User id: 2, name: "Jill", created_at: "2016-10-04 07:05:48", updated_at: "2016-10-04 07:05:48">]
2.2.0 :011 > User.joins(:industries).where(industries: {id: [1]}).references(:industries).includes(:avatars).each{|user| ap user.avatars }
User Load (0.3ms) SELECT "users".* FROM "users" INNER JOIN "user_industries" ON "user_industries"."user_id" = "users"."id" INNER JOIN "industries" ON "industries"."id" = "user_industries"."industry_id" WHERE "industries"."id" IN (1)
Avatar Load (0.2ms) SELECT "avatars".* FROM "avatars" WHERE "avatars"."user_id" IN (1, 2) ORDER BY "avatars"."sort_order" ASC, "avatars"."created_at" ASC
[
[0] #<Avatar:0x007ff03c7f0df8> {
:id => 1,
:user_id => 1,
:sort_order => 0,
:created_at => Tue, 04 Oct 2016 07:05:36 UTC +00:00,
:updated_at => Tue, 04 Oct 2016 07:05:44 UTC +00:00
},
[1] #<Avatar:0x007ff03c7f0bf0> {
:id => 3,
:user_id => 1,
:sort_order => 1,
:created_at => Tue, 04 Oct 2016 07:05:40 UTC +00:00,
:updated_at => Tue, 04 Oct 2016 07:05:40 UTC +00:00
},
[2] #<Avatar:0x007ff03c7f09c0> {
:id => 2,
:user_id => 1,
:sort_order => 2,
:created_at => Tue, 04 Oct 2016 07:05:38 UTC +00:00,
:updated_at => Tue, 04 Oct 2016 07:05:42 UTC +00:00
}
]
[
[0] #<Avatar:0x007ff03c7f07b8> {
:id => 4,
:user_id => 2,
:sort_order => 5,
:created_at => Tue, 04 Oct 2016 07:05:44 UTC +00:00,
:updated_at => Tue, 04 Oct 2016 07:05:48 UTC +00:00
},
[1] #<Avatar:0x007ff03c7f0588> {
:id => 6,
:user_id => 2,
:sort_order => 6,
:created_at => Tue, 04 Oct 2016 07:05:48 UTC +00:00,
:updated_at => Tue, 04 Oct 2016 07:05:48 UTC +00:00
},
[2] #<Avatar:0x007ff03c7f0380> {
:id => 5,
:user_id => 2,
:sort_order => 7,
:created_at => Tue, 04 Oct 2016 07:05:46 UTC +00:00,
:updated_at => Tue, 04 Oct 2016 07:05:48 UTC +00:00
}
]
=> [#<User id: 1, name: "John", created_at: "2016-10-04 07:05:40", updated_at: "2016-10-04 07:05:40">, #<User id: 2, name: "Jill", created_at: "2016-10-04 07:05:48", updated_at: "2016-10-04 07:05:48">]
基本上,我们有2个急切的加载函数:preload和eager_load。当您使用includes时,它会调用preload或eager_load。预加载导致2个查询(查找用户和查找检索到的用户的头像)wheres eager_load仅使用1个查询(连接查询)。因此,当包含结果导致连接查询(即,导致eager_load)时,由于单个查询,将跳过要检索的关联的顺序。
User.includes(:avatars, :industries).where(industries: {id: [1]}).references(:industries)
导致联接导致您根据特定行业过滤用户,这些行业本身就是一个“通过”关联。 'through'使用连接。另外,请记住'join'会导致INNER JOIN,而eager_load会使用LEFT OUTER JOIN。
2.2.0 :050 > User.joins(:industries).where(industries: {id: [1]}).references(:industries)
User Load (0.2ms) SELECT "users".* FROM "users" INNER JOIN "user_industries" ON "user_industries"."user_id" = "users"."id" INNER JOIN "industries" ON "industries"."id" = "user_industries"."industry_id" WHERE "industries"."id" IN (1)
=> #<ActiveRecord::Relation [#<User id: 1, name: "John", created_at: "2016-10-04 07:05:40", updated_at: "2016-10-04 07:05:40">, #<User id: 2, name: "Jill", created_at: "2016-10-04 07:05:48", updated_at: "2016-10-04
2.2.0 :054 > User.includes(:industries).where(industries: {id: [1]}).references(:industries)
SQL (0.3ms) SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, "users"."created_at" AS t0_r2, "users"."updated_at" AS t0_r3, "industries"."id" AS t1_r0, "industries"."name" AS t1_r1, "industries"."created_at" AS t1_r2, "industries"."updated_at" AS t1_r3 FROM "users" LEFT OUTER JOIN "user_industries" ON "user_industries"."user_id" = "users"."id" LEFT OUTER JOIN "industries" ON "industries"."id" = "user_industries"."industry_id" WHERE "industries"."id" IN (1)
=> #<ActiveRecord::Relation [#<User id: 1, name: "John", created_at: "2016-10-04 07:05:40", updated_at: "2016-10-04 07:05:40">, #<User id: 2, name: "Jill", created_at: "2016-10-04 07:05:48", updated_at: "2016-10-04 07:05:48">]>
2.2.0 :057 > User.eager_load(:industries).where(industries: {id: [1]}).references(:industries)
SQL (0.3ms) SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, "users"."created_at" AS t0_r2, "users"."updated_at" AS t0_r3, "industries"."id" AS t1_r0, "industries"."name" AS t1_r1, "industries"."created_at" AS t1_r2, "industries"."updated_at" AS t1_r3 FROM "users" LEFT OUTER JOIN "user_industries" ON "user_industries"."user_id" = "users"."id" LEFT OUTER JOIN "industries" ON "industries"."id" = "user_industries"."industry_id" WHERE "industries"."id" IN (1)
=> #<ActiveRecord::Relation [#<User id: 1, name: "John", created_at: "2016-10-04 07:05:40", updated_at: "2016-10-04 07:05:40">, #<User id: 2, name: "Jill", created_at: "2016-10-04 07:05:48", updated_at: "2016-10-04 07:05:48">]>
您可以参考http://blog.arkency.com/2013/12/rails4-preloading/了解更多示例并提供解释。我还没有找到为什么连接的类型不同。无论如何,我希望这会有所帮助。我将尝试为更高版本的rails重现相同的内容。
答案 2 :(得分:1)
因为那些事情是不同的
# returns a user which has smallest id (@users are ordered by id)
@users.first # User::ActiveRecord_Relation#first
# is different to
# returns a first user which fetched from database (@users are unordered)
@users.to_a.first # Array#first
答案 3 :(得分:1)
恕我直言,当你预加载时,avatars
关联关系范围内的订单信息无法使用(因为&#34;对象&#34;要返回User
,而不是{ {1}})。从SQL日志中,我们可以看到它只是在Avatar
字段上没有订单的情况下加入查询。
如果您不使用avatars
,则返回的数组按升序排列,恰好是正确的顺序。
答案 4 :(得分:1)
在2次赏金之后,我还没有得到令人满意的答案,所以我得出结论只是一个Rails错误,我在Github的Rails回购中创建了an issue。
我在这里发布了一个解决方法,以便其他人可以通过一些复制粘贴来解决他们的问题。
# in user.rb
has_many :avatars, -> { order([:sort_order => :asc,:created_at => :asc])}, dependent: :destroy
def sorted_avatars
# sometimes the avatars scope is ignored when eagerly loaded with includes
avatars.to_a.sort do |a1,a2|
comp = (a1.sort_order <=> a2.sort_order)
comp.zero? ? (a1.created_at <=> a2.created_at) : comp
end
end
我将把这第二笔赏金授予亚历山大,仅仅是因为他对改变这个问题的标题的评论对我来说比当前得票最高的答案更有价值。
答案 5 :(得分:0)
问题源于在查询中包含头像。这会以自然顺序(id)取出头像,并且:avatars
范围不会被调用,因为响应中的每个用户都已经绑定了#avatars
。
我担心你在构建查询时必须强化头像顺序:
relation = User.includes(:industries,:avatars)
.where(industries: {id: [1]}).references(:industries)
.order("users.id asc, avatars.sort_order asc, avatars.created_at asc")
avatars = relation.first.avatars
要测试对用户订单的副作用,请使用您的初始测试用例并在同一行业中添加其他用户,然后添加一个sort_order:-1
的头像。
relation.pluck(:id)
将返回[1,1,1,2]
,因此会保留用户订单。
答案 6 :(得分:0)
两种情况之间的主要区别是使用lazy loading
的{{1}}数据。
案例1
includes
在这种情况下,sql查询命中是
u = User.first
u.avatars.pluck(:id)
=> [1, 3, 2]
因为你没有在这里延迟加载SELECT "avatars"."id" FROM "avatars" WHERE "avatars"."user_id" = ? ORDER BY "avatars"."sort_order" ASC, "avatars"."created_at" ASC [["user_id", 1]]
数据,当你尝试获取头像数据时,它会命中数据库并使用你提到模型的排序顺序。
如您所见,此处avatar
就是您在范围内提到的那个。
案例2
order by
在这种情况下,sql查询命中是
User.includes(:avatars, :industries).where(industries: {id: [1]}).references(:industries).first.avatars.map(&:id)
=> [1, 2, 3]
正如您在上面的查询中使用SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, "users"."created_at" AS t0_r2, "users"."updated_at" AS t0_r3, "avatars"."id" AS t1_r0, "avatars"."user_id" AS t1_r1, "avatars"."sort_order" AS t1_r2, "avatars"."created_at" AS t1_r3, "avatars"."updated_at" AS t1_r4, "industries"."id" AS t2_r0, "industries"."name" AS t2_r1, "industries"."created_at" AS t2_r2, "industries"."updated_at" AS t2_r3 FROM "users" LEFT OUTER JOIN "avatars" ON "avatars"."user_id" = "users"."id" LEFT OUTER JOIN "user_industries" ON "user_industries"."user_id" = "users"."id" LEFT OUTER JOIN "industries" ON "industries"."id" = "user_industries"."industry_id" WHERE "industries"."id" IN (1) AND "users"."id" IN (1) ORDER BY "users"."id" ASC
一样,它是懒惰加载头像和行业。但是,在延迟加载数据时,它没有使用您在头像模型中提到的排序顺序,因此MySql默认为includes
作为排序顺序,给出(1,2,3)
在这种情况下,由于头像数据已经延迟加载,当您运行id
时,它不会再次访问数据库以获取first.avatars
数据并只是从延迟加载打印数据值为avatar