rails - mysql joining multiple tables - mysql

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

Related

Rails 5 - Activerecord association 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))

Thinking Sphinx or condtion

How do you use a conditional or in thinking sphinx?
The situation is:
I have a Message model with a sender_id and recipient_id attribute. I would like to compose this query:
Message.where("sender_id = ? OR recipient_id = ?", business_id, business_id)
Right now, I'm searching twice, one for all the messages that has recipient_id = business_id and another to return all messages that has sender_id = business_id. Then I just merge them.
I feel that there's a more efficient way to do this.
EDIT - Adding index file
ThinkingSphinx::Index.define :message, with: :active_record, delta: ThinkingSphinx::Deltas::DelayedDelta do
# fields
indexes body
# attributes
has job_id
has sender_id
has recipient_id
end
Sphinx doesn't allow for OR logic between attributes, only fields. However, a workaround would be to combine the two columns into a third attribute:
has [sender_id, recipient_id], :as => :business_ids, :multi => true
And then you can search on the combined values like so:
Message.search :with => {:business_ids => business_id}

Ruby on rails mysql searches

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)

Rails 2 - named_scope :include, :joins and selecting specific columns

I was wondering if it's possible to use :include in named_scope but to specify only specific columns to :include?
Currently I use the following:
class ProductOverwrite < ActiveRecord::Base
belongs_to :product
named_scope :with_name, :include => :product
def name
produt.name
end
end
But i'm wondering if I can select specific columns from the product table instead of selecting the entire set of columns which I obviously don't need.
This isn't something rails does out of the box.
You could 'piggy back' the attribute
named_scope :with_product_name, :joins => :product, :select => 'product_overwrites.*, products.name as piggy_backed_name'
def product_name
read_attribute(:piggy_backed_name) || product.name
end
If it is possible for a ProductOverwrite to have no product, then you'd need a left join rather than the default inner join.

Rails query on multiple primary keys with conditions on association

Is there a way in Active Record to construct a single query that will do a conditional join for multiple primary keys?
Say I have the following models:
Class Athlete < ActiveRecord::Base
has_many :workouts
end
Class Workout < ActiveRecord::Base
belongs_to :athlete
named_scope :run, :conditions => {:type => "run"}
named_scope :best, :order => "time", :limit => 1
end
With that, I could generate a query to get the best run time for an athlete:
Athlete.find(1).workouts.run.best
How can I get the best run time for each athlete in a group, using a single query?
The following does not work, because it applies the named scopes just once to the whole array, returning the single best time for all athletes:
Athlete.find([1,2,3]).workouts.run.best
The following works. However, it is not scalable for larger numbers of Athletes, since it generates a separate query for each Athlete:
[1,2,3].collect {|id| Athlete.find(id).workouts.run.best}
Is there a way to generate a single query using the Active Record query interface and associations?
If not, can anyone suggest a SQL query pattern that I can use for find_by_SQL? I must confess I am not very strong at SQL, but if someone will point me in the right direction I can probably figure it out.
To get the Workout objects with the best time:
athlete_ids = [1,2,3]
# Sanitize the SQL as we need to substitute the bind variable
# this query will give duplicates
join_sql = Workout.send(:santize_sql, [
"JOIN (
SELECT a.athlete_id, max(a.time) time
FROM workouts a
WHERE a.athlete_id IN (?)
GROUP BY a.athlete_id
) b ON b.athlete_id = workouts.athlete_id AND b.time = workouts.time",
athlete_ids])
Workout.all(:joins => join_sql, :conditions => {:athlete_id => })
If you require just the best workout time per user then:
Athlete.max("workouts.time", :include => :workouts, :group => "athletes.id",
:conditions => {:athlete_id => [1,2,3]}))
This will return a OrderedHash
{1 => 300, 2 => 60, 3 => 120}
Edit 1
The solution below avoids returning multiple workouts with same best time. This solution is very efficient if athlete_id and time columns are indexed.
Workout.all(:joins => "LEFT OUTER JOIN workouts a
ON workouts.athlete_id = a.athlete_id AND
(workouts.time < b.time OR workouts.id < b.id)",
:conditions => ["workouts.athlete_id = ? AND b.id IS NULL", athlete_ids]
)
Read this article to understand how this query works. Last check (workouts.id < b.id) in the JOIN ensures only one row is returned when there are more than one matches for the best time. When there are more than one match to the best time for an athlete, the workout with the highest id is returned(i.e. the last workout).
Certainly following will not work
Athlete.find([1,2,3]).workouts.run.best
Because Athlete.find([1,2,3]) returns an array and you can't call Array.workouts
You can try something like this:
Workout.find(:first, :joins => [:athlete], :conditions => "athletes.id IN (1,2,3)", :order => 'workouts.time DESC')
You can edit the conditions according to your need.