如何在Ruby on Rails中使用Joins结合左外连接查询?
解决方案:统计学校学生书籍总页数(含无书籍学生)
数据库关联结构
School has_many :students Student has_many :books Book # 包含pages整数属性
需求与问题
需要统计学校层面所有学生书籍的总页数,必须包含无书籍的学生,且要通过Ruby代码实现等价于以下SQL的查询逻辑:
SELECT * FROM school INNER JOIN students ON students.school_id = schools.id LEFT OUTER JOIN books ON books.student_id = students.id
此前使用School.joins({:students => :books}).all无法达成需求,原因是joins默认生成内连接,会自动过滤掉没有关联书籍的学生记录。
实现方案
1. 生成目标等价SQL的查询
要实现目标中的内连接+左外连接逻辑,在Rails 5及以上版本可以用left_joins(别名left_outer_joins)来指定左外连接:
# 与目标SQL逻辑完全一致的查询 School.joins(:students).left_joins(students: :books).all
joins(:students):建立学校与学生的内连接,确保只返回有学生的学校(和目标SQL一致)left_joins(students: :books):建立学生与书籍的左外连接,保留所有学生,包括无书籍的
2. 直接统计学校总页数
如果不需要加载所有记录,而是直接统计每个学校的学生书籍总页数,可以结合聚合函数和COALESCE处理无书籍的情况(将NULL转为0):
# 统计每个学校的学生书籍总页数 School.joins(:students) .left_joins(students: :books) .select('schools.id, schools.name, COALESCE(SUM(books.pages), 0) AS total_pages') .group('schools.id')
返回的结果中,total_pages字段就是对应学校所有学生书籍的总页数,无书籍的学生贡献0页。
关于includes的补充
如果只是需要预加载学校、学生及关联书籍(避免N+1查询),includes也能实现加载所有学生(含无书籍的),但它的核心是预加载而非统计:
# 预加载所有关联数据,包含无书籍的学生 School.includes(students: :books).all
这个方法会生成左连接SQL(或分两次查询),但如果是做统计场景,优先用left_joins结合聚合的方式更高效。
内容的提问来源于stack exchange,提问作者Sayan Patel
相关产品推荐
相关产品推荐

