I need to write a MySQL join query for the following scenario.
I have a answers table which has id, score, student_id, tests_passed, created_at, problem_id.
I have a problem table, which has an id, assignment_id.
I have a assignments table, which has an id, title and other fields.
Each answer belongs to a problem. In the answer table, I can retrieve all the answers to a problem by using the problem_id.
Each problem belongs to an assignment. I can retrieve all the problems of an assignment, using the assignment_id in the problems table.
I need to retrieve the final best score of a student in a problem for an assignment.
Is there a way to achieve this without using multiple queries.
Considering these relations:
class Answer < ActiveRecord::Base
belongs_to :student
belongs_to :problem
class Problem < ActiveRecord::Base
belongs_to :assignment
has_many :answers
class Assignment < ActiveRecord::Base
has_many :problems # or has_one, whatever
class Student < ActiveRecord::Base
has_many :answers
has_many :problems, through: :answers # You might not have this relation configured
You could do:
scope = Student.includes(answers: { problems: :assignment })
scope = scope.where(problems: { title: '3x + 2 = 12' })
scope = scope.where(students: { name: 'Johnny' })
scope = scope.where(assignment: { id: my_assignment_id })
scope = scope.select('MAX(score), *') # not sure about this part, depending on your needs
To view all students best assignment score for a given assignment/problem this should work:
select student_id, problem_id, assignment_id, max(score) from answers
join problem on answers.problem_id = problem.id
join assignments on problem.assignment_id = assignments.id
where assignment_id = <your assignment param>
and problem_id = <your problem param>
and answers.id = <your answers id>
group by student_id
However you should be able to do away with the assignment_id param assuming a problem is unique to a single assignment.
Related
I need run a mysql query from the controller but I do not know how to connect to db.
this is my method:
def set_dcs
sql="select employees.nombre, employees.apellido_paterno, employees.apellido_materno from employees, activities, field_tests
where activities.field_test_id=field_tests.id and employees.id=activities.dcs_id and field_tests.id=id"
end
How do I get the result of the query?
How could I do the same query but with the ActiveRecord?
You can execute raw SQL through ActiveRecord::Base.connection but I rarely recommend it and certainly not in this case, but for edification purposes
def set_dcs
sql= <<SQL
select employees.nombre, employees.apellido_paterno, employees.apellido_materno
from employees, activities, field_tests
where activities.field_test_id=field_tests.id and employees.id=activities.dcs_id and field_tests.id=id
SQL
ActiveRecord::Base.connection.exec_query(sql)
end
I was not sure what the trailing id is in reference to and it will raise a SQL error due to it's ambiguity. I am going to assume it is a parameter and the primary search condition where as the rest are table joins.
That being said since you are using rails these can be true associations and joins resulting in far more readable controller code e.g. Model Definitions
class Employee < ActiveRecord::Base
has_many :activities, foreign_key: :dcs_id
has_many :field_tests, through: :activities
end
class FieldTest < ActiveRecord::Base
has_many :activities
end
class Activity < ActiveRecord::Base
belongs_to :employee, foreign_key: :dcs_id
belongs_to :field_test
end
Then the controller is simply
Employee.
select("employees.nombre, employees.apellido_paterno, employees.apellido_materno").
joins(:field_tests).where(field_tests: {id: SOME_ID})
the resulting SQL will be similar to
SELECT
employees.nombre,
employees.apellido_paterno,
employees.apellido_materno
FROM
employees
INNER JOIN activities ON employees.id = activities.dcs_id
INNER JOIN field_tests ON activities.field_test_id = field_tests.id
WHERE
field_tests.id = SOME_ID
And this will return a collection of Employee Objects rather than an ActiveRecord::Result (returned from ActiveRecord::Base.connection.exec_query) which is more similar to an Array than anything else.
this situation that find the user on a join model is seems tricky than usual
class ConversationParticipant < ActiveRecord::Base
belongs_to :user
belongs_to :conversation
attr_accessible :user_id
end
class User < ActiveRecord::Base
has_many :conversation_participants
end
I'm trying to find the conversation_participant with the fisrt_name with this query
user = User.where('conversation_participant like ? or first_name like ?', query, query)
but the query did not return the user_participant and the user first_name either.
someone can spare a hint please!
You have to include the % for the sql LIKE operator
query = params[:query] # or something ...
User.where('conversation_participant LIKE :query OR first_name LIKE :query', query: "%#{query}%")
I have two models associated with each other as follows.
class Comment < ApplicationRecord
belongs_to :user
end
class User < ActiveRecord::Base
has_many :comments
end
Following record query
comments_list = Comment.where(:post_id => post_id, :is_delete => false).joins(:user).select('comments.*,users.*')
Generates the following mysql query in logger
SELECT comments.*,users.* FROM `comments` INNER JOIN `users` ON `users`.`id` = `comments`.`user_id` WHERE `comments`.`post_id` = '81' AND `comments`.`is_delete` = 0.
This seems generating very ligitimate query, but comments_list object contain columns only from comments table.
Thanks
It depends on what you want to do, if you want to display the username next to the comment, Mert B.'s answer is fine, all you have to do is include(:user) and the users from the comment list will be fetched along when you do something like this:
comments_list = Comment.where(:post_id => post_id, :is_delete => false).joins(:user).select('comments.*,users.*')
comments_list.each do |comment|
puts "#{comment.text} by #{comment.user.name}"
end
Or maybe if you want only users who have at least one comment, you can always select users from the user_ids on the comments table:
User.where(id: Comment.select(:user_id))
There are three table namely attendances,students and subjects for which I am unsuccessfully trying to do build an inner join query using active record.
Desired sql query
The raw sql statement that fetches correct data is
SELECT * FROM attendances AS a
INNER JOIN subjects AS b ON a.subject_id = b.id
WHERE b.organization LIKE '%city%'
AND a.started_at BETWEEN '2016-07-11 02:59:00' AND '2016-07-11 09:00:00'
The above query fetches exactly the correct 30 rows. However the problem arises when I try to perform the same with active-record associations.
Active-record associations
ActiveRecord::Base.establish_connection ({ adapter: 'mysql2',host:'localhost',
username: 'testuser',password: '***',database: 'database'})
class Subject < ActiveRecord::Base
has_many :attendances
has_many :students, through: :attendances
end
class Student < ActiveRecord::Base
has_many :attendances
has_many :subjects, through: :attendances
end
class Attendance < ActiveRecord::Base
belongs_to :subject
belongs_to :student
end
data = Attendance.
where(started_at: start_date..end_date).
joins(:subject).
where(subjects: {:organization => city}).
pluck(:subject_id,:name,:duration,:started_at,:student_id)
Using _to_sql_ and after removing the :pluck, the sql query generated by active record is
SELECT `attendances`.* FROM `attendances` INNER JOIN `subjects`
ON `subjects`.`id` = `attendances`.`subject_id`
WHERE (`attendances`.`started_at` BETWEEN '2016-07-11 02:59:00'
AND '2016-07-11 09:59:00')
AND `subjects`.`organization` = 'city'
Modifying the statement to the below one doesn't help either.
data = Attendance.joins(:subject).
where('started_at BETWEEN ? AND ?','2016-07-11 02:59:00','2016-07-11 09:59:00').
where(subjects: {:organization => 'city'}).
pluck(:subject_id,:name,:duration,:started_at,:student_id)
The sql query generated for the above statement after removing :pluck using _to_sql_ as suggested is
SELECT `attendances`.* FROM `attendances` INNER JOIN `subjects`
ON `subjects`.`id` = `attendances`.`subject_id`
WHERE (started_at BETWEEN '2016-07-11 02:59:00' AND '2016-07-11 09:59:00')
AND `subjects`.`organization` = 'city'
Both the above active-record statement results in additional irrelavent rows being fetched. I am unable generate the desired correct sql query as shown earlier.
Constructing a correct active record statement or associations would be really helpful.
environment:-
ruby 2.1.6
activerecord 4.2.4
I have a Student model and a Gpa model. Student has_many Gpa. How would I sort students based on their most recently created gpa record's value attribute?
NOTE: I don't want to sort an individual student's GPAs based on the created date. I would like to pull ALL students and sort them based on their most recent GPA record
class Student < ActiveRecord::Base
has_many :gpas
end
#students = Student.order(...)
assuming the gpas timestamp is updated_at
Student.joins(:gpas).order('gpas.updated_at DESC').uniq
To include students without gpas
#references is rails 4; works in rails 3 without it
Student.includes(:gpas).order('gpas.updated_at DESC').references(:gpas).uniq
if you dont like the distinct that uniq creates, you can use some raw sql
Student.find_by_sql("SELECT students.* FROM students
INNER JOIN gpas ON gpas.student_id = students.id
LEFT OUTER JOIN gpas AS future ON future.student_id = gpas.student_id
AND future.updated_at > gpas.updated_at
WHERE future.id IS NULL ORDER BY gpas.updated_at DESC")
# or some pretty raw arel
gpa_table = Gpa.arel_table
on = Arel::Nodes::On.new(
Arel::Nodes::Equality.new(gpa_table[:student_id], Student.arel_table[:id])
)
inner_join = Arel::Nodes::InnerJoin.new(gpa_table, on)
future_gpa_table = Gpa.arel_table.alias("future")
on = Arel::Nodes::On.new(
Arel::Nodes::Equality.new(future_gpa_table[:student_id], gpa_table[:student_id]).\
and(future_gpa_table[:updated_at].gt(gpa_table[:updated_at])
)
)
outer_join = Arel::Nodes::OuterJoin.new(future_gpa_table, on)
# get results
Student.joins(inner_join).joins(outer_join).where("future.id IS NULL").\
order('gpas.updated_at DESC')
I'm not sure that there's a way of achieving this in any kind of convenient and mostly-ruby way. The SQL required for an efficient implementation probably requires an order based on join -- something like ...
select
...
from
students
order by
( select gpas.value
from gpas
where gpas.student_id = student.id
order by gpas.as_of_date desc
limit 1)
I'm not sure if that's legal in MySQL, but if it is you could probably just:
Student.order("(select gpas.value from gpas where gpas.student_id = student.id order by gpas.as_of_date desc limit 1)")
On the other hand, it seems like the last value would be an important one, so you might like to implement a callback on gpas to set a "last_gpa_id" or "last_gpa_value" in the students table to make this common join more efficient.
Then of course the implementation would be trivial.
#students = Student.includes(:gpas).order('gpas.value DESC')
Still it's important to note that this will include Students, who has got no gpas. But you can filter that easly out with #students.delete_if{ |s| s.gpas.blank? }
Probably something like
Student.joins(:gpas).order("gpas.value DESC")
You could try adding some options to the relationships in your models.
Something like:
class Student < ActiveRecord::Base
has_many :gpas, order: "value DESC", conditions: "foo = bar" #<-whatever conditions you want here
end
class Gpa < ActiveRecord::Base
belongs_to :student
end
Using options, all you have to do make a call and let Rails do most of the heavy lifting.
If you get stumped, there are several more options here: http://guides.rubyonrails.org/association_basics.html
Just do a keyword search on the page for "The has_and_belongs_to_many association supports these options:"
This should work for you.
Try this SQL query
SELECT * FROM students WHERE id IN
(SELECT student_id
FROM gpa
GROUP BY student_id
ORDER BY created_at DESC);
I think, you could try this method:
class Student < ActiveRecord::Base
attr_accessible :name
has_many :gpas
def self.by_last_gpas
sql = <<-SQL
select students.*,
(
select gpas.created_at from gpas where student_id=students.id
order by gpas.created_at desc
limit 1
) as last_created_at
from students
order by last_created_at desc
SQL
Student.find_by_sql(sql)
end
end
Quick and dirty:
You can somehow implement a query like this to fetch AR objects you need:
select s.* from students s, gpas g
where s.id = gpas.student_id
and gpas.id in (select max(id) from gpas group by student_id)
order by gpas.value
Assuming that id is higher for records with higher created_at.
OR
Nicer way:
I assume, you'll need student's last GPA score very often. Why not add a new column :last_gpa_score to Student model?
You can use callback to keep this field consistent and autofilled, i. e.:
class Gpa < ActiveRecord::Base
belongs_to :student
after_save :update_student_last_score
private
def update_student_last_score
self.student.update_last_gpa_score
end
end
class Student < ActiveRecord::Base
has_many :gpas
def update_last_gpa_score
self.last_gpa_score = self.gpas.order("created_at DESC").first.value
end
end
Then you can do whatever you like with last_gpa_score field on student.
You'll have to adjust the table names and column names to match your DB.
SELECT s.*, g.*
FROM (SELECT studentId, MAX(gpaDate) as gpaDate FROM gpas GROUP BY studentId) maxgpa
JOIN gpas g
ON g.studentid = maxgpa.studentId
AND g.gpaDate = maxgpa.gpaDate
JOIN students s
ON s.studentId = g.studentId
ORDER g.gpaDate DESC
Create a last_gpa method to get the last GPA created and then perform a standard Ruby sort.
def last_gpa
a.gpas.order(:order=>"created_at DESC").first
end
Student.all.sort { |a, b| a.last_gpa <=> b.last_gpa }