How do I convert my postgres query to a rails object? - ruby-on-rails

The following code below works as a Postgre query, i'm trying to call it inside my rails app and assigning it to an object: #agent_monthly_performance.
SELECT user_id AS user_id,
to_char(created_at,'Mon-YY') AS year_month,
sum(total_amount) AS amount,
sum(total_lm) AS lm
FROM agent_sales
WHERE created_at > NOW() - INTERVAL '1 year'
GROUP BY user_id, year_month"
Once that works, will I be able to call it via #agent_monthly_performance.year_month? I'm aware that i've created new columns that aren't in my schema via "AS" command. (ex. year_month).

MyModel.find_by_sql(query) will still inherit model instance. You might need to check it further

you can use ActiveRecord::Base.connection.execute to execute row sql queries in rails
#agent_monthly_performance = ActiveRecord::Base.connection.execute("SELECT user_id AS user_id, to_char(created_at,'Mon-YY') AS year_month, sum(total_amount) AS amount, sum(total_lm) AS lm FROM agent_sales WHERE created_at > NOW() - INTERVAL '1 year' GROUP BY user_id, year_month").to_a

You could do this:
sql = <<-SQL
SELECT user_id AS user_id,
to_char(created_at,'Mon-YY') AS year_month,
sum(total_amount) AS amount,
sum(total_lm) AS lm
FROM agent_sales
WHERE created_at > NOW() - INTERVAL '1 year'
GROUP BY user_id, year_month
SQL
SomeModel.find_by_sql(sql)

Related

Average per hour inverval - Rails

[Info: Ubuntu 14.02, ruby 2.2.3p110, Rails 4.2.1]
I need a rake task to fetch the average inserts per one hour range [like 00:00 to 00:59] from the DB (SQLite).
I was trying to do something like:
namespace :db do
task results: :environment do
tweet = Tweet.group('date(created_at)').group('hour(created_at)').average()
puts "#{tweet}"
end
end
But this throw me this exception
ActiveRecord::StatementInvalid: SQLite3::SQLException: misuse of aggregate function count(): SELECT AVG(count(*)) AS average_count_all, date(created_at) AS date_created_at, hour(created_at) AS hour_created_at FROM "tweet" GROUP BY date(created_at), hour(created_at)
Is there a way to get this average per hour using ActiveRecord?
You could use a nested raw SQL query:
SELECT
AVG(hourly_count) as average_count_all
FROM
(
SELECT
count(*) as hourly_count,
date_created_at,
hour_created_at
FROM
(
SELECT
date(created_at) as date_created_at,
hour(created_at) as hour_created_at
FROM
tweet
) as hours
GROUP BY
date_created_at,
hour_created_at
) as hourly_counts
This is untested, but the idea is this:
Parse out the date and hour (in hours subquery)
Get the total hourly counts (in hourly_counts subquery)
Average the hourly counts (in outer query)
I solved my problem using the query of this answer:
I edited the table name, removed where clause, changed date(from_unixtime('date')) to date(created_at) and changed hour(from_unixtime('date')) to strftime('%H', created_at) (SQLite does not have a hour() function)
So I got something like:
select the_hour,avg(the_count) from(
select date(created_at) as the_day, strftime('%H', created_at) as the_hour, count(*) as the_count
from tweet group by the_day,the_hour
) s group by the_hour

How to count records for each day in a date range

I have a voting system. Each vote has a score.
I want to be able to count how many votes were made per day?
The code below adds the scores of each vote for each day and returns a hash with the total and the date as a key:
ratings = where(created_at: start.beginning_of_day..Time.zone.now).group("created_at").select("created_at, sum(score) as total_score")
ratings.each_with_object({}) do |rating, scores|
scores[rating.created_at.to_date] = rating.total_score
end
I want to be able to count how many votes were made on each day. Any ideas?
You can do what you want using a much nicer syntax:
from = start.beginning_of_day
to = Time.zone.now
Rating.group('date(created_at)').where(created_at: from .. to).count(:score)
This will return a hash having the date(created_at) as a key and count(:score) as value, for each day.
Example:
{ "2014-01-21" => 100, "2014-01-22" => 1213 }
The corresponding SQL is:
SELECT COUNT("ratings"."score") AS count_score, date(created_at) AS date_created_at FROM "ratings" WHERE ("ratings"."created_at" BETWEEN '2014-01-21' AND '2014-01-24') GROUP BY date(created_at)
The corresponding MySQL query would be
SELECT date(created_at) as date, count(*) as count FROM `ratings` WHERE (created_at between '2014-01-21 00:00:00' AND '2014-01-24 08:16:46') GROUP BY date(created_at)
The Rails version is:
start_date = Date.parse("2014-01-21").beginning_of_day
end_time = Time.zone.now
Rating.group('date(created_at)').select('date(created_at) as date, count(*) as count').where(["created_at between ? AND ?", start_time, end_time])

Rails: must appear in the GROUP BY clause or be used in an aggregate function

I'm following along Bate's tut on graphs & charts but am getting an error.
This is the error I'm getting
PG::Error: ERROR: column "orders.created_at" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: SELECT created_at, sum(amount) as total_amount FROM "orders"...
^
: SELECT created_at, sum(amount) as total_amount FROM "orders" WHERE ("orders"."created_at" BETW
And this is the code I'm using in my Orders model
def self.total_grouped_by_day(start)
orders = where(created_at: start.beginning_of_day..Time.zone.now)#.all
orders = orders.group("date(created_at)")
orders = orders.select("date(created_at), sum(amount) as total_amount")
orders.group_by { |order| order.created_at.to_date }
end
and in my helper
def orders_chart_data
orders_by_day = Order.total_grouped_by_day(3.weeks.ago)
(3.weeks.ago.to_date..Date.today).map do |date|
{
created_at: date,
price: orders_by_day[date].first.try(:total_amount) || 0
}
end
end
I'm using Postgres (which I'm pretty sure is the main reason I'm getting this error) & Rails 3.x.x.
Why is this error occurring & how do I fix it?
Thank you in advance
In my console:
Order.where(created_at: 3.weeks.ago.beginning_of_day..Time.zone.now).group("date(created_at)").select("date(created_at), sum(amount) as total_amount")
& this is what I get
[#<Order >, #<Order >, #<Order >]
weird..
running this in my console: Order.where(created_at: 3.weeks.ago.beginning_of_day..Time.zone.now).group("date(created_at)").select("date(created_at), sum(amount) as total_amount").first.total_amount
gives me this:
Creating scope :page. Overwriting existing method Order.page.
Order Load (18.1ms) SELECT date(created_at), sum(amount) as total_amount FROM "orders" WHERE ("orders"."created_at" BETWEEN '2013-07-05 00:00:00.000000' AND '2013-07-26 23:50:38.876609') GROUP BY date(created_at) LIMIT 1
=> "10.0"
You are grouping on date(created_at) but not selecting that column. Change this line:
orders = orders.select("created_at, sum(amount) as total_amount")
to this:
orders = orders.select("date(created_at), sum(amount) as total_amount")
That will also result in a change to the next line's group_by as well.
From one of my projects, using slightly different attributes, but doing the same thing as you:
1.9.3p327 > User.group('date(created_at)').select('date(created_at), sum(id) as total_amount').first.attributes
User Load (1.2ms) SELECT date(created_at), sum(id) as total_amount FROM "users" GROUP BY date(created_at) LIMIT 1
=> {"date"=>"2011-09-27", "total_amount"=>"657"}
A cast to date would be simplest & fastest. Here the raw SQL since I am no expert with Ruby syntax (and no fan of ORMs butchering SQL in general).
SELECT created_at::date, sum(amount) AS total_amount
FROM orders
WHERE created_at ...
GROUP BY created_at::date
ORDER BY created_at::date

rails group records by dates of created_at

So I have a model that I want to retrieve records and group them by dates of the created_at field. But created_at is a datetime field, and I am only interested in the date part. So I am looking for something like a 2D array, first layer would a hash with date string as the key, second layer would be arrays with records. How should I do it?
{
"9/28/2012" => [record, record, record],
"9/29/2012" => [record, record, record],
"9/30/2012" => [record, record, record]
}
At top of the above, how should I do it if I want the above arrange be applied to all records retrieved from this model?
ActiveRecord group method will do what you need. In your case, the problem will be with using created_at which is a datetime, and your constraint to group by date. Since casting date-times to dates is database-specific, the code will need to be database-specific as well.
For MySql you can do:
Model.group("date(table_name.created_at)")
For SQLite you can do:
Model.group("strftime('%Y-%m-%d', table_name.created_at)")
And for PostgreSQL:
Model.group("table_name.created_at::date")
This code will unfortunately not be portable, but that may not matter to you. If it does, you can always build a wrapper method that selects the correct conversion syntax based on your DBMS.
On ActiveRecord you can do this:
2.6.4 :036 > User.group('created_at::date').count
(5.3ms) SELECT COUNT(*) AS count_all, created_at::date AS created_at_date FROM "users" GROUP BY created_at::date
=> {Sat, 27 Feb 2021=>5865

Rails 3.1 with PostgreSQL: GROUP BY must be used in an aggregate function

I am trying to load the latest 10 Arts grouped by the user_id and ordered by created_at. This works fine with SqlLite and MySQL, but gives an error on my new PostgreSQL database.
Art.all(:order => "created_at desc", :limit => 10, :group => "user_id")
ActiveRecord error:
Art Load (18.4ms) SELECT "arts".* FROM "arts" GROUP BY user_id ORDER BY created_at desc LIMIT 10
ActiveRecord::StatementInvalid: PGError: ERROR: column "arts.id" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: SELECT "arts".* FROM "arts" GROUP BY user_id ORDER BY crea...
Any ideas?
The sql generated by the expression is not a valid query, you are grouping by user_id and selecting lot of other fields based on that but not telling the DB how it should aggregate the other fileds. For example, if your data looks like this:
a | b
---|---
1 | 1
1 | 2
2 | 3
Now when you ask db to group by a and also return b, it doesn't know how to aggregate values 1,2. You need to tell if it needs to select min, max, average, sum or something else. Just as I was writing the answer there have been two answers which might explain all this better.
In your use case though, I think you don't want a group by on db level. As there are only 10 arts, you can group them in your application. Don't use this method with thousands of arts though:
arts = Art.all(:order => "created_at desc", :limit => 10)
grouped_arts = arts.group_by {|art| art.user_id}
# now you have a hash with following structure in grouped_arts
# {
# user_id1 => [art1, art4],
# user_id2 => [art3],
# user_id3 => [art5],
# ....
# }
EDIT: Select latest_arts, but only one art per user
Just to give you the idea of sql(have not tested it as I don't have RDBMS installed on my system)
SELECT arts.* FROM arts
WHERE (arts.user_id, arts.created_at) IN
(SELECT user_id, MAX(created_at) FROM arts
GROUP BY user_id
ORDER BY MAX(created_at) DESC
LIMIT 10)
ORDER BY created_at DESC
LIMIT 10
This solution is based on the practical assumption, that no two arts for same user can have same highest created_at, but it may well be wrong if you are importing or programitically creating bulk of arts. If assumption doesn't hold true, the sql might get more contrieved.
EDIT: Attempt to change the query to Arel:
Art.where("(arts.user_id, arts.created_at) IN
(SELECT user_id, MAX(created_at) FROM arts
GROUP BY user_id
ORDER BY MAX(created_at) DESC
LIMIT 10)").
order("created_at DESC").
page(params[:page]).
per(params[:per])
You need to select the specific columns you need
Art.select(:user_id).group(:user_id).limit(10)
It will raise error when you try to select title in the query, for example
Art.select(:user_id, :title).group(:user_id).limit(10)
column "arts.title" must appear in the GROUP BY clause or be used in an aggregate function
That is because when you try to group by user_id, the query has no idea how to handle the title in the group, because the group contains several titles.
so the exception already mention you need to appear in group by
Art.select(:user_id, :title).group(:user_id, :title).limit(10)
or be used in an aggregate function
Art.select("user_id, array_agg(title) as titles").group(:user_id).limit(10)
Take a look at this post SQLite to Postgres (Heroku) GROUP BY
PostGres is actually following the SQL standard here whilst sqlite and mysql break from the standard.
Have at look at this question - Converting MySQL select to PostgreSQL. Postgres won't allow a column to be listed in the select statement that isn't in the group by clause.

Resources