How can I convert the following into an active record query:
SELECT reviews.style_id, AVG("col1"), AVG("col2")
FROM reviews, audios
WHERE reviews.consumer_id = audios.consumer_id
GROUP BY style_id;
col1 and col2 belong to the audios table, but they are uniquely named (no similar column name in reviews), so there is no ambiguity error.
I am using PostgreSQL.
If you have an association between Review and Audio then something like this:
revs = Review.joins(:audios)
.group('style_id')
.select('style_id, avg(col1) as avg_col1, avg(col2) as avg_col2')
That will give a list of Review instances in revs and those instances will have extra avg_col1 and avg_col2 methods for accessing the averages as well as the usual style/style_id methods but the other column accessor methods that Review would normally offer will raise exceptions.
If you don't have the associations set up then you can do the JOIN manually:
revs = Review.joins('join audios on reviews.consumer_id = audios.consumer_id')
.group('style_id')
.select('style_id, avg(col1) as avg_col1, avg(col2) as avg_col2')
If all you need is just the raw data without all the ActiveRecord wrapping and overhead, then you could execute the raw SQL and hashify it by hand using select_rows:
Review.connection.select_rows(%q{
select r.style_id, avg(a.col1), avg(a.col2')
from reviews r
join audios a on r.consumer_id = a.consumer_id
group by r.style_id
}).map do
{ :style_id => r.shift, :avg_col1 => r.shift.to_f, :avg_col2 => r.shift.to_f }
end
That would give you an Array of Hashes. You could even simplify that approach using Struct to create simple data wrapper classes:
c = Struct.new(:style_id, :avg_col1, :avg_col2)
revs = Review.connection.select_rows(%q{...}).map do |r|
c.new(r.shift, r.shift.to_f, r.shift.to_f)
end
PS: Don't use implicit join conditions your SQL, that's just a quick and easy way to produce cross products, use explicit join conditions:
SELECT ...
FROM reviews JOIN audios ON reviews.consumer_id = audios.consumer_id
GROUP BY style_id
Related
I have a HomeMaker and PerDateUnavailability models. HomeMaker has_many per_date_unavailabilities. I want all home makers who don't have a record in per_date_unavailabilities and home makers who have record but not when the per_date_unavailabilties.unavailable_date = somedate
I usually do the first part when I want HomeMakers without a PerDateUnavailability record using HomeMaker.includes(:per_date_unavailabilities).where(per_date_unavailabilities: {id: nil})
and the second part of the condition using HomeMaker.joins(:per_date_unavailabilities).where.not(per_date_unavailabilities: {unavailable_date: Date.today})
How do I mix these?
The sql you need is somewhat like this:
select a.*
from home_makers a left join per_date_unavailabilities b
on a.id = b.home_maker_id
where b.home_maker_id is NULL
or b.unavailable_date IS NOT ?;
Expressing the or clause is a little tough in ActiveRecord, and its best we don't fight it. (Edited after OP's comment)
HomeMaker.joins("LEFT JOIN per_date_unavailabilities on per_date_unavailabilities.home_maker_id = home_makers.id")
.where("per_date_unavailabilities.home_maker_id IS NULL OR per_date_unavailabilities.unavailable_date != ?", somedate)
I've a model StockUpdate which keeps track of stocks for every product for a store. Table attributes are: :product_id, :stock, :store_id. I was trying to find out last entry for every product for a given store. According to that I build my query in PGAdmin which is given below and it's working fine. I'm new in Rails and I don't know how to represent it in Model. Please help.
SELECT a.*
FROM stock_updates a
INNER JOIN
(
SELECT product_id, MAX(id) max_id
FROM stock_updates where store_id = 9 and stock > 0
GROUP BY product_id
) b ON a.product_id = b.product_id AND
a.id = b.max_id
I does not clearly understand what you want to do, but I think you can do something like this:
class StockUpdate < ActiveRecord::Base
scope :a_good_name, -> { joins(:product).where('store_id = ? and stock > ?', 9, 0) }
end
You can all call StoclUpdate.a_good_name.explain to check the generated sql
What you need is really simple and can be easily accomplished with 2 queries. Otherwise it becomes very complicated in a single query (it's still doable though):
store_ids = [0, 9]
latest_stock_update_ids = StockUpdate.
where(store_id: store_ids).
group(:product_id).
maximum(:id).
values
StockUpdate.where(id: latest_stock_update_ids)
Two queries, without any joins necessary. The same could be possible with a single query too. But like your original code, it would include subqueries.
Something like this should work:
StockUpdate.
where(store_id: store_ids).
where("stock_updates.id = (
SELECT MAX(su.id) FROM stock_updates AS su WHERE (
su.product_id = stock_updates.product_id
)
)
")
Or perhaps:
StockUpdate.where("id IN (
SELECT MAX(su.id) FROM stock_updates AS su GROUP BY su.product_id
)")
And to answer your original question, you can manually specify a joins like so:
Model1.joins("INNER JOINS #{Model2.table_name} ON #{conditions}")
# That INNER JOINS can also be LEFT OUTER JOIN, etc.
What I want to do is to join table and sum 3 columns.
self.document_products.joins("JOIN products ON products.id = document_products.product_id").group("products.tax_id").select("sum(a), sum(b), sum(c)")
Gives me
#<ActiveRecord::Relation [#<DocumentProduct id: nil>]>
Something like that works:
self.document_products.joins("JOIN products ON products.id = document_products.product_id").group("products.tax_id").sum("a")
But I want to have 3 sums. I can`t do sum("a, b, c"). Where is the problem?
So, the code is building a SQL query using the ActiveRecord chained method syntax. It's possible to use .to_sql as the final part of most such chains (basically, as long as it's still an ActiveRecord object, rather than having been converted to an Array, for example) to see the SQL generated, or indeed inspecting the log, if it's on. Considering the common part of the chain:
self.document_products.joins("JOIN products ON products.id = document_products.product_id").group("products.tax_id")
This generates something like (might not be exact, because I'm guessing a little about your application):
SELECT "document_products".* FROM "document_products" JOIN products ON products.id = document_products.product_id WHERE "document_products"."document_id" = 1497 GROUP BY products.tax_id
The two final methods you list are very different; select selects which columns in the query to return, whereas sum is an aggregate function which expects a single value to be returned in each case. Considering the select, we get something like the following generated:
SELECT SUM(products.a), SUM(products.b), SUM(products.c) FROM "document_products" JOIN products ON products.id = document_products.product_id WHERE "document_products"."document_id" = 1497 GROUP BY products.tax_id
When this query is interpreted, the expected data cannot be found, leading to the problem described. Ensuring that the GROUP BY clause is included in the SELECT part, however, yields the necessary information. Try something like this:
self.document_products.joins("JOIN products ON products.id = document_products.product_id").group("products.tax_id").select("products.tax_id, sum(a), sum(b), sum(c)")
This generates SQL something like:
SELECT products.tax_id, SUM(products.a), SUM(products.b), SUM(products.c) FROM "document_products" JOIN products ON products.id = document_products.product_id WHERE "document_products"."document_id" = 1497 GROUP BY products.tax_id
This appears to return the necessary information, and is, I think, what you're looking for (or close to it).
Let say a book model HABTM categories, for an example book A has categories "CA" & "CB". How can i retrieve book A if I query using "CA" & "CB" only. I know about the .where("category_id in (1,2)") but it uses OR operation. I need something like AND operation.
Edited
And also able to get books from category CA only. And how to include query criteria such as .where("book.p_year = 2012")
ca = Category.find_by_name('CA')
cb = Category.find_by_name('CB')
Book.where(:id => (ca.book_ids & cb.book_ids)) # & returns elements common to both arrays.
Otherwise you'd need to abuse the join table directly in SQL, group the results by book_id, count them, and only return rows where the count is at least equal to the number of categories... something like this (but I'm sure it's wrong so double check the syntax if you go this route. Also not sure it would be any faster than the above):
SELECT book_id, count(*) as c from books_categories where category_id IN (1,2) group by book_id having count(*) >= 2;
I have a relationship between two models, Registers and Competitions. I have a very complicated dynamic query that is being built and if the conditions are right I need to limit Registration records to only those where it's Competition parent meets a certain criteria. In order to do this without select from the Competition table I was thinking of something along the lines of...
Register.where("competition_id in ?", Competition.where("...").collect {|i| i.id})
Which produces this SQL:
SELECT "registers".* FROM "registers" WHERE (competition_id in 1,2,3,4...)
I don't think PostgreSQL liked the fact that the in parameters aren't surrounded by parenthesis. How can I compare the Register foreign key to a list of competition ids?
you can make it a bit shorter and skip the collect (this worked for me in 3.2.3).
Register.where(competition_id: Competition.where("..."))
this will result in the following sql:
SELECT "registers".* FROM "registers" WHERE "registers"."competition_id" IN (SELECT "competitions"."id" FROM "competitions" WHERE "...")
Try this instead:
competitions = Competition.where("...").collect {|i| i.id}
Register.where(:competition_id => competitions)