Kha*_*tor 76 sql ruby-on-rails mysql2 ruby-on-rails-4 rails-activerecord
我有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
Run Code Online (Sandbox Code Playgroud)
我希望查询Courses表中的课程列表,这些课程在StudentEnrollments表中与特定学生相关联.
我发现也许Left Join是要走的路,但似乎rails中的join()只接受一个表作为参数.我认为可以做我想要的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
Run Code Online (Sandbox Code Playgroud)
如何以Rails 4方式执行此查询?
任何输入都表示赞赏.
Tar*_*ast 82
您也可以传递一个join-sql字符串.例如joins("LEFT JOIN StudentEnrollment se ON c.id = se.course_id")
虽然为了清晰起见我使用rails标准表命名:
joins("LEFT JOIN student_enrollments ON courses.id = student_enrollments.course_id")
Run Code Online (Sandbox Code Playgroud)
Bla*_*icz 28
如果有人来到这里寻找在Rails 5中执行左外连接的通用方法,则可以使用该#left_outer_joins
功能.
多连接示例:
红宝石:
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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
sup*_*roi 21
实际上有一个"Rails方式"来做到这一点.
你可以使用Arel,这是Rails用来构建ActiveRecrods查询的东西
我会将它包装在方法中,以便您可以很好地调用它并传入您想要的任何参数,例如:
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
Run Code Online (Sandbox Code Playgroud)
许多人也使用快速(略微肮脏)的方式
Course.eager_load(:students).where(
student_enrollments: {student_id: some_user.id, id: nil},
active: true
)
Run Code Online (Sandbox Code Playgroud)
eager_load工作得很好,它只是具有你可能不需要的内存中的模型的"副作用"(就像你的情况一样)
请参阅Rails ActiveRecord :: QueryMethods .eager_load
它完全按照你要求的方式完成.
mac*_*atz 11
组合includes
并where
导致ActiveRecord在幕后执行LEFT OUTER JOIN(没有这将生成两个查询的正常集合).
所以你可以这样做:
Course.includes(:student_enrollments).where(student_enrollments: { course_id: nil })
Run Code Online (Sandbox Code Playgroud)
文档:http://guides.rubyonrails.org/active_record_querying.html#specifying-conditions-on-eager-loaded-associations
Jon*_*ner 11
添加到上面的答案,使用includes
,如果你想要OUTER JOIN而不引用where中的表(比如id为nil)或者引用是在你可以使用的字符串中references
.这看起来像这样:
Course.includes(:student_enrollments).references(:student_enrollments)
Run Code Online (Sandbox Code Playgroud)
要么
Course.includes(:student_enrollments).references(:student_enrollments).where('student_enrollments.id = ?', nil)
Run Code Online (Sandbox Code Playgroud)
http://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-references
您将执行以下查询:
Course.joins('LEFT JOIN student_enrollment on courses.id = student_enrollment.course_id')
.where(active: true, student_enrollments: { student_id: SOME_VALUE, id: nil })
Run Code Online (Sandbox Code Playgroud)
小智 6
我知道这是一个老问题,一个老线程,但是在Rails 5中,您可以简单地执行
Course.left_outer_joins(:student_enrollments)
Run Code Online (Sandbox Code Playgroud)
我已经在这个问题上苦苦挣扎了很长一段时间,并决定做一些事情来一劳永逸地解决它。我发布了解决此问题的要点:https://gist.github.com/nerde/b867cd87d580e97549f2
我创建了一个小小的 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
Run Code Online (Sandbox Code Playgroud)
希望能帮助到你。
请参阅下面我对这个问题的原始帖子。
从那时起,我.left_joins()
为 ActiveRecord v4.0.x 实现了自己的应用程序(抱歉,我的应用程序在此版本中冻结,因此我无需将其移植到其他版本):
在文件中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)
Run Code Online (Sandbox Code Playgroud)
现在我可以在.left_joins()
我通常使用的任何地方使用.joins()
.
----------------- 原帖如下 -----------------
如果您想要 OUTER JOIN 而不需要所有额外的急切加载的 ActiveRecord 对象,请使用.pluck(:id)
after.eager_load()
中止急切加载,同时保留 OUTER JOIN。使用.pluck(:id)
会阻碍急切加载,因为列名别名(items.location AS t1_r9
例如 )在使用时会从生成的查询中消失(这些独立命名的字段用于实例化所有急切加载的 ActiveRecord 对象)。
此方法的缺点是您需要运行第二个查询来提取第一个查询中标识的所需 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)
Run Code Online (Sandbox Code Playgroud)
您可以使用left_joins gem,它left_joins
从Rails 5向Rails 4和3 反向移植方法。
Course.left_joins(:student_enrollments)
.where('student_enrollments.id' => nil)
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
83832 次 |
最近记录: |