A Country
has_many residences
and a Residence
has_many listed_amenities
which are linked to amenities
. I need to know how many residences
which are located in the Country
with the id 1 have the amenities
with the ids [48, 49, 50] (they can have more but they have to have all three of them).
The following Ruby on Rails (v4.2.4) code does the trick:
id_list = [48, 49, 50]
Country.first.
residences.
where(id: Residence.
joins(:listed_amenities).
where(listed_amenities: {amenity_id: id_list}).
group('residences.id').
having("count(listed_amenities.*) = ?", id_list.size).
pluck(:id)).
count
It results in the following SQL:
SELECT "countries".*
FROM "countries"
ORDER BY "countries"."id" ASC LIMIT 1
SELECT "residences"."id"
FROM "residences"
INNER JOIN "listed_amenities"
ON "listed_amenities"."residence_id" = "residences"."id"
WHERE "listed_amenities"."amenity_id" IN (48, 49, 50)
GROUP BY residences.id
HAVING count(listed_amenities.*) = 3
SELECT COUNT(*)
FROM "residences"
WHERE "residences"."country_id" = $1
AND "residences"."id"
IN (51, 59, 60, ...) [["country_id", 1]]
Obviously that is an expensive way to solve the given problem and each table has more than 1,000,000 entries.
How can I solve this on a PostgreSQL (9.4.4) in a better/faster way? Is there one magic SQL line which does the trick?
Thanks to "Get the count of rows count after GROUP BY" I have the following SQL query for doing the search over all residences
but I lack the SQL know-how to connect the Country
to it:
SELECT count(*) AS ct
FROM (
SELECT 1
FROM listed_amenities
WHERE amenity_id IN (48, 49, 50)
GROUP BY residence_id
HAVING count(*) = 3
) sub;
The Ruby on Rails setup:
rails g scaffold Country name
rails g scaffold Residence country:references name:string
rails g scaffold Amenity name:string
rails g scaffold ListedAmenity residence:references amenity:references
app/models/country.rb
class Country < ActiveRecord::Base
has_many :residences
end
app/models/residences.rb
class Residence < ActiveRecord::Base
belongs_to :country
has_many :listed_amenities
has_many :amenities, through: :listed_amenities
end
app/models/amenities.rb
class Residence < ActiveRecord::Base
belongs_to :country
has_many :listed_amenities
has_many :amenities, through: :listed_amenities
end
app/models/listed_amenities.rb
class ListedAmenity < ActiveRecord::Base
belongs_to :residence
belongs_to :amenity
end