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))
Related
Lets take the following example. We have two models with this association
class Post < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :posts
end
So as you know, we can do the following:
<%= #post.title %>
<%= #post.user.name %>
etc etc
This association usually generates the following queries:
SELECT * FROM `posts` WHERE `post`.`id` = 1;
SELECT * FROM `users` WHERE `user`.`id` = 15; # from #post.user
Now if I want to select some specific fields (lets assume for a moment there's no association) when listing all posts or show just a single one, I do something like this:
#posts = Post.select("id, title, slug, created_at").all()
# or
#post = Post.select("id, title, slug, created_at").find(1)
In case of association how can I select specific fields for the associated query? In other words instead of having
SELECT * FROM `users`
SELECT * FROM `posts` WHERE `user_id` IN ( user IDs here )
to have
SELECT `id`, `name` FROM `users`
SELECT `id`, `user_id`, `title`, `slug` FROM `posts` WHERE `user_id` IN ( user IDs here )
When you retrieve a single Post restricting it's attributes in select clause, it behaves like any other Post, but in order to have access to the User it should have the user_id attribute selected.
#post = Post.select("id").first
# this post doesn't know about it's user since it has no user_id attribute
#post.user # => nil
#post = Post.select("id", "user_id").first
# this post has a user
#post.user # => #<User...>
When you retrieve a list of posts you should solve the 'n+1 queries problem'. You can read about it here http://guides.rubyonrails.org/active_record_querying.html (13 Eager Loading Associations)
You can use includes for eager loading associations:
#posts = Post.includes(:user)
#posts.each do |post|
post.user # this will not make an extra DB query since users are already eager loaded
end
Or if you don't need to instantiate users and want just get specific attributes, the better solution is to join with users and take attributes that you need
#posts = Post.joins(:user).select("posts.id", "posts.title", "users.name AS user_name")
#posts.each do |post|
post.user_name # each post now has `user_name` attribute
end
Edited
As far as I know using includes will ignore select. You can't use select here to specify which attributes of a Post or a User you need. The whole Post will be retrieved and the whole Users will be eager loaded. However, there is a way to restrict user's loading attributes by specifying them in belongs_to association. There is a similar question about this: ActiveRecord includes. Specify included columns
Also, keep in mind, that Post.joins(:user) will not return posts that have no user. So, if it is possible and you need all posts use .left_outer_joins instead.
# works in Rails 5
Post.left_outer_joins(:user).select(...)
You need to refer below link about association
http://blog.bigbinary.com/2013/07/01/preload-vs-eager-load-vs-joins-vs-includes.html
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 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.
I am totally new to Ruby on Rails and I am trying to search through some relational database tables, I am trying to search for a given ID number in a Customer table then from the results look at who the sales_rep for that customer is. With this
#salesrepcust = Customer.find(:all, :conditions => ["id = ?",#data])
I am able to get back the correct customer given there ID number but I dont see how in ruby on rails to then pull from those results just one column value, in this it would be the value for sales_rep, and then use that as my #result for
#salesrepcustdata = Salesrep.find(:all, :conditions => ["id = ?", #result])
I have searched for this but i guess im not wording it correctly because i am not able to find anything specifically on this, can anyone help?
It's pretty straightforward to select a single column; you can try something like this:
#salesrepcustids = Customer.where(id: #data).select(:id)
This will generate a SELECT id FROM ... statement.
And now you can do this:
#salesrepcustdata = Salesrep.where(id: #salesrepcustids)
This will generate an SELECT...IN statement with those ids.
(You might find it easier to set up proper ActiveRecord has_many and belongs_to relationships in your models, or whatever relationship is appropriate.)
Assuming the sales rep is represented in the Customer table as sales_rep_id you can just do:
Salesrep.find(Customer.find(#data).sales_rep_id)
The find method assumes you're looking for id and if there's just one item with that id, there's no need to specify :all.
This is all discussed in http://guides.rubyonrails.org/active_record_querying.html
That Customer query could be simplified to just:
#customer = Customer.find(#data)
You don't mention if you've setup a relationship between Customer and Salesrep but here goes:
# app/models/customer.rb
class Customer < ActiveRecord::Base
belongs_to :salesrep, class_name: 'Salesrep' # => the customers table should have a salesrep_id column
end
# app/models/salesrep.rb
class Salesrep < ActiveRecord::Base
has_many :customers
end
customer_id = 1
#customer = Customer.find(customer_id)
#salesrep = #customer.salesrep
# from the other way, assuming you need both the salesrep and customer:
salesrep_id = 10
#salesrep = Salesrep.find(salesrep_id)
# the following will only find the customer if it's owned by the Salesrep
#customer = #salesrep.customers.find(customer_id)
I am trying to do a basic report here
Show a list of users with details like names, age etc.
Also show other details like number of posts, last activity time etc.
All these other details are in a has_many relation from users
This is what I am doing right now:
User.find(
:all,
:select => "users.id, users.name, count(distinct(posts.id)) as posts_count",
:joins => "left outer join posts on posts.user_id = users.id",
:group => "users.id",
:limit => "100"
)
Have indexed the user_id column in posts table
My problem is it is taking a very long time to do this and sometimes hangs when I try to do more tables along with posts like activities, comments etc.
Is there a way join the count alone or some other way to achieve this?
Thanks in advance
I think one of these might work, but as always when doing an outer join, the query will be slower.
User.count(:include=>[:posts])
User.count(:select => "users.id, users.name, count(distinct(posts.id)) as posts_count",
:joins => "left outer join posts on posts.user_id = users.id",
:group => "users.id")
User.find(:all, :include => [:posts])
Also you can put in an initializer file or irbrc file this:
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Base.clear_active_connections!
And check the queries as you type them in console.
For post count
User.all.posts.size
As far as reporting each post count later you can do that with the User record you have already.
#users = User.find(:all, :include=> [:posts])
Later ...
<% #users.each do |user|
Posts: <%= user.posts.size %>
<% end %>
Users sorted by post
#sorted_users = #users.sort_by{|user| user.posts.size }
https://stackoverflow.com/a/5739222/1354978