I am trying to get the employees of a business with the highest points in placements for each month.
The schema looks like this:
class Business < ActiveRecord::Base
has_many :employees
has_many :placements, through: :employees
class Employee < ActiveRecord::Base
attr_accessible :name
belongs_to :business
has_many :placements
class Placement < ActiveRecord::Base
attr_accessible :month, :employee_id, :points
belongs_to :employee
I have tried the following:
#business.placements.includes(:employee).group(:month).order('points DESC').map(&:employee)
But I get a PostgreSQL group_by error:
: SELECT "placements".* FROM "placements" INNER JOIN "employees" ON "placements"."employee_id" = "employees"."id" WHERE "employees"."business_id" = 43 GROUP BY month ORDER BY points DESC
ActiveRecord::StatementInvalid: PG::Error: ERROR: column "placements.id" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: SELECT "placements".* FROM "placements" INNER JOIN "employee...
Any help will be appreciated.
When you group any columns you select either need to be in the group_by or have some aggregation function on them. By default AR pulls back all the columns so you probably want to restrict that with a .select
Related
I am trying to order my active record query by a specific table column but with the standard Query.order(:id) format rails thinks the column is in a different table.
This is my active record query:
#course = Course.select('*').joins(:course_metadata, :courseContent).where(course_id: params[:id]).order(:content_order)
Here are my models:
class Course < ActiveRecord::Base
has_one :course_metadata
has_many :xrefCourseContent, foreign_key: 'course_id'
has_many :courseContent, through: :xrefCourseContent, foreign_key: 'course_guid'
end
class CourseMetadata < ActiveRecord::Base
self.table_name = 'course_metadata'
belongs_to :course
end
class CourseContent < ActiveRecord::Base
self.table_name = 'courseContent'
has_many :xrefCourseContent, foreign_key: 'content_id', primary_key: 'content_id'
has_many :course, through: :xrefCourseContent, foreign_key: 'content_id',
end
class XrefCourseContent < ActiveRecord::Base
self.table_name = 'xrefCourseContent'
belongs_to :course, primary_key: 'course_id', foreign_key: 'course_guid'
belongs_to :courseContent, primary_key: 'content_id', foreign_key: 'content_guid'
end
The query connects courses to course content through the xref table.
SELECT * FROM [courses]
INNER JOIN [course_metadata] ON [course_metadata].[course_id] = [courses].[course_id]
INNER JOIN [xrefCourseContent] ON [xrefCourseContent].[course_id] = [courses].[course_id]
INNER JOIN [courseContent] ON [courseContent].[content_id] = [xrefCourseContent].[content_id]
WHERE [courses].[course_id] = #0
ORDER BY [courses].[content_order]
This is the sql query that shows up in the error message and as you can see, it thinks the content_order column is in the courses table when in fact it is in the xrefCourseContent table.
I'm new to rails and am still trying to wrap my mind around the whole Active Record system so forgive me if some of the code in my models is redundant or unnecessary, but feel free to point out anything that could be improved.
It should be
#course = Course.select('*').joins(:course_metadata, :courseContent)
.where(course_id: params[:id])
.order("xrefCourseContents.content_order")
You need to specify the table from which you will find the content_order column.
My models:
metro_station.rb
class MetroStation < ActiveRecord::Base
belongs_to :metro_line
has_one :city, through: :metro_line, autosave: false
end
metro_line.rb`
class MetroLine < ActiveRecord::Base
belongs_to :city
has_many :metro_stations
end
city.rb
class City < ActiveRecord::Base
has_many :metro_lines
end
When I run:
MetroStation.where(city: City.first)
I get
PG::UndefinedColumn: ERROR: column metro_stations.city_id does not exist
: SELECT "metro_stations".* FROM "metro_stations" WHERE "metro_stations"."city_id" = 1
(pry) output error: #<ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column metro_stations.city_id does not exist
LINE 1: ...CT "metro_stations".* FROM "metro_stations" WHERE "metro_sta...
While this query works:
MetroStation.joins(:metro_line).where(metro_lines: {city_id: 1})
To find out why your first approach doesn't work type in
MetroStation.where(city: City.first).to_sql
You should see something like
... WHERE metro_stations.city_id = 1
forming part of the query. The MetroStation model simply doesn't have the city_id attribute, as a result your first approach forms an invalid SQL statement.
The join works since it is filtering on the MetroLine table which has the relationship to the City model in form of the city_id field.
Nothing wrong with your models it is just the way Rails generates the SQL statements which in the world of SQL makes sense.
A has_many relationship on City to MetroStation through MetroLine delivers the desired results for your question (which metro stations are in a city).
Example
class City < ActiveRecord::Base
has_many :metro_lines
has_many :metro_stations, through: :metro_lines
end
class MetroLine < ActiveRecord::Base
belongs_to :city
has_many :metro_stations
end
class MetroStation < ActiveRecord::Base
belongs_to :metro_line
has_one :city, through: :metro_line
end
# Return all metro stations for a city
# * assuming City has name 'name' field
london_stations = City.find_by_name('London').metro_stations
Hope this helps!
City should also:
has_many :metro_stations, :through => :metro_lines
And then you write:
City.first.metro_stations
I have a deeply nested layout as follows:
Contract -> has many Packages -> has many Services
Payment -> belongs_to Invoice -> belongs_to Contract
class Contract < ActiveRecord::Base
has_many :invoices
has_many :contract_packages
has_many :packages, through: :contract_packages
end
class Package < ActiveRecord::Base
has_many :services
has_many :contract_packages
has_many :contracts, through: :contract_packages
end
class ContractPackage < ActiveRecord::Base
belongs_to :contract
belongs_to :package
end
class Service < ActiveRecord::Base
belongs_to :package
end
class Invoice < ActiveRecord::Base
belongs_to :contract
end
class Payment < ActiveRecord::Base
belongs_to :invoice
end
I want to find what Services, and how many times were invoiced in a certain period of time, based on payment date. Invoice date may not be the same as payment date.
I know hot to do it by pure SQL, and it works, but I am stuck if I want to do it the rails way.
Any ideas?
Edit:
The pure sql query:
select s.[name], count(*), s.[price] from payments p
left join invoices i on p.invoice_id=i.id
left join contracts c on i.[contract_id]=c.id
left join contract_packages cp on cp.contract_id=c.id
left join packages pk on cp.[package_id]=pk.id
left join services s on s.package_id=pk.id
where ... conditions
group by s.id
order by s.id asc
In my original question I left out, for brevity, a join table, because a package may belong to many contracts. The sql here includes the join table. I updated the models also.
Doing joins in in activerecord is quite straight forward as long as you have defined the relationships in your models. You just pass a hash to joins and it figures out what keys to use. Adding the where conditions can be done in similar fashion.
I noticed that there was no has_many :payments in your invoice is this by design? In that case why?
The select clause I have written will give all Service objects created with this query an extra method count where you will find your value.
Service.select('*, count(*) as count')
.joins({
package: {
contract: {
invoices: :payment
}
}
})
.where(conditions_hash)
.group('services.id asc')
.order(:id)
I am trying to do the following:
#business.placements.includes(:employee).group(:month). order('points DESC').map(&:employee)
But I keep getting the following error (whenever I use group_by I run into problems because of my lack of postgresql knowledge):
ActiveRecord::StatementInvalid (PG::Error: ERROR: column "placements.id" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: SELECT "placements".* FROM "placements" INNER JOIN "employee...
^
: SELECT "placements".* FROM "placements" INNER JOIN "employees" ON "placements"."employee_id" = "employees"."id" WHERE "employees"."business_id" = 43 GROUP BY month ORDER BY points DESC)
My schema looks as follows (if any help):
class Business < ActiveRecord::Base
has_many :employees, dependent: :destroy
has_many :placements, through: :employees
class Employee < ActiveRecord::Base
belongs_to :business
has_many :placements
class Placement < ActiveRecord::Base
belongs_to :employee
class Vote < ActiveRecord::Base
belongs_to :employee
I am using rails and graphing some data. I use the following:
<%= column_chart User.includes(:levels).group(:email).sum(:score) %>
How do i make this group command sort the returned array by score from highest to lowest?
My models are arranged as follows
class User < ActiveRecord::Base
has_many :games
contains id, email
end
class Game < ActiveRecord::Base
has_many :levels
belongs_to :user
#contains id, user_id, name
accepts_nested_attributes_for :levels
end
class Level < ActiveRecord::Base
belongs_to :game
#contains id, score and game_id
end
Is your score in Level or in User ?
OK, they're in a deeper nested relation.
You can make your life easier, if your User model declares that:
class User < ActiveRecord::Base
has_many :games
has_many :levels, through: :games
end
Then you have to join the levels.
Looking at the SQL generated by ActiveRecord, you can see that
User.joins(:levels).group(:email).sum(:score)
generates
SELECT sum(score) AS sum_score, email FROM users INNER JOIN games ON games.user_id = users.id INNER JOIN levels ON levels.games_id=games.id GROUP BY email
As sum doesn't return a Relation, but an ActiveSupport::OrderedHash, you cannot append .order() to it.
What you can do, is inject the order before the sum:
User.joins(:levels).group(:email).order('sum_score DESC').sum(:score)
generates
SELECT sum(score) AS sum_score, email FROM users
INNER JOIN games ON games.user_id = users.id
INNER JOIN levels ON levels.games_id=games.id
GROUP BY email
ORDER BY sum_score DESC
which is, what you are looking for.