Are those two queries the same in rails 3.2.12? - sql

We need to retrieve logs for customer comm record in our rails app. The condition in plain english is logs for customer_comm_record based on:
#1. the sales_id in customer comm record's customers is equal to current user id
#2. the sales_id in customer comm record's customer's project is equal to the current user id.
The SQL code for #1 could be (SQL#1):
Log.joins(:customer_comm_record =>:customer).where('customers.sales_id = ?', session[:user_id])
SQL code for #2 could be (SQL#2):
Log.joins(:customer_comm_record =>{ :customer => :projects}).where('projects.sales_id = ?', session[:user_id])
Here is what we come up (SQL#3) in one line:
Log.joins(:customer_comm_record =>{ :customer => :projects}).where('customers.sales_id = ? OR projects.sales_id = ?', session[:user_id], session[:user_id])
The SQL code returned by above in rails console is (replacing session[:user_id] with 1) :
SELECT "logs".* FROM "logs" INNER JOIN "customer_comm_records" ON "customer_comm_records"."id" = "logs"."customer_comm_record_id" INNER JOIN "customers" ON "customers"."id" = "customer_comm_records"."customer_id" INNER JOIN "projects" ON "projects"."customer_id" = "customers"."id" WHERE (customers.sales_id = 1 OR projects.sales_id = 1)
The question is weather SQL#3 == SQL#1 UNION SQL#2. Can someone answer the question? If it is not, what's the right one? Thanks.

The only differences are:
The first method will return duplicate entries where the sales_id for the customer and project both match the users id.
The second method might not return records where a customer with a matching sales_id does not have a project (no projects.customer_id for that customer).
The last difference only matters if a customer does not necessarily have any projects. Otherwise, the second method is better because it avoids dealing with duplicates.
If a customer does not always have a project, a simple workaround would be to use includes instead of joins:
Log.includes(:customer_comm_record =>{ :customer => :projects}).
where('customers.sales_id = :id OR projects.sales_id = :id', id: session[:user_id])
This will force a LEFT JOIN and will return customers regardless of whether they are associated with a project.

Related

how to get name from a table corresponding to the id taken from different table in zend framework

i written a query in php zend framework where i joined three table employee, products, bid.
in product table i have a column buyer, where am saving buyer employee id, and am displaying details of sellername(taken from employee nname, column name ename, ) and also need to display buyer name by taking buyer id from the product table and matching ename from employee table.
my sql query is given below.
except the above feature its working fine.
$oSelect = $this->select()
->setIntegrityCheck(false)
->from(array("p" => "products","b" => "bid"), ('*'))
->joinLeft(array("b" => "bid"), "b.product_id=p.product_id", array('bid_id','bid_amount'))
->joinInner(array("e" => "employees"), "e.employee_id=p.employee_id",array('ename'))
->where("p.verified = ?", "Yes")
->where("p.sold_out = ?", "Yes")
->group('p.product_id')
->having("p.sale_end_date >= ?", date("Y-m-d"));
so anyone help me to add the above feature to this query.
Select query will be like this for displaying buyername
$oSelect = $this->select()
->setIntegrityCheck(false)
->from(array("p" => "products","b" => "bid"), ('*'))
->joinLeft(array("b" => "bid"), "b.product_id=p.product_id", array('bid_id','bid_amount'))
->joinLeft(array("e" => "employees"), "e.employee_id=p.employee_id",array('ename'))
->joinLeft(array("e1" => "employees"), "e1.employee_id=p.buyer_id",array('buyer_name' => 'ename'))
->where("p.verified = ?", "Yes")
->where("p.sold_out = ?", "Yes")
->group('p.product_id')
->having("p.sale_end_date >= ?", date("Y-m-d"));

Rails ActiveRecord Join Query With conditions

I have following SQL Query:
SELECT campaigns.* , campaign_countries.points, offers.image
FROM campaigns
JOIN campaign_countries ON campaigns.id = campaign_countries.campaign_id
JOIN countries ON campaign_countries.country_id = countries.id
JOIN offers ON campaigns.offer_id = offers.id
WHERE countries.code = 'US'
This works perfectly well. I want its rails active record version some thing like:
Campaign.includes(campaign_countries: :country).where(countries: {code: "US"})
Above code runs more or less correct query (did not try to include offers table), issue is returned result is collection of Campaign objects so obviously it does not include Points
My tables are:
campaigns --HAS_MANY--< campaign_countries --BELONGS_TO--< countries
campaigns --BELONGS_TO--> offers
Any suggestions to write AR version of this SQL? I don't want to use SQL statement in my code.
I some how got this working without SQL but surely its poor man's solution:
in my controller I have:
campaigns = Campaign.includes(campaign_countries: :country).where(countries: {code: country.to_s})
render :json => campaigns.to_json(:country => country)
in campaign model:
def points_for_country country
CampaignCountry.joins(:campaign, :country).where(countries: {code: country}, campaigns: {id: self.id}).first
end
def as_json options={}
json = {
id: id,
cid: cid,
name: name,
offer: offer,
points_details: options[:country] ? points_for_country(options[:country]) : ""
}
end
and in campaign_countries model:
def as_json options={}
json = {
face_value: face_value,
actual_value: actual_value,
points: points
}
end
Why this is not good solution? because it invokes too many queries:
1. It invokes query when first join is performed to get list of campaigns specific to country
2. For each campaign found in first query it will invoke one more query on campaign_countries table to get Points for that campaign and country.
This is bad, Bad and BAD solution. Any suggestions to improve this?
If You have campaign, You can use campaign.campaign_countries to get associated campaign_countries and just get points from them.
> campaign.campaign_countries.map(&:points)
=> [1,2,3,4,5]
Similarly You will be able to get image from offers relation.
EDIT:
Ok, I guess now I know what's going on. You can use joins with select to get object with attached fields from join tables.
cs = Campaign.joins(campaign_countries: :country).joins(:offers).select('campaigns.*, campaign_countries.points, offers.image').where(countries: {code: "US"})
You can than reference additional fields by their name on Campaign object
cs.first.points
cs.first.image
But be sure, that additional column names do not overlap with some primary table fields or object methods.
EDIT 2:
After some more research I came to conclusion that my first version was actually correct for this case. I will use my own console as example.
> u = User.includes(:orders => :cart).where(:carts => { :id => [5168, 5167] }).first
> u.orders.length # no query is performed
=> 2
> u.orders.count # count query is performed
=> 5
So when You use includes with condition on country, in campaign_countries are stored only campaign_countries that fulfill Your condition.
Try this:
Campaign.joins( [{ :campaign_countries => :countries}, :offers]).where('`countries`.`code` = ?', "US")

Is it the right way to do union query after joins in rails 3.2?

There are 3 models log (which belongs to customer), customer and project in rails 3.2 app. Both customer and project have sales_id field. Here is the query we want to do:
return the following logs for customers 1) logs for customers whose sales_id is equal to session[:user_id] and 2) logs for customers whose projects' sales_id is equal to session[:user_id]
The rails query for 1) could be:
Log.joins(:customer).where(:customers => {:sales_id => session[:user_id]})
Rails query for 2) could be:
Log.joins(:customer => :projects).where(:projects => {:sales_id => session[:user_id})
To combine the queries above, is it the right way to do the following?
Log.joins([:customer, {:customer => :projects}]).where('customers.sales_id = id OR projects.sales_id = id', id: session[:user_id])
Chapter 11.2.4 in http://guides.rubyonrails.org/v3.2.13/active_record_querying.html talks about an interesting query case. We haven't tested the query above yet. We would like to know if the union query above is indeed correct.
Rails doesn't support union natively. In your case, I think it doesn't need union, just use left outer join.
Log.joins('left outer JOIN `customers` ON `customers`.`id` = `logs`.`customer_id`
left outer JOIN `projects` ON `projects`.`customer_id` = `customers`.`id`').where('customers.sales_id = :id OR projects.sales_id = :id', id: session[:user_id]).distinct

Complex rails find ordering

I am trying to do a find which orders results by their house name and then by the customer's last name.
Customer.find(:all,
:conditions =>['customers.id IN (?)', intersection],
:joins => 'JOIN histories ON histories.customer_id = customers.id
JOIN houses ON histories.house_id = houses.id',
:order => "houses.name ASC, customers.last_name ASC",
:select => "customers.*, histories.job_title, houses.name"
)
My problem is this will return every history related to each customer.
if I add AND histories.finish_date IS NULL
This will prevent every history for the selected customer being returned but it will also stop customers in the intersection who have no history or a finish_date set from being returned.
Basically I need every customer in the intersection returned once with there current house name(if they have one) and then ordered by their house name and then their last name.
So is there a way of doing this?
Here is an example
customer
id last_name
1 franks
2 doors
3 greens
histories
id finish_date house_id customer_id
1 NULL 1 1
2 NULL 2 2
3 11/03/10 2 1
4 22/04/09 1 2
NULL = current house
houses
id name
1 a
2 b
Results
intersection = 1,2,3
last_name house
franks a
doors b
greens NULL
Thanks
I think you need to use outer joins.
For example, this should work:
Customer.find(:all,
:conditions =>['customers.id IN (?) and histories.finish_date is null', intersection],
:joins => 'LEFT OUTER JOIN histories ON histories.customer_id = customers.id
LEFT OUTER JOIN houses ON histories.house_id = houses.id',
:order => "houses.name ASC, customers.last_name ASC",
:select => "customers.*, histories.job_title, houses.name"
)
If you've got an association between Customer and History and between History and House you should be able to do :include => [:histories => :house] instead of the :joins option.
The only other thing is that the customers with no house will appear first in the list due to NULL being earlier in the order than a non-NULL value. You might want to try an order option like this :
:order => 'isnull(houses.name), houses.name, customers.last_name'
to achieve what you specified.
IMO it's simpler to do the sorting logic in Rails instead of the database:
customers = Customer.find(:all, :conditions => { :id => intersection }, :include => [ { :histories => :houses } ])
customers.sort_by { |c| c.last_name }
customers.sort_by do |c|
current_house = c.histories.find_by_finish_date(nil) # Returns nil if no matching record found
if current_house
current_house.name
else
''
end
end
Explanations
:conditions can take an hash { :column_name => array } which translates into your IN where-condition
:include pre-loads (eager loading) the tables if the corresponding associations exist. To put it another way: :joins creates INNER JOINs, while :include creates LEFT JOINs. Here we will left join histories and again left join houses. You could omit this :include tag, in which case rails does a new query each time you access a histories or houses property.
sort_by allows to define a custom sort criteria.
find_by_finish_date is one of rails' magic methods; it is equivalent to h.find(:conditions => {:finish_date => nil })
How to output: Just output all of them in your view. If he does not have histories, customer.histories is an empty array.

Complex Join Queries in Rails

I have 3 tables - venues, users, and updates (which have a integer for rating) - and I want to write a query that will return a list of all my venues as well as their average ratings using only the most recent update for each person, venue pair. For example, if user 1 rates venue A once at 9 am with a 4, and then rates it again at 5 pm with a 3, I only want to use the rating of 3, since it's more recent. There are also some optional conditions, such as how recent the updates must be, and if there is an array of user ids the users must be within.
Does anybody have a suggestion on what the best way to write something like this is so that it is clean and efficient? I have written the following named_scope which should do the trick, but it is pretty ugly:
named_scope :with_avg_ratings, lambda { |*params|
hash = params.first || {}
has_params = hash[:user_ids] || hash[:time_ago]
dir = hash[:dir] || 'DESC'
{
:joins => %Q{
LEFT JOIN (select user_id, venue_id, max(updated_at) as last_updated_at from updates
WHERE type = 'Review' GROUP BY user_id, venue_id) lu ON lu.venue_id = venues.id
LEFT JOIN updates ON lu.last_updated_at = updates.updated_at
AND updates.venue_id = venues.id AND updates.user_id = lu.user_id
},
:select => "venues.*, ifnull(avg(rating),0) as avg_rating",
:group => "venues.id",
:order => "avg_rating #{dir}",
:conditions => Condition.block { |c|
c.or { |a|
a.and "updates.user_id", hash[:user_ids] if hash[:user_ids]
a.and "updates.updated_at", '>', hash[:time_ago] if hash[:time_ago]
} if has_params
c.or "updates.id", 'is', nil if has_params
}
}
}
I include the last "updates.id is null" condition because I still want the venues returned even if they don't have any updates associated with them.
Thanks,
Eric
Yikes, that looks like a job for find_by_sql to me. When you're doing something that complex, I find it's best to take the job away from ActiveRecord and DIY.