How to filter by association count? - sql

Let's say I have models that look like this:
class Foo < ActiveRecord::Base
has_many :bars, :through => :cakes
has_many :cakes
end
class Bar < ActiveRecord::Base
has_many :foos, :through => :cakes
has_many :cakes
end
class Cake < ActiveRecord::Base
belongs_to :foo
belongs_to :bar
end
How would I get all foos which had 10 or more bars (and therefore 10 or more cakes)?

Foo.all(:joins => :cakes,
:group => "cakes.foo_id",
:having => "count(cakes.bar_id) >= 10")

okay i tried the answer above, but had a problem.
for our purposes Father has_many :sons, ok?
i wanted to find Fathers that had zero sons.
the above did not work, because it produced an inner join... thereby filtering out all fathers without sons.
the following did work for me:
Father.includes(:sons).group('fathers.id').having( 'count(sons.id)=0' )
and it also happens to work for any other filter you'd require
Father.includes(:sons).group('fathers.id').having( 'count(sons.id)=3' )

Related

Ordering records by an association presence

Lets say I have an instructions table which is associated to a surveys table through survey_instructions join table.
What I need to achieve is to fetch all instruction records, but ordered by an association presence with a given survey.
So instructions associated with a given survey will go first, and then all other instructions which have no association with this survey.
class Instruction < ApplicationRecord
has_many :survey_instructions, dependent: :destroy
has_many :surveys, through: :survey_instructions
and
class Survey < ApplicationRecord
has_many :survey_instructions, dependent: :destroy
has_many :instructions, through: :survey_instructions
and
class SurveyInstruction < ApplicationRecord
belongs_to :survey
belongs_to :instruction
and
Could this be achieved by chaining active record queries somehow? Would appreciate any thoughts on this
Yes you can achieve this by ActiveRecord query. Try this:
survay_id = 10
#instructions = Instruction.includes(:survey_instructions).order("(CASE WHEN survey_instructions.survay_id = #{survay_id} THEN 1 ELSE 2 END) ASC NULLS LAST")
Happy coding :)

How do I chain multiple models to reduce the number of SQL queries

I'm trying to check whether a student has attempted an assigned test or not. I want to chain the relevant models to bring down the number of queries to just 1. The following are my models:
class Test < ActiveRecord::Base
has_many :assigns
has_many :attempts
belongs_to :topic
end
class Topic < ActiveRecord::Base
has_many :tests
has_many :attempts
has_many :assigns, through: :test
end
class Assign < ActiveRecord::Base
belongs_to :test
belongs_to :student
has_many :attempts
end
class Attempt < ActiveRecord::Base
belongs_to :test
belongs_to :topic
belongs_to :assign
belongs_to :student
end
I want to check if a particular student (id: 100) has attempted an assigned test or not, and also retrieve other details such as the topic name of the test. So far I have something like this:
ta = Assign.includes(:test => {:topic => :attempts})
This allows me to retrieve details such as the test_name, topic_name, when it was assigned etc. in a single query. How do I also include the Attempt records of student_id: 100 in the same query? With what I have now, when I retrieve the student's attempt details a brand new query is being generated.
What I want is something like the follwoing without having to touch the database again:
ta.test.attempts.where(student_id: 100)
How do I do all this with just one query?
Okay, since you want all kinds of information from all the joined tables, so you will have to join them up from the beginning.
Attempt.joins(:topic, :test, :assign)
Then you can filter it with the student_id
.where("attempts.student_id" => 100)
Finally, the fields you want
.select("attempts.id as attempt_id, tests.name as test_name, topics.name as topic_name, assigns.created_at as assigned_at")
In summary
Attempt
.joins(:topic, :test, :assign)
.where("attempts.student_id" => 100)
.select("attempts.id as attempt_id, tests.name as test_name, topics.name as topic_name, assigns.created_at as assigned_at")

Sort by a column in JOIN table of many-to-many relationship (rails)

I have a many to many relationship between category and post.
The join table is category_post_relationships.
class Post < ActiveRecord::Base
has_many :categories, through: :category_link_relationships
has_many :category_post_relationships , :dependent => :destroy
end
class Category < ActiveRecord::Base
has_many :posts, through: :category_link_relationships
has_many :category_post_relationships , :dependent => :destroy
end
class CategoryPostRelationship < ActiveRecord::Base
belongs_to :post
belongs_to :category
end
If I have a category, the category can query for all posts by category.posts. And I want to sort these posts by the created_at of the join table category_link_relationships. There can be only one record for each category and post. I want to sort the links by the column created_at of related relationship records.
For example, post 1 was created and associated to category A.
Then, the post 1 was associated to category B.
Post 2 was then created and associated to category B.
category B now has post 1 and post 2. I want to sort post 1 and post 2 by the created_at of the relationships, not the created_at of posts.
Thanks.
class Post < ActiveRecord::Base
has_many :category_link_relationships , :dependent => :destroy
has_many :categories, through: :category_link_relationships
end
class Category < ActiveRecord::Base
has_many :category_link_relationships , :dependent => :destroy
has_many :posts, through: :category_link_relationships
end
and now you can find posts in next way:
#category.posts.joins(:category_link_relationships).order('category_link_relati‌​onships.created_at')
If you will always order this way, you can use a default scope on the join table.
class CategoryLinkRelationship < ApplicationRecord
belongs_to :post
belongs_to :category
default_scope { order(created_at: :asc)}
end
This will be automatically picked up by ActiveRecord. Be careful, if there is also a default scope on Category it will override the sort.

Rails 3 has_one with join table

I have the following:
has_many :sports, :through => :user_sports
has_one :primary_sport, class_name: "UserSport", conditions: ["user_sports.primary = ?", true]
has_many :user_sports
When I run this in console:
athlete = Athlete.all.last
athlete.primary_sport
The record that is returned is the record from the join table instead of the record joining the sports table. Any way to return the actual sport from the join?
You might probably do something like this:
class UserSport < ActiveRecord::Base
has_many :athletes
has_many :sports
end
athlete = Athlete.all.last
athlete.primary_sport.sport
Didn't try it by myself, just check and see :)

PostgreSQL Rails has_many :through / collection_singular_ids / :order problem

In the process of migrating to heroku, I have a weird error only when I use PostgreSQL (works fine in Mysql)
When I execute #user.county_ids I get the following error:
ActiveRecord::StatementInvalid: PGError: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list
LINE 1: ...id" WHERE ("activity_areas".user_id = 1) ORDER BY counties.n...
The generated sql request is :
SELECT DISTINCT "activity_areas".county_id FROM "activity_areas" INNER JOIN "counties" ON "counties"."id" = "activity_areas"."county_id" WHERE ("activity_areas".user_id = 1) ORDER BY counties.name ASC
and finally the models:
class User < ActiveRecord::Base
has_many :activity_areas
has_many :counties, :through => :activity_areas
end
class ActivityArea < ActiveRecord::Base
belongs_to :user
belongs_to :county
default_scope joins(:county).order("counties.name ASC")
end
class County < ActiveRecord::Base
has_many :activity_areas
has_many :users, :through => :activity_areas
default_scope :order => 'name ASC'
end
Any idea on how to fix this?
Thanks,
When it comes to PostgreSQL, ensure that the elements in order by clause are also present in the select clause. MySQL is kinda lenient on this rule :)
Try changing the default scope in activity area model to
default_scope select('counties.name').joins(:county).order("counties.name ASC")
This should generate a SQL like
SELECT DISTINCT "activity_areas".county_id, counties.name FROM "activity_areas"...