Query that finds objects with ANY TWO association ids (Rails 4) - ruby-on-rails

BACKGROUND: Posts have many Communities through CommunityPosts. I understand the following query returns posts associated with ANY ONE of these community_ids.
Post.joins(:communities).where(communities: { id: [1,2,3] })
OBJECTIVE: I'd like to query for posts associated with ANY TWO community_ids in the array. Having either communities 1 and 2, communities 1 and 3, or communities 2 and 3.
EDIT: Please assume that length of the array is unknown. Used this array of explanation purposes. It will be current_user.community_ids instead of [1,2,3].

This will get you all posts having exactly any two associations from the current user's communities:
Post.select("posts.*, count(distinct(communities.id))").joins(:communities).where("communities.id in (?)", current_user.community_ids).group("posts.id").having("count(distinct(communities.id)) = 2")
Apparently, to relax the restriction, you'll need to change the condition in the having clause to >=.

Related

How to query to return the most common foreign key in a join table with rails

I have 3 models. Project, ProjectMaterial, and Material
A Project has_many ProjectMaterials and many Materials through ProjectMaterials.
This is bidirectional, with ProjectMaterial acting as a join table with user-submittable attributes.
I'd like to query the ProjectMaterial model to find the most frequent value of material_id. This way I can use it to find the most frequently used material.
Any help with a query would be greatly appreciated. I'm stuck. Thanks in advance!
You can chain group, count and sort methods on your ActiveRecord query like this:
ProjectMaterial.group(:material_id).count.values.sort.last
The first part ProjectMaterial.group(:material_id).count gives you the hash of each {material_id0 => rows_count0, material_id1 => rows_count1, ...}. Then, you can just get the values of the hash in an array, sort it and get the last item.
One way could be pluck ids to get the array, then count the most frequent.
ids = ProjectMaterial.pluck[:material_id]
For example: Ruby: How to find item in array which has the most occurrences?
Or better, by query to get a hash with counts:
counts = ProjectMaterial.group(:material_id).count
Once you know that you get a hash, you can sort by any ruby method, picking the most frequent or the n most frequent. Example of sorting:
counts.sort_by { |_, v| v }

Count returns the count of all joined data instead of the main object

I'm trying to count users who have dogs.
User.joins(:pets).where("pets.type = ?", :dog).count
This returns the count of the users + their dogs combined, instead i just want the count of actual users.
What am i doing wrong?
Update
I've also tried to just fetch the users using the above query and it returns an array of the same users repeated multiple times depending on how many dogs they have.
Try this:
User.joins(:pets).where("pets.type = ?", :dog).count(distinct: true)
See api doc.
The joins would return all the duplicate rows for which the user has multiple associations...
For example if a user, U1 has one dog and user, U2 has two dogs then total three rows would be returned by the joins....so instead of using joins, try to use the includes option...
refer to this Railscast, http://railscasts.com/episodes/181-include-vs-joins for more...

How do I find where id does not match any of an array?

I have a Workout model that has and belongs to many Equipment models. I have an array of some Equipment IDs. I want to find all Workouts that don't have any Equipment assigned that matches any of the array of Equipment IDs.
So, if my array = [2,3,5] I want to find all workouts where the assigned equipment ids does not include 2, 3 or 5.
EDIT:
Workout.joins(:equipment).where("equipment.id not in(?)",[2,3,5]).uniq
Assuming five instances of Equipment, the code above returns workouts with equipment.ids 1 and 4 (good), but also returns partial matches for example Workouts with equipment.id = [1,2], [1,2,3].
It helps to think of what result set your query returns.
Workout.joins(:equipment).where("equipment.id not in(?)",[2,3,5]).uniq
Joins all the related equipments to their workouts. If a workout was linked to 4 equipments then you'd get 4 rows for that workout. The where clause just filters that 4 down to a smaller number - it can't wipe them all out just because one matches.
What you need to do instead is add conditions to the join itself. Something like
select workouts.*
left join equipments_workouts on workout_id = workouts.id and equipment_id in (2,3,5)
where equipment_id is null
Should return the correct workouts (it should also return a workout with 0 equipments but I don't know if that's a consideration.)
This works by trying to join 'bad' equipments. Because it's a left join, if no such row can be found then the result set will still include a row for that workout but with the columns for equipmnts_workouts all set to null. As a bonus you no longer have to eliminate duplicates.
Activerecord doesn't have a very nice way of writing queries like this. The joins method will accept an arbitrary SQL fragment though:
Workout.joins("left join equipment_workouts on workout_id = workouts.id and equipment_id in (2,3,5)").
where("equipment_id is null")
You might find the sanitize_sql method useful for generating that sql fragment
Workout.joins(:equipment).merge(Equipment.where("id not in(?)",[2,3,5])).uniq
or
Workout.joins(:equipment).where("equipments.id not in(?)",[2,3,5]).uniq
also u can try this, it should find all Workouts that don't have any Equipment
Workout.includes(:equipment).where("equipments.id not in(?)",[2,3,5])
This can be improved, but should work:
class Workout < ActiveRecord::Base
scope :without_equipments, lambda{|ids| joins(:equipment).where("equipments.id not in (?)", ids.repeated_permutation(ids.size).map(&:uniq).uniq)}
end
Workout.without_equipments 2,3,5

Rails select distinct

in a scenario i want to retrieve the records with different values so i used distinct for that,
Book.where(["user_id = ?",#user_id]).select('distinct title_id')
`this, only retrives the records like this [#<Book title_id: 30>, #<Book title_id: 31> ]`
but i want to fetch the id of Book as well along with title_id
so, please advise me how to work on this
thanks
use grouping:
Book.where(:user_id => #user.id).grouped('title_id')
problem is that if you do grouping you can't have different book ids, they are all grouped into single row. You can use GROUP_CONCAT to workaround that:
Book...select('books.*, GROUP_CONCAT(id) as ids')
that way you'll have book ids attribute for every group

rails, Query for Records that are nested with a Count() GT 0?

2 models
Books (has_many: chapters)
Chapters (belongs_to: books)
Id like to display a list of books, but only books with chapters.
Here's what I have so far:
#books = Book.find(:all,:include => :chapters)
Problem here is that books is returning books w/o chapters (0 chapters)
when dealing with nested resources like this, how do I say find where count > 0 for chapters?
Right now I'm doing this in the controller, not sure if that's a problem. but i should probably move this to a model?
cheers
Add this code to Book:
scope :only_with_chapters, includes(:chapters).having('COUNT(chapters.id) > 0').group('books.id')
And to use it just run Book.only_with_chapters
Depends on performance use joins, instead of includes.
Using joins it will look like:
scope :only_with_chapters, includes(:joins).group('books.id')

Resources