Group records by created_at in ActiveRecord - ruby-on-rails

I'm trying to find how many records were created on a given day, to get something like this:
User uploaded 5 objects two days ago
User uploaded 12 objects 21 days ago
I've tried:
Flight.group("created_at::date")
but get the error:
ActiveRecord::StatementInvalid: PG::GroupingError: ERROR: column "flights.id" must appear in the GROUP BY clause or be used in an aggregate function
but when I try adding the id either directly to the group field or by adding another .group I end up grouping on both the id and created_at, which obviously doesn't group anything since there would only be one instance of each match.
What am I missing here?

If you're trying to count things then count them:
Flight.group('created_at::date').count # PostgreSQL-only
Flight.group('date(created_at)').count # PostgreSQL and AFAIK MySQL
Flight.group('cast(created_at as date)').count # Standard SQL
That will give you a hash with dates as keys and counts as values. Otherwise, the database won't know which values from the rows in the group should be used to represent the group. If you add the count aggregate function then the ambiguity is gone and the database knows what you want it to do.

Related

How do I translate my SQLite3 query to postgreSQL?

I am trying to order all the recipes in my database by the number of likes they have received. Likes are polymorphic and belong to :likeable while a recipe has many likes.
My query works for SQLite3, but when I upload to Heroku using PostgreSQL it seems to break things.
function is as follows:
Recipe.select('*').joins(:likes).group('recipes.id').order('COUNT(likes.likeable_id)')
And the error that Heroku gives me when I try to run the website:
ActionView::Template::Error (PG::GroupingError: ERROR: column "likes.id" must appear in the GROUP BY clause or be used in an aggregate function
Everything compiles, but the homepage uses that scope function so I get a server error right away.
You need to explicitly select recipies.*:
Recipe.select(
Recipe.arel_table[:*],
Likes.arel_table[:*].count.as('likes_count')
)
.joins(:likes)
.group(:id)
.order(:likes_count)
Selecting the count is really optional - you can skip .select entirely and just fetch the aggregate in the order clause:
Recipe.joins(:likes)
.group(:id)
.order(Likes.arel_table[:*].count)
You cannot select * from grouping by.
for most SQL-dabases (Postgres, newer Mysql, ...) you can only use SELET columns in a GROUP BY:
columns you've grouped by, and that are transient by the grouped column (e.g. grouping recipes.id can also select recipes.title)
And aggregated columns (count, sum, max)
Try:
Recipe.select('recipies.*').joins(:likes).group(:id).order('COUNT(likes.likeable_id)')

Rails, Count of Users Grouped by created_at day - PostgreSQL

I am using Rails 5, PostgreSQL. I need to get count of users grouped by created_at day, using postgres DATE_TRUNC.
The conditions are users created within a date range and have orders within the same date range.
Below is my code which result in an AmbiguousFunction error
Spree::User.joins(:orders)
.where(spree_orders: { completed_at: params[:start_date]..params[:end_date] })
.order("DATE_TRUNC('day', 'created_at')")
.group("DATE_TRUNC('day', 'created_at')")
.count
The params of start_date and end_date are as follow:
params[:end_date] = Time.current.end_of_day
params[:start_date] = (Time.current - 200.days).beginning_of_day
I get the following error
ActiveRecord::StatementInvalid: PG::AmbiguousFunction: ERROR: function date_trunc(unknown, unknown) is not unique
and even when I explicitly write spree_users.created_at I get the same error.
Is there a better way to achieve the required or a solution for this error?
ActiveRecord::StatementInvalid: PG::AmbiguousFunction
This error occurs when our query contains a column name, which may belong to more than one table. For example, we have two tables, User and Company, and both of them have a column called name. Now the following query would raise an error similar to the one that you are facing:
User.joins(:companies).where("name = ABC")
This happens because we do not specify which table to search the name in. Hence, ActiveRecord gets confused and cannot create a unique query.
In the case mentioned above, the error can be resolved simply by prepending spree_users to the created_at column name used in the order and group queries:
Spree::User.joins(:orders)
.where(spree_orders: { completed_at: params[:start_date]..params[:end_date] })
.order("DATE_TRUNC('day', 'spree_users.created_at')")
.group("DATE_TRUNC('day', 'spree_users.created_at')")
.count
I think you can use date function from sql to get date of timestamp field, and since table User and SpreeOrder has created_at field, you should inform table name (spree_orders.created_at)
Spree::User.joins(:orders)
.where(spree_orders: { completed_at: params[:start_date]..params[:end_date]})
.order("date(spree_orders.created_at)")
.group("date(spree_orders.created_at)")
.count

Rails: Omit order by clause when using ActiveRecord .first query?

I'm having a problem with a .first query in Rails 4 ActiveRecord. New behavior in Rails 4 is to add an order by the id field so that all db systems will output the same order.
So this...
Foo.where(bar: baz).first
Will give the query...
select foos.* from foos order by foos.id asc limit 1
The problem I am having is my select contains two sum fields. With the order by id thrown in the query automatically, I'm getting an error that the id field must appear in the group by clause. The error is right, no need for the id field if I want the output to be the sum of these two fields.
Here is an example that is not working...
baz = Foo.find(77).fooviews.select("sum(number_of_foos) as total_number_of_foos, sum(number_of_bars) as total_number_of_bars").reorder('').first
Here is the error...
ActiveRecord::StatementInvalid: PG::GroupingError: ERROR: column "foos.id" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: ...bars FROM "fooviews" ORDER BY "...
Since the select is an aggregate expression, there is no need for the order by id, but AR is throwing it in automatically.
I found that I can add a reorder('') on to the end before the .first and that removes the order by id, but is that the right way to fix this?
Thank you
[UPDATE] What I neglected to mention is that I'm converting a large Rails 3 project to Rails 4. So the output from the Rails 3 is an AR object. If possible, the I would like the solution to keep in that format so that there is less code to change in the conversion.
You will want to use take:
The take method retrieves a record without any implicit ordering.
For example:
baz = Foo.find(77).fooviews.select("sum(number_of_foos) as total_number_of_foos, sum(number_of_bars) as total_number_of_bars").take
The commit message here indicates that this was a replacement for the old first behavior.

Transform query that works in SQLite into something that works in Postgres

Message.order("created_at DESC").where(user_id: current_user.id).group(:sender_id, :receiver_id).count
Works with my dev environment SQLite3 for a Rails 3.2 app but fails when pushed to Heroku using Postgres with this error:
PG::GroupingError: ERROR: column "messages.created_at" must appear in the GROUP BY clause or be used in an aggregate function
It seems it wants :created_at in the query. Unable to come up with anything. Any ideas?
Thanks
PostgreSQL can't figure out how to order by your created_at.
Suppose that you find 2 groups of (:sender_id, :receiver_id), for instance [1, 2] and
[1, 3].
Now suppose that in the first group you have 2 messages, one from 1 day ago and one from 1 minute ago. And let's say you have 1 message in the second group from 12 hours ago.
Then ORDER BY created_at DESC doesn't make any sense: do you take the message from 1 day ago as the created_at of the first group (hence the first group appears after the second one), or the one from 1 minute ago (in which case the first group now appears first)?
That's why PostgreSQL says that you need to either have created_at in the GROUP BY (in which case you now have 3 different group, as the first one is now split in two), or you need to use an aggregate function to transform multiple values of created_at into a single one.
This will run (I don't know what you expect the results to be, you might not want to use MAX(created_at) ! You can find a list of PostgreSQL's aggregate functions here) :
Message.order("MAX(created_at) DESC")
.where(user_id: current_user.id)
.group(:sender_id, :receiver_id)
.count

Rails Postgres Error GROUP BY clause or be used in an aggregate function

In SQLite (development) I don't have any errors, but in production with Postgres I get the following error. I don't really understand the error.
PG::Error: ERROR: column "commits.updated_at" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: ...mmits"."user_id" = 1 GROUP BY mission_id ORDER BY updated_at...
^
: SELECT COUNT(*) AS count_all, mission_id AS mission_id FROM "commits" WHERE "commits"."user_id" = 1 GROUP BY mission_id ORDER BY updated_at DESC
My controller method:
def show
#user = User.find(params[:id])
#commits = #user.commits.order("updated_at DESC").page(params[:page]).per(25)
#missions_commits = #commits.group("mission_id").count.length
end
UPDATE:
So i digged further into this PostgreSQL specific annoyance and I am surprised that this exception is not mentioned in the Ruby on Rails Guide.
I am using psql (PostgreSQL) 9.1.11
So from what I understand, I need to specify which column that should be used whenever you use the GROUP_BY clause. I thought using SELECT would help, which can be annoying if you need to SELECT a lot of columns.
Interesting discussion here
Anyways, when I look at the error, everytime the cursor is pointed to updated_at. In the SQL query, rails will always ORDER BY updated_at. So I have tried this horrible query:
#commits.group("mission_id, date(updated_at)")
.select("date(updated_at), count(mission_id)")
.having("count(mission_id) > 0")
.order("count(mission_id)").length
which gives me the following SQL
SELECT date(updated_at), count(mission_id)
FROM "commits"
WHERE "commits"."user_id" = 1
GROUP BY mission_id, date(updated_at)
HAVING count(mission_id) > 0
ORDER BY updated_at DESC, count(mission_id)
LIMIT 25 OFFSET 0
the error is the same.
Note that no matter what it will ORDER BY updated_at, even if I wanted to order by something else.
Also I don't want to group the records by updated_at just by mission_id.
This PostgreSQL error is just misleading and has little explanation to solving it. I have tried many formulas from the stackoverflow sidebar, nothing works and always the same error.
UPDATE 2:
So I got it to work, but it needs to group the updated_at because of the automatic ORDER BY updated_at. How do I count only by mission_id?
#missions_commits = #commits.group("mission_id, updated_at").count("mission_id").size
I guest you want to show general number of distinct Missions related with Commits, anyway it won't be number on page.
Try this:
#commits = #user.commits.order("updated_at DESC").page(params[:page]).per(25)
#missions_commits = #user.commits.distinct.count(:mission_id)
However if you want to get the number of distinct Missions on page I suppose it should be:
#missions_commits = #commits.collect(&:mission_id).uniq.count
Update
In Rails 3, distinct did not exist, but pure SQL counting should be used this way:
#missions_commits = #user.commits.count(:mission_id, distinct: true)
See the docs for PostgreSQL GROUP BY here:
http://www.postgresql.org/docs/9.3/interactive/sql-select.html#SQL-GROUPBY
Basically, unlike Sqlite (and MySQL) postgres requires that any columns selected or ordered on must appear in an aggregate function or the group by clause.
If you think it through, you'll see that this actually makes sense. Sqlite/MySQL cheat under the hood and silently drop those fields (not sure that's technically what happens).
Or thinking about it another way if you are grouping by a field, what's the point of ordering it? How would that even make sense unless you also had an aggregate function on the ordered field?

Resources