左侧外部joinRails 4

我有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 

我希望查询课程表中不存在的与某个学生关联的StudentEnrollments表中的课程列表。

我发现也许左连接是要走的路,但似乎rails中的joins()只接受一个表作为参数。 我认为会执行我想要的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 

我如何执行这个查询Rails 4的方式?

任何input赞赏。

你也可以传递一个join-sql的string。 例如joins("LEFT JOIN StudentEnrollment se ON c.id = se.course_id")

虽然我会使用rails-标准表格命名来清晰起见:

 joins("LEFT JOIN student_enrollments ON courses.id = student_enrollments.course_id") 

实际上有一个“Rails方式”来做到这一点。

您可以使用Arel ,这是Rails用来为ActiveRecrods构build查询的

我会用方法来包装它,这样你就可以很好地调用它,并通过你想要的任何参数,例如:

 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 

也有许多人使用的快速(和稍微脏)的方式

 Course.eager_load(:students).where( student_enrollments: {student_id: some_user.id, id: nil}, active: true ) 

eager_load的效果很好,它只是在你可能不需要的内存模型的“副作用”(就像你的情况)
请参阅Rails ActiveRecord :: QueryMethods .eager_load
它完全正是你所要求的一个整洁的方式。

您将执行该查询为:

 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中的主动模型连接查询。

请点击这里获取关于活动模型查询格式的更多信息 。

 @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 

我一直在努力解决这个问题,并决定一劳永逸地解决这个问题。 我发表了一个解决这个问题的Gist: https : //gist.github.com/nerde/b867cd87d580e97549f2

我创build了一个使用Arel Table的AR hack,为您dynamic构build左连接,而无需在代码中编写原始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 

希望能帮助到你。

使用Squeel :

 Person.joins{articles.inner} Person.joins{articles.outer} 

合并includeswhere导致ActiveRecord在幕后执行LEFT OUTER JOIN(没有在哪里产生两个正常的查询集合)。

所以你可以做这样的事情:

 Course.includes(:student_enrollments).where(student_enrollments: { course_id: nil }) 

Google文档: http : //guides.rubyonrails.org/active_record_querying.html#specifying-conditions-on-eager-loaded-associations

如果您希望在没有所有额外加载的ActiveRecord对象的情况下使用OUTER JOIN,则在保留OUTER JOIN的同时,在.eager_load()之后使用.pluck(:id) .eager_load()来中止.eager_load()加载。 使用.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) 

如果有人来这里寻找一个通用的方法来在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