处理以下 8 个不同 SQL 问题的最佳方法是什么。
我在下面放置了一个数据库模式,它在我的 Rails 模型中是如何表示的,以及我需要从数据库中取出数据的七个问题。有些问题我已经回答了,有些问题我不确定最好的解决方案。
问题 #7 是一个曲线球,因为它可能会改变所有其他问题的答案。
标准
- 不需要 n+1 查询。多个查询是可以的,但是如果返回的每一行都需要一个额外的查询,那么它是不可扩展的。
- 不需要后处理来过滤 SQL 可以自己执行的结果。例如,第 5 项的答案不应该是从数据存储中拉出所有学生,然后删除那些没有课程的学生。
- 检索对象的计数不应触发另一个 SQL 查询。
- 如果 SQL 允许我聚合数据,则不必通过非规范化添加数据库列
- MongoDB 或 CouchDB 等 NOSQL 解决方案是否更适合回答以下所有问题?
数据库模式
学生 -------- ID 姓名 培训班 ----- ID 姓名 年级 招生 ---------- ID 学生卡 Course_ID
活动记录模型
class Course < ActiveRecord::Base
has_many :enrollments
has_many :students, :through=>:enrollments
end
class Enrollment < ActiveRecord::Base
belongs_to :student
belongs_to :course
end
class Student < ActiveRecord::Base
has_many :enrollments
has_many :courses, :through => :enrollments
end
问题
1) 检索所有 9 年级数学课程的学生
SQL
SELECT s.* FROM Students s
LEFT JOIN Enrollments e on e.student_id = s.id
LEFT JOIN Courses c on e.course_id = c.id
WHERE c.grade = 9 AND c.name = 'Math'
解决方案
这个很简单。ActiveRecord 处理得很好
c = Course.where(:grade=>9).where(:name=>'Math').first
c.students
2) 检索 John 上的所有课程
SQL
SELECT c.* FROM Courses c
LEFT JOIN Enrollments e on c.id = e.course_id
LEFT JOIN Students s on e.student_id = s.id
WHERE s.name = 'John'
解决方案
再次,简单。
s = Student.where(:name=>'John').first
s.courses
3) 检索所有 9 年级课程以及参加该课程的学生人数(但不检索学生)
SQL
SELECT c.*, count(e.student_id) FROM Courses C
LEFT JOIN Enrollments e on c.id = e.course_id
WHERE c.grade = 9 GROUP BY c.id
解决方案
Counter Cache 在这里可以很好地工作。
类 AddCounters < ActiveRecord::Migration 向上定义 add_column :students, :courses_count, :integer, :default=>0 add_column :courses, :students_count, :integer, :default=>0 Student.reset_column_information Student.all.each 做 |s| Student.update_counters s.id, :courses_count => s.courses.length 结尾 Course.reset_column_information Course.all.each 做 |c| Course.update_counters c.id, :students_count => c.students.length 结尾 结尾 向下定义 remove_column:学生,:courses_count remove_column:课程,:students_count 结尾 结尾
活动记录
Course.where(:grade=>9).each 做 |c| 提出“#{c.name} - #{c.students.size}” 结尾
4) 检索所有参加至少三门 11 年级课程、多于一门 10 年级课程且没有 9 年级课程的学生
没有解决方案
不确定最佳解决方案。如果没有为每个学生的每个年级水平的课程数量保留计数器缓存,在 SQL 中执行此操作将非常混乱。我可以添加一个钩子来自己更新这些信息。我不想拉出所有学生和课程并在后期处理中计算它们。
缓慢的解决方案
以下解决方案会产生大量查询。可能无法预加载课程。(例如,学生来自协会的课程)
students = some_course.students
matching_students = []
students.each do |s|
courses_9 = 0
courses_10 = 0
courses_11 = 0
s.courses.each do |c|
courses_9 += 1 if c.grade == 9
courses_10 += 1 if c.grade == 10
courses_11 += 1 if c.grade == 11
end
if courses_11 <= 3 && courses_10 > 1 && courses_9 == 0
matching_students << s
end
end
return matching_students
5) 检索所有参加多于一门数学课程查询的学生)
SQL
SELECT s.*, count(e.course_id) as num_Courses FROM Students s
INNER JOIN Enrollments e on s.id = e.student_id
INNER JOIN Courses c on e.course_id = c.id AND c.name = 'Math'
GROUP BY s.id HAVING num_Courses > 0
或者
SELECT DISTINCT s.* FROM Students s
INNER JOIN Enrollments e_math_1 on e_math_1.student_id = s.id
INNER JOIN Courses c_math_1 ON e_math_1.course_id = c_math_1.id AND c_math_1.name = 'Math'
INNER JOIN Enrollments e_math_2 on e_math_2.student_id = s.id
INNER JOIN Courses c_math_2 ON e_math_2.course_id = c_math_2.id AND c_math_2.name = 'Math'
WHERE c_math_1.id != c_math_2.id
没有解决方案
不确定最佳解决方案。棘手的部分是 ActiveRecord(或 NoSQL)解决方案无法检索所有学生,然后查看他们的课程,因为这太慢了。
缓慢的解决方案
students = SomeObject.students
multiple_math_course_students = []
students.each do |s|
has_math_course = false
add_student = false
s.courses.each do |c|
if c.name == 'Math'
if has_math_course
add_student = true
else
has_math_course = true
end
end
end
multiple_math_course_students << s if add_student
end
6) 检索所有正在学习数学和科学课程的学生
SQL
SELECT s.* FROM Students s
INNER JOIN Enrollments e_math on e_math.student_id = s.id
INNER JOIN Courses c_math ON e_math.course_id = c_math.id
INNER JOIN Enrollments e_science on e_science.student_id = s.id
INNER JOIN Courses c_science on e_science.course_id = c_science.id WHERE c_math.name = 'Math' AND c_science.name = 'Science'
没有解决方案
这涉及两次加入同一个表(或在 Rails 中,关联)。有没有办法用 ActiveRecord 的 AREL 包装器顺利地做到这一点?您可以为科学课和数学课建立一个单独的关联,允许您对每个课程进行单独的操作,但这在下面的 #7 的情况下不起作用。
缓慢的解决方案
students = SomeObject.students
math_and_science_students = []
students.each do |s|
has_math_course = false
has_science_course = false
s.courses.each do |c|
has_math_course = true if c.name == 'Math'
has_science_course = true if c.name == 'Science'
end
math_and_science_students << s if has_math_course && has_science_course
end
7) 客户表示,每当系统显示学生时,在学生旁边显示一个数字,显示他们正在学习的最高年级课程。例如,如果 Suzie 正在学习 9 年级的科学课程和 10 年级的数学课程,则在 Suzie 旁边显示“10”。
解决方案
查询每个学生记录的数据库是不可接受的。显示 100 个学生的页面需要 100 个查询。此时,我想通过在学生表中放置一个带有“最高级别课程”的标志来对数据库进行非规范化。这是我最好的做法吗?从一开始就使用关系数据库以外的其他数据存储会更好吗?
想象一下,客户要求将任意数据显示为徽章:最高年级、所修数学课程数量、如果同时学习数学、科学和历史,则获得金徽章等 。这些案例中的每一个是否都需要非规范化数据库的?非规范化数据是否应该与规范化数据保存在同一个关系数据库中?