Group by query in Rails 3 - ruby-on-rails-3

I have the (working) code
counts = Registration.select('regulator_id').group('regulator_id').count
#regulators.each {|r| r.registration_count=counts[r.id]}
which allows me to show how many Registrations there are per Regulator. The query it generates is:
SELECT COUNT("registrations"."regulator_id") AS count_regulator_id, regulator_id AS regulator_id FROM "registrations" GROUP BY regulator_id
I would like to restrict my count to those registrations from the last scrape only, with a query like:
select
regulator_id, count(*)
from
registrations inner join
regulators on regulators.id = registrations.regulator_id
where
registrations.updated_at > regulators.last_scrape_start
group by
regulator_id
but I cannot get the syntax to work either using arel or find_by_sql. I am sure this is simple when you know the answer but it has cost me ages so far.
Thanks in advance.

Just add 'joins' and 'where'
Registration.joins(:regulator).where('registrations.updated_at > regulators.last_scrape_start').select('regulators.id').group('regulators.id').count

Related

how to get total sum of two tables and group responses with eloquent Laravel?

I am trying to create a query using eloquent where I need to know the total points summed by id, username and session_id.
but the result I get is not correct.
my code
$query = DB::table('advisors')
->select('advisors.id','advisors.nombre_comercial','session_details.session_id',
DB::raw('SUM(session_details.spent_points + template_sales.price_points) AS suma_total'))
->join('consultancy_requests','advisors.id','=','consultancy_requests.advisor_id')
->whereBetween('consultancy_requests.created_at',[$from,$to])
->join('receipts','consultancy_requests.id','=','receipts.session_id')
->where('receipts.status',NULL)
->whereBetween('receipts.created_at',[$from,$to])
->join('session_details','consultancy_requests.consultancy_id','=','session_details.session_id')
->whereBetween('session_details.created_at',[$from,$to])
->join('template_sales','session_details.session_id','=','template_sales.session_id')
->whereBetween('template_sales.created_at',[$from,$to])
->groupBy('advisors.id','advisors.nombre_comercial','session_details.session_id')
->get();
code responses
session_details table
template_sales table
this is the correct answer i want to get.
There are a couple of mistakes in your query that I noticed. For instance you don't need to SUM (session_details.spent_points + template_sales.price_points) because this is already performing the addition.
Instead of pointing out all these, let's break your problem down into smaller pieces; when query seems complicated it would be a good idea to break it down for better understanding. There seems to be a couple of tables but I will base my answer on the two tables provided, and that should give you a starting point.
Essentially, what you want is,
Get the sum of spent_points per session_id; so you need to group by session_id and sum(spent_points)
$sumSpentPointsQuery = DB::table('session_details')
->select('session_id', DB::raw('SUM(spent_points) as sum_spent_points'))
->groupBy('session_id');
Get the sum of price_points per session_id; so you need to group by session_id and sum(price_points)
$sumPricePointsQuery = DB::table('template_sales')
->select('session_id', DB::raw('SUM(price_points) as sum_price_points'))
->groupBy('session_id');
Now we need to get the addition of sum_spent_points and sum_price_points. This time our tables would be the results we got from the sub queries. So we can work with Laravel's fromSub and joinSub to get the result we want.
DB::query()
->select('ssp.session_id as session_id', DB::raw('sum_spent_points + sum_price_points as suma_total') )
->fromSub($sumSpentPointsQuery, 'ssp')
->joinSub($sumPricePointsQuery, 'spp', function($join){
$join->on('ssp.session_id', '=', 'spp.session_id');
})->get();
This query should produce the sql that represents this:
select ssp.session_id as session_id, (sum_spent_points + sum_price_points) as suma_total
from
(select session_id, sum(spent_points) as sum_spent_points
from session_details group by session_id) ssp
inner join
(select session_id, sum(price_points) as sum_price_points
from template_sales group by session_id) spp
on ssp.session_id = spp.session_id ;
Hope this kicks you in the right direction.

Use includes() instead of joins() with Rails

I have two Models invoice and payevents. The relationship between them is invoice has_many payevents.
I'm using the following query to get all bills that have been fully paid.:
Invoice.joins(:payevents).group("invoice.id").having("sum(payevents.amount) >= invoice.amount")
This query works fine. However, it is not optimal since the result doesn't include the payevents. I tried to use includes instead of joins but it doesn't work.
Invoice.includes(:payevents).group("invoice.id").having("sum(payevents.amount) >= invoice.amount")
The error is
ActiveRecord::StatementInvalid: PG::GroupingError: ERROR: column "payevents.id" must appear in the GROUP BY clause or be used in an aggregate function
Any ideas what is wrong here?
I use PostgreSQL and Rails 4.1
If I correctly understand you - you should use subquery. Like this:
subquery = Invoice.joins(:payevents)
.group("invoice.id")
.having("sum(payevents.amount) >= invoice.amount")
.select("invoice.id id, sum(payevents.amount) amount").to_sql
query = Invoice.includes(:payevents).joins("JOIN (#{subquery}) subquery ON invoice.id = subquery.id")
So, you'll have Invoice, aggregated amount, filtered result by inner join of subquery and all payevents fields.
The much simpler solution is to explicitly use preload
Invoice.joins(:payevents).group("invoice.id").having("sum(payevents.amount) >= invoice.amount").preload(:payevents)

Rails query - latest Post for each day

A User has_many Posts. I want to retrieve the latest Post for each day (using created_at), ignoring other posts that may have been written earlier. Another way to pose this question might to ask for a each top salary earning employee by department - same thing I think.
How do I write this query in Rails (4.0 preferably)? I think it has something to do with group and maximum but I can't seem to get it. Is there a way to do it without resorting to SQL?
To clarify, what I'd like returned is an array of post objects that are the last ones written on their respective date.
Thanks!
Something like this. You can convert this to AREL syntax as needed:
SELECT posts.created_at, *
FROM posts
INNER JOIN (
SELECT MAX(created_at) AS max_order_date FROM posts
GROUP BY DATE(posts.created_at)
) AS last_postings ON last_postings.max_order_date = posts.created_at
ORDER BY DATE(created_at) DESC
LIMIT 10
AREL syntax might be:
join_sql = <<-SQL
INNER JOIN (
SELECT MAX(created_at) AS max_order_date FROM posts
GROUP BY DATE(posts.created_at)
) AS last_postings ON last_postings.max_order_date = posts.created_at
SQL
Post.joins(join_sql).order('DATE(created_at) DESC')
Remove the LIMIT as it suits you.
It's not very clean, but this works in Rails 3 (taken from a Book model in my case) using PostgreSQL syntax for truncating the created_at to the date:
max_created_at_list = Book.select("max(created_at) as created_at").group("date_trunc('day',created_at)")
last_books = Book.where(:created_at => max_created_at_list)
... or just:
Book.where(:created_at =>Book.select("max(created_at) as created_at").group("date_trunc('day',created_at)"))
You'd want an index on created_at for large data sets, and either created_at to be constrained to not null at the database level or an "is not null" predicate if the RDBMS you use does not index nulls (eg. Oracle)
Try this
Post.select("user_id, max(created_at) as created_at").group(:user_id)

PGError: ERROR: column “recipes.id” must appear in the GROUP BY clause or be used in an aggregate function

I have this SQL query in my DB which is causing a problem with PostgreSQL on heroku, Causing the page not to load with the above error in the heroku logs. I am using postgreSQL 9.1.6 so previous bugs have apparently been fixed
def self.top_countries
joins(:recipes).
select('countries.*, count(*) AS recipes_count').
group('countries.id').
order('recipes_count DESC')
end
I am unsure on how to refactor this so that it will work.Could anyone advise please?
Thank You
def self.top_countries
joins(:recipes).
select('countries.id, count(*) AS recipes_count').
group('countries.id').
order('recipes_count DESC')
This generates the SQL
select countries.id, count(*) AS recipes_count
from countries
join recipes on countries.id = recipes.country_id
group by countries.id
order by recipes_count
You'll notice that you only have 2 columns in the SELECT.
Not being a Heroku expert, I suspect you can get it to work by explicitly listing all column that you need from countries, and grouping by the full column list i.e.
def self.top_countries
joins(:recipes).
select('countries.id, countries.name, countries.other, count(*) AS recipes_count').
group('countries.id, countries.name, countries.other').
order('recipes_count DESC')
There might be a more concise way to join the original answer (top part) with another join to top_countries on countries.id to get the rest of the columns after the group by.

Joining tables, counting, and group to return a Model

So I've got a SQL query I'd like to duplicate in rails:
select g.*
from gamebox_favorites f
inner join gameboxes g on f.gamebox_id = g.id
group by f.gamebox_id
order by count(f.gamebox_id) desc;
I've been reading over the rails Active Record Query Interface site, but can't quite seem to put this together. I'd like the query to return a collection of Gamebox records, sorted by the number of 'favorites' a gamebox has. What is the cleanest way to do this in rails?
I believe this will work (works on a similarly structured database locally), though I'm not sure I have the proper models in the proper spots for what you're trying to do, so you might need to move a coule things around:
Gamebox.joins(:gamebox_favorites).
group('"gamebox_favorites"."gamebox_id"').
order('count("gamebox_favorites"."gamebox_id")')
On the console, this should compile to (in the case of PostgreSQL on the back end):
SELECT "gameboxes".* FROM "gamebox_favorites"
INNER JOIN "gamebox_favorites"
ON "gamebox_favorites"."gamebox_id" = "gamebox"."id"
GROUP BY "gamebox_favorites"."gamebox_id"
ORDER BY count("gamebox_favorites"."gamebox_id")
...and I'm guessing that you don't want do just wrap it in a find_by_sql call, such as:
Gamebox.find_by_sql("select g.* from gamebox_favorites f
inner join gameboxes g
on f.gamebox_id = g.id
group by f.gamebox_id
order by count(f.gamebox_id) desc")