[英]Rails 5 left outer join using includes() and where()
I'm having a heck of a time getting the intended behavior using includes() and where().我有很多时间使用includes() 和where() 来获得预期的行为。
Result I want:我想要的结果:
- All students (even if they have zero check-ins) - 所有学生(即使他们零签到)
- All check-ins in the Library - 图书馆中的所有签到
Result I'm getting:结果我得到:
- Only students with check-ins in the library - 只有在图书馆签到的学生
- All check-ins in the library, for those students - 所有在图书馆签到的学生,对于那些学生
Currently my code is based off of this: http://edgeguides.rubyonrails.org/active_record_querying.html#specifying-conditions-on-eager-loaded-associations目前我的代码基于此: http : //edgeguides.rubyonrails.org/active_record_querying.html#specifying-conditions-on-eager-loaded-associations
Which describes the behavior I want:它描述了我想要的行为:
Article.includes(:comments).where(comments: { visible: true })
If, in the case of this includes query, there were no comments for any articles, all the articles would still be loaded.如果在这种包含查询的情况下,没有任何文章的评论,则仍会加载所有文章。
My code:我的代码:
@students = Student.includes(:check_ins)
.where(check_ins: {location: "Library"})
.references(:check_ins)
. .
class CheckIn < ApplicationRecord
belongs_to :student
end
. .
class Student < ApplicationRecord
has_many :check_ins, dependent: :destroy
end
The generated SQL query:生成的 SQL 查询:
SELECT "students"."id" AS t0_r0,"check_ins"."id" AS t1_r0, "check_ins"."location" AS t1_r1, "check_ins"."student_id" AS t1_r6 FROM "students" LEFT OUTER JOIN "check_ins" ON "check_ins"."student_id" = "students"."id" WHERE "check_ins"."location" IN ('Library')
This SQL query gives the join behavior I want:这个 SQL 查询给出了我想要的连接行为:
SELECT first_name, C.id FROM students S LEFT OUTER JOIN check_ins C ON C.student_id = S.id AND location IN ('Library');
Tried a new approach using Scopes with relations, expecting to preload everything and filter it out, but was pleasantly surprised that Scopes actually give me the exact behavior I want (right down to the eager loading).尝试使用带有关系的 Scopes 的新方法,期望预加载所有内容并将其过滤掉,但惊喜的是 Scopes 实际上给了我我想要的确切行为(一直到急切加载)。
Here's the result:结果如下:
This ActiveRecord Call pulls in the full list of students and eager loads the check-ins:此 ActiveRecord 调用会拉入完整的学生列表并急切地加载签到:
@students = Student.all.includes(:check_ins)
The scope of check_ins can be limited right in the has_many declaration: check_ins 的范围可以在 has_many 声明中进行限制:
Class Student < ApplicationRecord
has_many :check_ins, -> {where('location = 'Library'}, dependent: :destroy
end
Resulting in two clean, efficient queries:产生了两个干净、高效的查询:
Student Load (0.7ms) SELECT "students".* FROM "students"
CheckIn Load (1.2ms) SELECT "check_ins".* FROM "check_ins" WHERE location = 'Library') AND "check_ins"."student_id" IN (6, 7, 5, 3, 1, 8, 9, 4, 2)
Bingo!答对了!
ps you can read more about using scopes with assocations here: ps,您可以在此处阅读有关将作用域与关联一起使用的更多信息:
http://ducktypelabs.com/using-scope-with-associations/ http://ducktypelabs.com/using-scope-with-associations/
What you want in terms of pure SQL is:就纯 SQL 而言,您想要的是:
LEFT OUTER JOIN "check_ins" ON "check_ins"."student_id" = "students"."id"
AND location IN ('Library')
However it is not possible (afaik) to get ActiveRecord to mark the association as loaded without trickery* .但是,不可能(afaik)让 ActiveRecord 将关联标记为已加载而无需技巧* 。
class Student < ApplicationRecord
has_many :check_ins
def self.joins_check_ins
joins( <<~SQL
LEFT OUTER JOIN "check_ins" ON "check_ins"."student_id" = "students"."id"
AND location IN ('Library')
SQL
)
end
end
So if we iterate though the result it will cause a N+1 query issue:因此,如果我们迭代结果,它将导致 N+1 查询问题:
irb(main):041:0> Student.joins_check_ins.map {|s| s.check_ins.loaded? }
Student Load (1.0ms) SELECT "students".* FROM "students" LEFT OUTER JOIN "check_ins" ON "check_ins"."student_id" = "students"."id"
AND location IN ('Library')
=> [false, false, false]
irb(main):042:0> Student.joins_check_ins.map {|s| s.check_ins.size }
Student Load (2.3ms) SELECT "students".* FROM "students" LEFT OUTER JOIN "check_ins" ON "check_ins"."student_id" = "students"."id"
AND location IN ('Library')
(1.2ms) SELECT COUNT(*) FROM "check_ins" WHERE "check_ins"."student_id" = $1 [["student_id", 1]]
(0.7ms) SELECT COUNT(*) FROM "check_ins" WHERE "check_ins"."student_id" = $1 [["student_id", 2]]
(0.6ms) SELECT COUNT(*) FROM "check_ins" WHERE "check_ins"."student_id" = $1 [["student_id", 3]]
To be honest, I never like preloading only a subset of association because some parts of your application probably assume that it is fully loaded.老实说,我从不喜欢只预加载关联的一个子集,因为您的应用程序的某些部分可能会假设它已完全加载。 It might only make sense if you are getting the data to display it.只有在获取数据以显示它时才有意义。
- Robert Pankowecki, 3 ways to do eager loading (preloading) in Rails 3 & 4 - Robert Pankowecki,在 Rails 3 和 4 中进行预加载(预加载)的 3 种方法
So in this case you should consider preloading all the data and using something like a subquery to select the count of check_ins
.因此,在这种情况下,您应该考虑预加载所有数据并使用子查询之类的内容来选择check_ins
的计数。
I would also advise you to create a separate table for locations.我还建议您为位置创建一个单独的表。
I think this is the only way to create the query you want.我认为这是创建所需查询的唯一方法。
Student.joins("LEFT OUTER JOIN check_ins ON check_ins.student_id = students.id AND check_ins.location = 'Library'")
Reference : http://apidock.com/rails/ActiveRecord/QueryMethods/joins参考: http : //apidock.com/rails/ActiveRecord/QueryMethods/joins
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.