处理以下8个不同SQL问题的最佳方法是什么。
我已经在数据库模式下面放置了它,它在我的Rails模型中如何表示,以及我需要从数据库中获取的数据的七个问题。我已回答了一些问题,其他问题我不确定最佳解决方案。
问题#7是一个曲线球,因为它可能会改变所有其他问题的答案。
Students ------- ID Name Courses ----- ID Name Grade Enrollments ---------- ID Student_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
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
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
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
计数器缓存在这里可以很好地工作。
class AddCounters < ActiveRecord::Migration def up add_column :students, :courses_count, :integer, :default=>0 add_column :courses, :students_count, :integer, :default=>0 Student.reset_column_information Student.all.each do |s| Student.update_counters s.id, :courses_count => s.courses.length end Course.reset_column_information Course.all.each do |c| Course.update_counters c.id, :students_count => c.students.length end end def down remove_column :students, :courses_count remove_column :courses, :students_count end end
ActiveRecord的
Course.where(:grade=>9).each do |c| puts "#{c.name} - #{c.students.size}" end
不确定最佳解决方案。如果没有为每个学生的每个年级的课程数量保留一个计数器缓存,那么在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
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
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
查询每个学生记录的数据库是不可接受的。显示100名学生的页面需要100个查询。此时,我希望通过在学生表中使用“最高级别课程”标记来对数据库进行非规范化。这是我最好的行动方案吗?从一开始就使用除关系数据库之外的其他数据存储会更好吗?
想象一下,客户要求将任意数据显示为徽章:最高等级,数学课程数量,如果将数学,科学和历史全部放在一起的金徽章等等。这些情况中的每一个都应该是对数据库进行非规范化的调用吗?非规范化数据应该与标准化数据保存在同一个关系数据库中吗?
答案 0 :(得分:3)
首先,我认为您的数据库架构很好。我不会根据这些用例去标准化,因为它们很常见。
其次,您必须学会区分持久性,业务逻辑和报告。 ActiveRecord适用于基本持久性和封装业务逻辑。它处理CRUD的东西,并允许您将大量的应用程序逻辑放在模型中。但是,你所谈论的很多逻辑听起来都像是报道,特别是#6。您将不得不接受这样的查询逻辑,原始SQL将是您最好的选择。我认为你已经实现的缓存计数器可以帮助你保持活跃的记录和模型,如果你在那里更舒服,但很可能你必须放弃到简单的SQL,因为你已经为这些解决方案做了几个。报告通常需要直接sql。
规范化数据库对良好的应用程序设计至关重要。对于使OLTP事务和业务逻辑清理代码非常重要。不要因为你必须在sql中做一些连接而非规范化。这就是sql擅长的。通过非规范化所要做的就是使你的一些报告逻辑变得更快更容易,这使得你的持久性和OLTP逻辑越来越慢。
所以我会开始保持你的规范化数据库。如果你需要加入一个相关的表,你通常可以使用activerecord的include方法来执行此操作,而无需使用常规的SQL。要做一些基于连接的计数,你必须使用普通的sql。
最终,如果您的数据库变得非常大并且包含大量数据,那么您的报告将因为您必须执行的所有联接而变慢。这可以。在那一点上,不久之后,开始考虑建立一个非规范化的单独报告数据库,您可以从规范化数据库中每小时,每晚,每周等更新。然后移动报告逻辑以查询报告数据库,而无需进行连接。但是,没有必要以这种方式开始。你只需承担额外的复杂性和费用而不确定收益。也许带有连接的报告sql将无限期地工作,而不使用索引进行非规范化。不要过早优化。
我不认为nosql也不一定是答案。据我所知,NoSQL适用于特定用例。您的应用程序的用例和模式似乎适合关系数据库。
总的来说,我认为原始sql(不是arel / activerecord)和你实现的计数器的组合都很好。
答案 1 :(得分:1)
我此刻遇到了同样的问题。根据我的研究,有几种方法可以解决它。
首先,我相信任何应用程序都会遇到这些问题。基本思想是我们以规范化的方式对数据进行建模,当有大量数据和数据跨越多个表时,这种方式本身变得缓慢而繁琐。
我能够提出的最佳方法如下:
这两个应该为应用程序提供很大的灵活性,并提供许多便利方法以及解决我试图回答的大部分问题
一旦我需要做一堆连接来获得我需要的东西,我觉得我应该对表格进行非规范化以便轻松达到我需要的东西,我考虑以下内容:
SQL视图: 这些是预定义的sql语句,例如,我可以将模型链接到的语句。 通常,这比通过ActiveRecord查询更快 http://hashrocket.com/blog/posts/sql-views-and-activerecord
聚合表: 创建一个或多个聚合表并使用delayed_job异步更新,例如resque。 例如,这些聚合可以每天更新一次,模型可以直接查询。 请注意,这是某种非规范化表。
Couchbase(NOSQL) 我还没有用过这个,但它看起来很有趣。 http://couchbaseonrails.com/understand