[英]LEFT OUTER JOIN in Rails 4
I have 3 models:我有 3 个模型:
class Student < ActiveRecord::Base
has_many :student_enrollments, dependent: :destroy
has_many :courses, through: :student_enrollments
end
class Course < ActiveRecord::Base
has_many :student_enrollments, dependent: :destroy
has_many :students, through: :student_enrollments
end
class StudentEnrollment < ActiveRecord::Base
belongs_to :student
belongs_to :course
end
I wish to query for a list of courses in the Courses table, that do not exist in the StudentEnrollments table that are associated with a certain student.我希望在 Courses 表中查询与某个学生关联的 StudentEnrollments 表中不存在的课程列表。
I found that perhaps Left Join is the way to go, but it seems that joins() in rails only accept a table as argument.我发现也许 Left Join 是要走的路,但似乎 rails 中的 joins() 只接受一个表作为参数。 The SQL query that I think would do what I want is:我认为会做我想做的 SQL 查询是:
SELECT *
FROM Courses c LEFT JOIN StudentEnrollment se ON c.id = se.course_id
WHERE se.id IS NULL AND se.student_id = <SOME_STUDENT_ID_VALUE> and c.active = true
How do I execute this query the Rails 4 way?如何以 Rails 4 方式执行此查询?
Any input is appreciated.任何输入表示赞赏。
You can pass a string that is the join-sql too.您也可以传递一个作为 join-sql 的字符串。 eg joins("LEFT JOIN StudentEnrollment se ON c.id = se.course_id")
例如joins("LEFT JOIN StudentEnrollment se ON c.id = se.course_id")
Though I'd use rails-standard table naming for clarity:虽然为了清楚起见,我会使用 rails-standard 表命名:
joins("LEFT JOIN student_enrollments ON courses.id = student_enrollments.course_id")
If anyone came here looking for a generic way to do a left outer join in Rails 5, you can use the #left_outer_joins
function.如果有人来这里寻找在 Rails 5 中进行左外连接的通用方法,您可以使用#left_outer_joins
函数。
Multi-join example:多连接示例:
Ruby:红宝石:
Source.
select('sources.id', 'count(metrics.id)').
left_outer_joins(:metrics).
joins(:port).
where('ports.auto_delete = ?', true).
group('sources.id').
having('count(metrics.id) = 0').
all
SQL: SQL:
SELECT sources.id, count(metrics.id)
FROM "sources"
INNER JOIN "ports" ON "ports"."id" = "sources"."port_id"
LEFT OUTER JOIN "metrics" ON "metrics"."source_id" = "sources"."id"
WHERE (ports.auto_delete = 't')
GROUP BY sources.id
HAVING (count(metrics.id) = 0)
ORDER BY "sources"."id" ASC
There is actually a "Rails Way" to do this.实际上有一个“Rails Way”可以做到这一点。
You could use Arel , which is what Rails uses to construct queries for ActiveRecrods您可以使用Arel ,这是 Rails 用来构造 ActiveRecrods 查询的工具
I would wrap it in method so that you can call it nicely and pass in whatever argument you would like, something like:我会将它包装在方法中,以便您可以很好地调用它并传入您想要的任何参数,例如:
class Course < ActiveRecord::Base
....
def left_join_student_enrollments(some_user)
courses = Course.arel_table
student_entrollments = StudentEnrollment.arel_table
enrollments = courses.join(student_enrollments, Arel::Nodes::OuterJoin).
on(courses[:id].eq(student_enrollments[:course_id])).
join_sources
joins(enrollments).where(
student_enrollments: {student_id: some_user.id, id: nil},
active: true
)
end
....
end
There is also the quick (and slightly dirty) way that many use还有许多人使用的快速(而且有点脏)的方式
Course.eager_load(:students).where(
student_enrollments: {student_id: some_user.id, id: nil},
active: true
)
eager_load works great, it just has the "side effect" of loding models in memory that you might not need (like in your case) eager_load 效果很好,它只是在你可能不需要的内存中加载模型的“副作用”(就像你的情况一样)
Please see Rails ActiveRecord::QueryMethods .eager_load请参阅 Rails ActiveRecord::QueryMethods .eager_load
It does exactly what you are asking in a neat way.它以一种简洁的方式完全满足您的要求。
Combining includes
and where
results in ActiveRecord performing a LEFT OUTER JOIN behind the scenes (without the where this would generate the normal set of two queries).结合includes
和where
会导致 ActiveRecord 在幕后执行 LEFT OUTER JOIN(没有 where 这将生成正常的两个查询集)。
So you could do something like:因此,您可以执行以下操作:
Course.includes(:student_enrollments).where(student_enrollments: { course_id: nil })
Docs here: http://guides.rubyonrails.org/active_record_querying.html#specifying-conditions-on-eager-loaded-associations此处的文档:http: //guides.rubyonrails.org/active_record_querying.html#specifying-conditions-on-eager-loaded-associations
Adding to the answer above, to use includes
, if you want an OUTER JOIN without referencing the table in the where (like id being nil) or the reference is in a string you can use references
.添加到上面的答案,使用includes
,如果你想要一个 OUTER JOIN 而不引用 where 中的表(如 id 为 nil)或引用在一个字符串中,你可以使用references
。 That would look like this:看起来像这样:
Course.includes(:student_enrollments).references(:student_enrollments)
or或者
Course.includes(:student_enrollments).references(:student_enrollments).where('student_enrollments.id = ?', nil)
http://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-references http://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-references
You'd execute the query as:您将执行查询为:
Course.joins('LEFT JOIN student_enrollment on courses.id = student_enrollment.course_id')
.where(active: true, student_enrollments: { student_id: SOME_VALUE, id: nil })
我知道这是一个老问题和一个老线程,但在 Rails 5 中,你可以简单地做
Course.left_outer_joins(:student_enrollments)
You could use left_joins gem, which backports left_joins
method from Rails 5 for Rails 4 and 3.您可以使用left_joins gem,它将 Rails 5 中的left_joins
方法反向移植到 Rails 4 和 3。
Course.left_joins(:student_enrollments)
.where('student_enrollments.id' => nil)
I've been struggling with this kind of problem for quite some while, and decided to do something to solve it once and for all.我已经为这种问题苦苦挣扎了很长一段时间,并决定做一些事情来一劳永逸地解决它。 I published a Gist that addresses this issue: https://gist.github.com/nerde/b867cd87d580e97549f2我发表了一个解决这个问题的要点: https ://gist.github.com/nerde/b867cd87d580e97549f2
I created a little AR hack that uses Arel Table to dynamically build the left joins for you, without having to write raw SQL in your code:我创建了一个小 AR hack,它使用 Arel Table 为您动态构建左连接,而无需在您的代码中编写原始 SQL:
class ActiveRecord::Base
# Does a left join through an association. Usage:
#
# Book.left_join(:category)
# # SELECT "books".* FROM "books"
# # LEFT OUTER JOIN "categories"
# # ON "books"."category_id" = "categories"."id"
#
# It also works through association's associations, like `joins` does:
#
# Book.left_join(category: :master_category)
def self.left_join(*columns)
_do_left_join columns.compact.flatten
end
private
def self._do_left_join(column, this = self) # :nodoc:
collection = self
if column.is_a? Array
column.each do |col|
collection = collection._do_left_join(col, this)
end
elsif column.is_a? Hash
column.each do |key, value|
assoc = this.reflect_on_association(key)
raise "#{this} has no association: #{key}." unless assoc
collection = collection._left_join(assoc)
collection = collection._do_left_join value, assoc.klass
end
else
assoc = this.reflect_on_association(column)
raise "#{this} has no association: #{column}." unless assoc
collection = collection._left_join(assoc)
end
collection
end
def self._left_join(assoc) # :nodoc:
source = assoc.active_record.arel_table
pk = assoc.association_primary_key.to_sym
joins source.join(assoc.klass.arel_table,
Arel::Nodes::OuterJoin).on(source[assoc.foreign_key].eq(
assoc.klass.arel_table[pk])).join_sources
end
end
Hope it helps.希望能帮助到你。
See below my original post to this question.请参阅下面我对这个问题的原始帖子。
Since then, I have implemented my own .left_joins()
for ActiveRecord v4.0.x (sorry, my app is frozen at this version so I've had no need to port it to other versions):从那时起,我为 ActiveRecord v4.0.x 实现了我自己的.left_joins()
(对不起,我的应用程序在这个版本中被冻结,所以我不需要将它移植到其他版本):
In file app/models/concerns/active_record_extensions.rb
, put the following:在文件app/models/concerns/active_record_extensions.rb
中,输入以下内容:
module ActiveRecordBaseExtensions
extend ActiveSupport::Concern
def left_joins(*args)
self.class.left_joins(args)
end
module ClassMethods
def left_joins(*args)
all.left_joins(args)
end
end
end
module ActiveRecordRelationExtensions
extend ActiveSupport::Concern
# a #left_joins implementation for Rails 4.0 (WARNING: this uses Rails 4.0 internals
# and so probably only works for Rails 4.0; it'll probably need to be modified if
# upgrading to a new Rails version, and will be obsolete in Rails 5 since it has its
# own #left_joins implementation)
def left_joins(*args)
eager_load(args).construct_relation_for_association_calculations
end
end
ActiveRecord::Base.send(:include, ActiveRecordBaseExtensions)
ActiveRecord::Relation.send(:include, ActiveRecordRelationExtensions)
Now I can use .left_joins()
everywhere I'd normally use .joins()
.现在我可以在通常使用 .joins .left_joins()
的任何地方使用.joins()
。
----------------- ORIGINAL POST BELOW ----------------- ----------------- 下面是原帖 -----------------
If you want OUTER JOINs without all the extra eagerly loaded ActiveRecord objects, use .pluck(:id)
after .eager_load()
to abort the eager load while preserving the OUTER JOIN.如果您希望 OUTER JOIN 没有所有额外急切加载的 ActiveRecord 对象,请在.eager_load()
) 之后使用.pluck(:id)
中止急切加载,同时保留 OUTER JOIN。 Using .pluck(:id)
thwarts eager loading because the column name aliases ( items.location AS t1_r9
, for example) disappear from the generated query when used (these independently named fields are used to instantiate all the eagerly loaded ActiveRecord objects).使用.pluck(:id)
阻止预加载,因为列名别名(例如items.location AS t1_r9
)在使用时会从生成的查询中消失(这些独立命名的字段用于实例化所有预加载的 ActiveRecord 对象)。
A disadvantage of this approach is that you then need to run a second query to pull in the desired ActiveRecord objects identified in the first query:这种方法的一个缺点是您需要运行第二个查询来提取在第一个查询中标识的所需 ActiveRecord 对象:
# first query
idents = Course
.eager_load(:students) # eager load for OUTER JOIN
.where(
student_enrollments: {student_id: some_user.id, id: nil},
active: true
)
.distinct
.pluck(:id) # abort eager loading but preserve OUTER JOIN
# second query
Course.where(id: idents)
It'a join query in Active Model in Rails.这是 Rails 中 Active Model 中的连接查询。
Please click here for More info about Active Model Query Format .请单击此处了解有关活动模型查询格式的更多信息。
@course= Course.joins("LEFT OUTER JOIN StudentEnrollment
ON StudentEnrollment .id = Courses.user_id").
where("StudentEnrollment .id IS NULL AND StudentEnrollment .student_id =
<SOME_STUDENT_ID_VALUE> and Courses.active = true").select
If anyone out there still needs true left_outer_joins support in Rails 4.2 then if you install the gem "brick" on Rails 4.2.0 or later it automatically adds the Rails 5.0 implementation of left_outer_joins
.如果有人在 Rails 4.2 中仍然需要真正的 left_outer_joins 支持,那么如果您在 Rails 4.2.0 或更高版本上安装gem “brick” ,它会自动添加 Rails 5.0 的left_outer_joins
实现。 You would probably want to turn off the rest of its functionality, that is unless you want an automatic "admin panel" kind of thing available in your app!您可能想要关闭它的其余功能,除非您想要在您的应用程序中使用自动“管理面板”之类的东西!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.