ActiveRecord : find associated records with ALL conditions - sql

I am trying to perform an activerecord query that essentially does the below.
I have 3 models in a many-to-many relationship
class Item < ActiveRecord::Base
has_many :item_modifiers, dependent: :destroy
has_many :modifiers, through: :item_modifiers
end
class ItemModifier < ActiveRecord::Base
belongs_to :item
belongs_to :modifier
end
class Modifier < ActiveRecord::Base
has_many :item_modifiers
has_many :items, through: :item_modifiers
end
Now I want to find all items that have modifiers with IDs 1 and 2
I have tried several things like:
Item.includes(:modifiers).where(modifiers: {id: 1}).where(modifiers: {id: 2})
This fails because it searches for modifiers where ID = 1 AND ID = 2 which is always false.
This also doesn't work
Item.includes(:modifiers).where(modifiers: {id: [1, 2]})
Because this does an IN (1, 2) query so it returns items with modifiers of either 1 or 2. I want items that have any modifiers as long as they have AT LEAST 1 modifier with ID 1 and AT LEAST 1 modifier with ID 2
I seem to be missing something quite simple but I just can't get my head around it.
Thanks in advance.

It could like:
Item.joins(:item_modifiers).where("item_modifiers.modifier_id=1 OR
item_modifiers.modifier_id=2").group("items.id").having("COUNT(item_modifiers.id)=2")
If write in plain SQL, it could be:
SELECT I.*, COUNT(IM.id) FROM items as I INNER JOIN item_modifiers AS IM on I.id=IM.item_id
WHERE IM.modifier_id=1 OR IM.modifier_id=2 GROUP BY I.id HAVING COUNT(IM.id)=2
It will get all the items with its modifers' id include 1 and 2. Maybe in different DB, the statement need a slight change.

Related

Rails select by number of associated records

I have following models in my rails app:
class Student < ApplicationRecord
has_many :tickets, dependent: :destroy
has_and_belongs_to_many :articles, dependent: :destroy
class Article < ApplicationRecord
has_and_belongs_to_many :students, dependent: :destroy
class Ticket < ApplicationRecord
belongs_to :student, touch: true
I need to extract all Students who has less than articles and I need to extract all Students who's last ticket title is 'Something'.
Everything I tried so far takes a lot of time. I tried mapping and looping through all Students. But I guess what I need is a joined request. I am looking for the most efficient way to do it, as database I am working with is quite large.
go with #MCI's answer for your first question. But a filter/select/find_all or whatever (although I havn't heared about filter method in ruby) through students record takes n queries where n is the number of student records (called an n+1 query).
studs = Student.find_by_sql(%{select tmp.id from (
select student_id as id from tickets where name='Something' order by tickets.created_at desc
) tmp group by tmp.id})
You asked
"I need to extract all Students who has less than articles". I'll presume you meant "I need to extract all Students who have less than X articles". In that case, you want group and having https://guides.rubyonrails.org/active_record_querying.html#group.
For example, Article.group(:student_id).having('count(articles.id) > X').pluck(:student_id).
To address your second question, you can use eager loading https://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations to speed up your code.
result = students.filter do |student|
students.tickets.last.name == 'Something'
end
Here association is HABTM so below query should work
x = 10
Student.joins(:articles).group("articles_students.student_id").having("count(articles.id) < ?",x)

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

Rails linking multiples row's id in a table to a single row in another table SQL

I'm making a calendar app in which the user can create multiple calendar and add multiple entries to each different calendar in Rails. For instance, I have the following table
Entry
id description
----------------------------------------------------
1 go shopping
2 go to cinema
3 do homework
Calendar
id entry_id
----------------
1 1, 3
2 2
3 1, 2, 3
What would be the association/solution if I want to get all the entries from a row (in array?) in the calendar and how do would I add a new entry to the row? (I looked at the has_and_belongs_to_many association but it seems to require a third table which isn't as direct as having multiple ids assigned to a single row...)
If you don't want to have to do crazy joins later you would probably want to have a join table. Yes the third table is a good idea here.
class Entry < ActiveRecord::Base
has_many :calendars, through: :scheduling
end
class Calendar < ActiveRecord::Base
has_many :entries, through: scheduling
end
class Scheduling < ActiveRecord::Base
belongs_to :entry
belongs_to :calendar
end
Then tables look like this:
entires
id:integer
description:string
calendars
id:integer
schedulings
id:integer
calendar_id:integer
entry_id:integer
And your ActiveRecord queries look like this:
Calendar.find(3).entries.ids #=> [1, 2, 3]
Entry.find(1).calendars.ids #=> [1, 3]
The rails guide also provide a great example:
http://guides.rubyonrails.org/association_basics.html#choosing-between-has-many-through-and-has-and-belongs-to-many

rails ActiveRecord includes method with where : where not applied in subsequent calls to get associations

Say I have 2 tables as and bs in a many-to-many relationship with a join table. The join table has an attribute cond.
The records are as follows:
as:
id
1
abs:
id a_id b_id cond
1 1 1 1
2 1 1 0
bs:
id
1
Now I do:
b = A.find(1).bs.where(:abs => {:cond => 1}).includes(:abs).first
Which gives me my b instance, the question is, why does b.abs return all abs rows?
I thought the includes method was meant to avoid reloading associations in subsequent calls to the associations methods. Since I used a where in the first call I would expect b.abs to return only the row with cond == 1.
I tried this in the rails console and no SQL request is issued when I perform the b.abs call. What is happening here? Is there a way to do what I want or do I need to put the condition again on b.abs like *b.abs.where(:abs => {:cond => 1}) ?
As requested in the comments, here is the code for the models:
cat app/models/a.rb
class A < ActiveRecord::Base
has_many :bs, :through => :abs
has_many :abs
end
cat app/models/b.rb
class B < ActiveRecord::Base
has_many :as, :through => :abs
has_many :abs
end
cat app/models/ab.rb
class Ab < ActiveRecord::Base
attr_accessible :cond, :a_id, :b_id
belongs_to :a
belongs_to :b
end

Query a 3-way relationship in Active Record

I'm trying to figure out how to query this relationship without using find_by_sql
class User < ActiveRecord::Base
has_many :lists
end
class List < ActiveRecord::Base
has_many :list_items
belongs_to :user
end
class ListItem < ActiveRecord::Base
belongs_to :list
belongs_to :item
end
class Item < ActiveRecord::Base
has_many :list_items
end
this should be what we are using but How would I do this not by find_by_sql
in user.rb
def self.find_users_who_like_by_item_id item_id
find_by_sql(["select u.* from users u, lists l, list_items li where l.list_type_id=10 and li.item_id=? and l.user_id=u.id and li.list_id=l.id", item_id])
end
I've tried several different includes / joins / merge scenarios but am not able to get at what I'm trying to do.
thx
It's a bit difficult to tell exactly what query you're trying to do here, but it looks like you want the user records where the user has a list with a particular list_type_id and containing a particular item. That would look approximately like this:
User.joins(:lists => [:list_items]).where('lists.list_type_id = ? and list_items.item_id = ?', list_type_id, item_id)
This causes ActiveRecord to execute a query like the following:
SELECT "users".* FROM "users" INNER JOIN "lists" ON "lists"."user_id" = "users"."id" INNER JOIN "list_items" ON "list_items"."list_id" = "lists"."id" WHERE (lists.list_type_id = 10 and list_items.item_id = 6)
and return the resulting collection of User objects.