以下の8つの異なるSQLの質問に対処するための最良の方法は何ですか。
データベーススキーマ、Railsモデルでの表現方法、およびデータベースから取得する必要のあるデータに関する7つの質問を下に配置しました。私が答えた質問もあれば、最善の解決策がわからない質問もあります。
質問#7は、他のすべての質問への回答を変更する可能性があるため、カーブボールです。
基準
- n+1クエリは必要ありません。複数のクエリは問題ありませんが、返されるすべての行に追加のクエリが必要な場合、スケーラブルではありません。
- SQLが独自に実行できる結果をフィルタリングするために後処理を必要としないでください。たとえば、5番目の答えは、すべての学生をデータストアから取得してから、コースのない学生を削除することではありません。
- オブジェクトのカウントを取得しても、別のSQLクエリがトリガーされることはありません。
- SQLでデータを集約できる場合は、非正規化によってデータベース列を追加する必要はありません。
- MongoDBやCouchDBなどのNOSQLソリューションは、以下のすべての質問に答えるのに適していますか?
データベーススキーマ
学生 ------- ID 名前 コース ----- ID 名前 学年 登録 ---------- ID 学生証 Course_ID
ActiveRecordモデル
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)ジョンが受講したすべてのコースを取得する
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
解決
カウンターキャッシュはここでうまく機能します。
クラス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 do | s | Student.update_counters s.id、:courses_count => s.courses.length 終わり Course.reset_column_information Course.all.each do | c | Course.update_counters c.id、:students_count => c.students.length 終わり 終わり デフダウン remove_column:students、:courses_count remove_column:courses、:students_count 終わり 終わり
ActiveRecord
Course.where(:grade => 9).each do | c | puts "#{c.name}-#{c.students.size}" 終わり
4)少なくとも3つの11年生のコース、1つ以上の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では関連付け)に2回参加することが含まれます。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)顧客は、学生がシステムに表示されるときはいつでも、受講している最高学年レベルのコースを示す番号を学生の横に表示すると述べています。たとえば、スージーが9年生の理科コースと10年生の数学コースを受講している場合、スージーの横に「10」を表示します。
解決
すべての学生レコードについてデータベースにクエリを実行することは受け入れられません。100人の学生を表示するページには、100個のクエリが必要です。この時点で、「最高レベルのコース」のフラグを学生テーブルに配置して、データベースを非正規化したいと思います。これが私の最善の行動ですか?最初からリレーショナルデータベース以外の別のデータストアを使用する方が良いでしょうか?
顧客が任意のデータをバッジとして表示するように要求したと想像してください:最高学年レベル、受講した数学コースの数、数学、科学、歴史をすべて一緒に受講する場合のゴールドバッジなど。 これらの各ケースが非正規化の要求である場合データベースの?非正規化されたデータは、正規化されたデータと同じリレーショナルデータベースに保持する必要がありますか?