ske*_*ell 12 ruby activerecord ruby-on-rails
TL; DR:这个问题在https://github.com/skensell/SO-question-example有自己的示例应用程序,您可以使用它自行调试.我已经在这个问题上提了一次赏金,但我不相信(或者我不明白)最重要的回答者的推理.我将再次给予此奖励,因为这让我感到非常沮丧.
原始问题
我有一个User
像这样的关联的模型:
has_many :avatars, -> { order([:sort_order => :asc,:created_at => :asc])}
Run Code Online (Sandbox Code Playgroud)
我有一个端点,它执行搜索用户并设置@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.
Run Code Online (Sandbox Code Playgroud)
这里发生了什么?
唯一的区别是to_a
.我甚至试图忽略它to_a
,但我认为它被jbuilder隐含地调用,因为我将它设置为json数组.
也许我正在搜索的方式User
与它有关?我正在使用几个包含和连接.
UPDATE
在这里,我可以向您展示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.
Run Code Online (Sandbox Code Playgroud)
我可以验证这些查询指的是相同的用户,并标有正确顺序的一个真的是正确的WRT 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]
>>
Run Code Online (Sandbox Code Playgroud)
在这里,我将附上一个日志,显示有问题的用户的头像(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]]
Run Code Online (Sandbox Code Playgroud)
另外,我使用的是Rails 4.1.4和Ruby 2.1.10.
更新3
我在这里创建了一个示例应用程序:https://github.com/skensell/SO-question-example.在这个示例应用程序中甚至更奇怪的是,这to_a
甚至都不重要.即使只是,我的订单也是错误的includes... references
.
@users.first.avatars[0..2].map(&:id)
# => [2546, 2547, 2548]
@users.to_a.first.avatars[0..2].map(&:id)
# => [2548, 2546, 2547]
Run Code Online (Sandbox Code Playgroud)
- 这里发生了什么?
没有任何错误.
第一种方法查找按主键排序的第一条记录(默认)
SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1
Run Code Online (Sandbox Code Playgroud)
以及作为集合顺序返回的下一个方法@users.to_a
updated_at
希望这对你有所帮助!
包含考虑仅在产生联接查询时才会检索其记录的父表的顺序。即在上述情况下,当包含查询导致连接时,将跳过头像顺序,并使用用户顺序。您可以向用户添加默认范围并确认。
如果您仍然希望 user.avatars 按照定义的头像顺序进行排序,则需要将 include 替换为 join。请注意,使用 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">]
Run Code Online (Sandbox Code Playgroud)
基本上,我们有 2 个急切加载函数:preload 和 eager_load。当您使用 include 时,它会调用 preload 或 eager_load。预加载结果为 2 个查询(查找用户并查找检索到的用户的头像),其中 eager_load 仅使用 1 个查询(连接查询)。因此,当在连接查询中包含结果(即 eager_load 中的结果)时,将跳过要检索的关联的顺序,因为它是单个查询。
User.includes(:avatars, :industries).where(industries: {id: [1]}).references(:industries)
Run Code Online (Sandbox Code Playgroud)
导致加入的原因是您根据特定行业过滤用户,这本身就是一个“通过”关联。“through”使用连接。另外,请记住“joins”会产生 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">]>
Run Code Online (Sandbox Code Playgroud)
您可以参考http://blog.arkency.com/2013/12/rails4-preloading/获取更多示例和解释。我还没有找到为什么连接类型不同。无论如何,我希望这会有所帮助。我将尝试在 Rails 的更高版本中重现相同的内容。