Ordering records by an association presence - sql

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 :)

Related

How to remove duplicates before aggregating sql

I'm struggling to get the correct points return value from a many to many through relationship. I have the tables seasons, teams, drivers, results and driver_teams with the relationship below
class Season < ApplicationRecord
has_many :driver_teams
has_many :drivers, through: :driver_teams
has_many :teams, through: :driver_teams
end
class DriverTeam < ApplicationRecord
belongs_to :season
belongs_to :driver
belongs_to :team
has_many :results
end
class Team < ApplicationRecord
has_many :driver_teams
has_many :results, through: :driver_teams
end
class Driver < ApplicationRecord
has_many :driver_teams
has_many :results, through: :driver_teams
end
class Result < ApplicationRecord
belongs_to :driver_team
has_one :driver, though: :driver_team
has_one :team, though: :driver_team
end
The results table has a points attribute that is just a simple interger field
I'm trying to get the sum of all points for each team within a seaon like below
season.teams.joins(:results).select('teams.*, SUM(results.points) AS points').group('teams.id')
But because a team can have multiple drivers using the Driverteam through table, these points are being duplicated by the number of drivers per team, since referencing teams from a season will return multiple teams within the through table.
The ideal result is to have season.teams return only the single instances of each team for a season.
Is there a way to prevent season.teams from returning duplicates of the teams before running an aggregate SQL function?
I've tried simply using season.teams.distinct but the distinct statement appears to be run after the group by so its still including the duplicates during the calulation.
Maybe try to select the distinct before and don't use the function .distinct of ruby. Do something like (Select distinct seasons FROM..). It should leave you without duplicates.
I ended up solving the issue by just simply adding driver_teams.id into the group by clause
season.teams.joins(:results).select('teams.*, SUM(results.points) AS points').group('teams.id, driver_teams.id')

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")

Active Record query to find records that match all conditions in Rails has_many through relationship

I have two models, Apartments and Amenities, which are associated through ApartmentAmenities. I am trying to implement a filter where I only show apartments that have all of the amenities specified.
class Amenity < ActiveRecord::Base
has_many :apartment_amenities
has_many :apartments, through: :apartment_amenities
end
class ApartmentAmenity < ActiveRecord::Base
belongs_to :apartment
belongs_to :amenity
end
class Apartment < ActiveRecord::Base
has_many :apartment_amenities
has_many :amenities, through: :apartment_amenities
end
I've got a query working that will return all apartments that match at least one of the amenities of given set like so:
Apartment.joins(:apartment_amenities).where('apartment_amenities.amenity_id IN (?)', [1,2,3])
but this isn't quite what I'm going for.
Alright, after giving up for a few days then getting back to it, I finally found this question: How to find records, whose has_many through objects include all objects of some list?
Which led me to the answer that works properly:
def self.with_amenities(amenity_ids)
where("NOT EXISTS (SELECT * FROM amenities
WHERE NOT EXISTS (SELECT * FROM apartment_amenities
WHERE apartment_amenities.amenity_id = amenities.id
AND apartment_amenities.apartment_id = apartments.id)
AND amenities.id IN (?))", amenity_ids)
end

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 :)