Say I have the two models Users and Appointments where a user has_many appointments.
An appointment can be of two different types: typeA and typeB.
How can I write a query to order the users by the amount of typeB appointments they have?
I've looked into counter_cache but it seems to just count the number of the association (so in this case the number of appointments a user would have) and does not allow for the counting of a particular type of appointment.
With joins (INNER JOIN) you'll get only those users, who have at least one appointment associated:
User.joins(:appointments)
.where(appointments: { type: 'typeB' })
.group('users.id')
.order('count(appointments.id) DESC')
If you use includes (LEFT OUTER JOIN) instead, you'll get a list of all users having those without appointments of 'typeB' at the end of the list.
Depending on the size and complexity of the database, it may be sometimes better to do two queries instead of joins on tables. In case you want to skip joins one way could be to get the order of Ids from one select query, and then retrieving the records from the second query.
ordered_user_ids = Appointments.select('user_id, count(1) AS N')
.where(:type => 'typeB').group(:user_id).order('N desc').collect(&:user_id)
# find users keeping the order of the users intact
User.find(ordered_user_ids, :order => "field(id, #{ordered_user_ids.join(',')})")
Related
Scenario:
Pupils have many grade progressions.
A grade progression belongs to a polymorphic progressable (either a subject or topic), and a score (decimal).
Pupils may not yet have a grade progression recorded for
all progressables.
I need:
A list of Pupils ordered by grade score (highest first) for any given progressable.
Pupils without grades for the progressable should appear last.
If a Pupil has multiple grades for the progressable then the most recent should be used.
I've tried
Pupil.left_joins(:grade_progressions).where(grade_progressions: { progressable: progressable })
.order('grade_progressions.score DESC')
however, this does not work because it excludes any pupils without a grade for the progressable.
With raw SQL this should be possible by specifying the condition on the LEFT JOIN however I can't find a way to do this with ActiveRecord object syntax. I'd like to avoid specifying the join as a string if possible.
How can I author an ActiveRecord query to order by an optional polymorphic association in this way?
Rails 5 has a native .left_outer_joins method. Previously you needed to specify the join as a string or use .includes which often has unintended results.
An OUTER JOIN unlike the SQL default INNER JOIN includes rows that have no matches in the joined table.
Suppose I have two models, one is User and one is Story. Both models has has_and_belongs_to_many relationship with each other. One user can have many stories and one story can have many users.
Since most of you already have understood, there exists a join table between the two, "users_stories" which has 'user_id' and 'story_id' as its columns.
Question is "How to get the users who do not have story?". Any efficient way would be appreciated.
Try this:
User.joins('left join user_stories ON users.id = user_stories.user_id').where('user_stories.user_id is NULL')
For more details you can see this image:
https://www.codeproject.com/KB/database/Visual_SQL_Joins/Visual_SQL_JOINS_orig.jpg
you can put a scope on model with joins query to check for count of stores greater than zero
without_stories_user = User.joins(users_stories: :story).group('users.id').having("count(stories.id) > ?",0)
For me this was users without notifications with simillar has_many and belongs to association.
without_notifications_user = User.joins(user_notifications: :notification).group('users.id').having("count(notifications.id) > ?",0)
Thanks
I am wondering how to do this without double each loop.
Assume that I have a user model and order model, and user has_many orders.
Now I have a users array which class is User::ActiveRecord_Relation
How can I get the orders of these users in one line?
Actually, the best way to do that is :
users.includes(:orders).map(&:orders)
Cause you eager load the orders of the users (only 2 sql queries)
Or
Order.where(user_id: users.pluck(:id))
is quite good too in term of performance
If you've got a many-to-many association and you need to quickly load in all the associated orders you will need to be careful to avoid the so-called "N plus 1" load that can result from the most obvious approach:
orders = users.collect(&:orders).flatten
This will iterate over each user and run a query like SELECT * FROM orders WHERE user_id=? without any grouping.
What you really want is this:
orders = Order.where(user_id: users.collect(&:id))
That should find all orders from all users in a single query.
An answer just come up my mind after I asked....
users.map {|user| user.orders}
In Rails 4, I have a project in which I've set up three models with the following many-to-many relationships:
An Item
has_and_belongs_to_many categories
has_and_belongs_to_many tags
A Category
has_and_belongs_to_many items
A Tag
has_and_belongs_to_many items
And while it's easy to select an Item and automatically get all associated categories and tags, there are some situations in which I'd want to select items AND their associated categories, but NOT their tags. In these cases, I'd like to avoid doing extra database joins against the Tags table and ItemsTags join table. Can anyone help me with the correct find syntax to only join Items to categories? (Side note: I'm also planning on adding 10 additional many-to-many relationships between items and other models, but I'm just simplifying the scenario for this question. In the end, I'm trying to avoid doing a join with an excessive number of tables whenever I can.)
Thanks!
Rails will by default not load associated records unless you request it
Item.all will only fetch record from 'items' table
Then later in your code if you call item.categories that's the point when a query is performed to fetch all categories of this particular item. If you never call item.tags then the query to 'tags' table is never executed and the records are not fetch. Bottom line is: you can have as many associations as needed, as long as you don't explicitly call them they won't be loaded.
Side note about performance, rails offer several ways to join and include associated tables:
Item.include(:category).all Will trigger only 2 queries to fetch all items, and all associated categories.
Item.include(:category).joins(:category).all -> will trigger only 1 query joining the items and categories tables (but it may be slower than 2 requests)
So you have all control over what's loaded from the database. Those can apply for scope as well.
I have two models I want to connect with an m-to-m relationship, but I want the relationship to have some data of its own, such as a date due or a count or something like that...
Suppose I have Users, Groups, and some UsersInGroups object, where users and groups both have a has_many X, :through Y relationship. UsersInGroups belongs_to a user and a group, but also has a join_date that shows when a user joined the group.
so I can use self.groups.A to get Group variables from User and vice versa, but how do I get at the join_date variable?
In a many to many relationship, if a User can have many groups, and you do aUser.user_in_groups, it will return an array of the groups (which will be an instance of the model class representing them). You can iterate over each of these and obtain the join_date on each one, or by indexing into the array: aUser.user_in_groups[0].join_date
If you just want an array of join dates or something, I would look into the Ruby collect method.
Iteration:
aUser.users_in_groups.each do |group|
group.join_date
end