group results by field on rails postgres - ruby-on-rails

I'm studying rails at the moment and i'm trying to group results of a query by field.
the query is being created by:
#orders = Order.includes(:product).where(user_id: current_user.id, paid: 0).group(:product_id)
It works fine on sqlite (dev env) but it throws a fatal error while on production (postgreSQL).
Error is:
ActionView::Template::Error (PG::GroupingError: ERROR: column "orders.id" must appear in the GROUP BY clause or be used in an aggregate function
Hmm.. what am i doing wrong here?
Thanks

If you want to group in postgres, you need to either directly select that column or you need to call an aggregate function that uses this column. Count is such an aggregate function and you can achieve that in Rails via
#product_counts = Order.where(user: current_user, paid: 0).group(:product_id).count
Postgres automatically executes Count on the column you grouped by, which is product_id in this case. This will give you a hash like this:
{ product_id => count }
For more information, see here: https://www.tutorialspoint.com/postgresql/postgresql_group_by.htm

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 (PG::GroupingError: ERROR: column must appear in the GROUP BY clause or be used in an aggregate function

In my local this query work perfect with sqlite3
def Event.most_like
select("events.*, count(like_events.event_id) as likes_count")
.joins(:like_events).group(:event_id).order("likes_count
DESC").limit(4)
end
but i got some error when deploy heroku
PG::GroupingError: ERROR: column "events.id" must appear in the GROUP BY clause or be used in an aggregate function
Can someone help me fix this?
As the message, all fields in Select clause must appear in the GROUP BY or in aggregate function. In this case, you select all fields of Event so that the fields, which includes events.id, need to satisfy above requirement.
To fix that, I suggest to change function to select only event's id and likes_count as below:
def Event.most_like
select("events.id, count(like_events.event_id) as likes_count").
joins(:like_events).
group('events.id').
order("likes_count DESC").
limit(4)
end
If you still need to get event records, you can fetch those records based on their ids.

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.

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