I'm trying to find a way to create a simple outer join without too much hassle. I know I can do this manually by specifying an outer join, but I'm looking for a simple way.
Therefore, I was taking a look at Squeel, which seems to be the new alternative for Metawhere. It seems to be able to handle outer joins, but I can't get what I want.
In particular, I have three models :
City
Building
CityBuilding
I would simply like a list of all the buildings whether they exist in a city or not. CityBuilding is, of course, the model that connects a city to a building. I would like to get something like :
city 1{
TownCenter => city_building
Sawmill => city_building
Quarry => nil
}
The query is null since there is no city_building entry for this one, you get the idea.
Is there a way that Squeel does that? Or maybe another gem, without having to manually do an outer join?
I think you can try something like the below using Squeel. I am not sure about the where part. You will have to give one of the two join conditions.
Building.joins{city}.joins(city_buildings.outer).where{(buidlings.id == city_buildings.building_id) & (cities.id == city_buildings.city_id)}
or
Building.joins{city}.joins(city_buildings.outer).where{buidlings.id == city_buildings.building_id}
or
Building.joins{city}.joins(city_buildings.outer).where{cities.id == city_buildings.city_id}
The AR association includes uses LEFT OUTER JOIN. If you have a model relationship as follows, then:
class City
has_many :city_buildings
has_many :buildings, :through => :city_buildings
end
class Building
has_one :city_building
has_one :city, :through => :city_building
end
class CityBuilding
belongs_to :city
belongs_to :building
end
To get a list of buildings regardless of city link
Building.includes(:city).where(...)
To get a list of buildings with a of city link
Building.includes(:city).where("cities.id IS NOT NULL")
Note
I am assuming you want to access the city object after the query(if present).
This is not a good solution if you DO NOT want to eager load the city object associated with a building (as AR eager loads include associations after executing the OUTER JOIN).
Related
Here are the models I created in my Rails app:
class Pet < ActiveRecord::Base
belongs_to :shelter
belongs_to :type
end
class Shelter < ActiveRecord::Base
has_many :pets
end
class Type < ActiveRecord::Base
has_many :pets
end
I'm trying to find shelters that don't have any exotic pets in them but am stuck joining the tables in the way where I can retrieve that information! Here is my latest attempt where I believe I'm at least reaching the Types table. Any help and explanation on joins would be much appreciated!
Shelter.joins(:pet => :type).where(:types => {exotic => false})
I believe it is impossible to get the results you want using just JOINS. Instead you need to find which shelters do have exotic pets and then negate that.
One way to accomplish that is through a subquery:
Shelter.where(<<~SQL)
NOT EXISTS (
SELECT 1 FROM pets
INNER JOIN types ON types.id = pets.type_id
WHERE shelters.id = pets.shelter_id
AND types.exotic IS TRUE
)
SQL
Of course that involves a lot of explicit SQL, something I don't mind, but others do not like it.
You can also do something similar using just the ActiveRecord query interface.
shelters_with_exotics = Shelter.joins(pets: :type).where(types: { exotic: true })
Shelter.where.not(id: shelters_with_exotics)
NOTE: The queries for the two examples are different. If it mattered you would need to benchmark both of them to determine which one performed best.
class Category
has_many :images
has_many :articles
end
class Image
belongs_to :category
end
class Article
belongs_to :category
end
I'm trying to understand what solutions there are in Rails for children of different models to be queried by the same parent?
E.g. I'd like to get all images and articles that belong to the same category and sort them all by created_at.
You can try 'includes' in rails
Article.includes(:Category)
As I said it seems to me you can use eager loading multiple associations. In your case it could be something like this:
Category.where(id: 2).includes(:images, :articles).sort_by(&:created_at)
Basically you pass your desired Category ID and get :images, :articles which belongs_to Category with particular ID. sort_byprobably should do the sorting thing.
This blog post on eager loading could help you as well.
You can't simply force Active Record to bring all their dependences in a single query (afaik), regardless if is lazy/eager loading. I think your best bet is:
class Category
has_many :images, -> { order(:created_at) }
has_many :articles, -> { order(:created_at) }
end
categories = Category.includes(:images, :articles)
As long as you iterate categories and get their images and articles, this will make three queries, one for each table categories, images and articles, which is a good tradeoff for the ease of use of an ORM.
Now, if you insist to bring all that info in just one query, for sure it must be a way using Arel, but think twice if it worths. The last choice I see is the good old SQL with:
query = <<-SQL
SELECT *, images.*, articles.*
FROM categories
-- and so on with joins, orders, etc...
SQL
result = ActiveRecord::Base.connection.execute(query)
I really discourage this option as it will bring A LOT of duplicated info as you will joining three tables and it really would be a pain to sort them for your use.
I have the following models:
Car:
has_many :car_classes
CarClass:
belongs_to :Car
belongs_to :CarMainClass
CarMainClass:
has_many :car_classes
What I want to do is to count the amount of cars in CarClass grouped by the car_main_class_id but then linked to the main_class_symbol which is in CarMainClass.
The query I have now is:
CarClass.group(:car_main_class_id).count(:car_id) => {43=>79, 45=>4 ...}
Which is almost what I want, except that I end up only with the :car_main_class_id which I to be the :main_class_symbol from CarMainClass:
{"A1"=>79, "A2"=>4 ...}
I tried joining the tables and custom select options, but they didn't work.
Can this be done in a query in which I don't have to iterate through the main classes again?
Many thanks for your help!
Instead of having a SQL approach and using a "count/group by", you should look to a very simple feature of Rails ActiveRecords : the counter_cache column.
For example, you can add a column "car_classes_count" in the CarMainClass, and in CarClass class, you do like this :
CarClass:
belongs_to :car
belongs_to :car_main_class, :counter_cache => true
You can do the same with a column "car_class_count" in Car.
I don't know if it can help, but I had the same kind of problems when I started to develop with Rails. I tried to do some unsuccessful crazy SQL queries (queries that worked w/ sqlite, but did not w/ postgres) and I finally choose an other approach.
Try this:
CarClass.includes(:car_main_class => :car_classes)
.group(:car_main_class_id).map { |cc|
{ cc.car_main_class.main_class_symbol => cc.car_main_class.cars.size }
}
Although this is quite ugly - I agree with #Tom that you should try to think of more meaningful class names.
This may be a simple question, but I seem to be pulling my hair out to find an elegant solution here. I have two ActiveRecord model classes, with a has_one and belongs_to association between them:
class Item < ActiveRecord::Base
has_one :purchase
end
class Purchase < ActiveRecord::Base
belongs_to :item
end
I'm looking for an elegant way to find all Item objects, that have no purchase object associated with them, ideally without resorting to having a boolean is_purchased or similar attribute on the Item.
Right now I have:
purchases = Purchase.all
Item.where('id not in (?)', purchases.map(&:item_id))
Which works, but seems inefficient to me, as it's performing two queries (and purchases could be a massive record set).
Running Rails 3.1.0
It's quite common task, SQL OUTER JOIN usually works fine for it. Take a look here, for example.
In you case try to use something like
not_purchased_items = Item.joins("LEFT OUTER JOIN purchases ON purchases.item_id = items.id").where("purchases.id IS null")
Found two other railsey ways of doing this:
Item.includes(:purchase).references(:purchase).where("purchases.id IS NULL")
Item.includes(:purchase).where(purchases: { id: nil })
Technically the first example works without the 'references' clause but Rails 4 spits deprecation warnings without it.
A more concise version of #dimuch solution is to use the left_outer_joins method introduced in Rails 5:
Item.left_outer_joins(:purchase).where(purchases: {id: nil})
Note that in the left_outer_joins call :purchase is singular (it is the name of the method created by the has_one declaration), and in the where clause :purchases is plural (here it is the name of the table that the id field belongs to.)
Rails 6.1 has added a query method called missing in the ActiveRecord::QueryMethods::WhereChain class.
It returns a new relation with a left outer join and where clause between the parent and child models to identify missing relations.
Example:
Item.where.missing(:purchase)
I have a slightly complicated query that I'd like to have as a natural ActiveRecord relationship. Currently I call #calendar.events and #calendar.shared_events then join the arrays and sort them. The consolidation is able to be done with this sql:
SELECT calendar_events.* FROM calendar_events left outer join calendar_events_calendars on calendar_events_calendars.calendar_event_id = calendar_events.id where calendar_events.calendar_id = 2 or calendar_events_calendars.calendar_id = 2
but I'm not sure how to represent this as an ActiveRecord relationship. I know I could use a custom sql finder but I'd like to be able to use scopes on the results.
Currently an event belongs to one calendar and also belongs to many other calendars via a habtm join table:
has_and_belongs_to_many :shared_events, :class_name => 'CalendarEvent', :order => 'beginning DESC, name'
has_many :events, :class_name => 'CalendarEvent', :dependent => :destroy, :order => 'beginning DESC, name'
Any pointers would be greatly appreciated :)
Can you help us understand your data structure a bit more? What are you trying to achieve with the two relationships (the has_many/belongs_to and the HABTM) that you can't achieve through the HABTM or a has_many, :through? It looks to me like a simplification of your data model is likely to yield the results you're after.
(sorry - not enough points or would have added as a comment)
--UPDATED AFTER COMMENT
I think that what you've suggested in your comment is an infinitely better solution. It is possible to do it how you've started implementing it, but it's unnecessarily complex - and in my experience, you can often tell when you're going down the wrong path with ActiveRecord when you start having crazy complex and duplicate relationship names.
Why not
a) have it all in a has_many, through relationship (in both directions)
b) have an additional field on the join table to specify that this is the main/primary attachment (or vice versa).
You can then have named scopes on the Event model for shared events, etc. which do the magic by including the condition on the specified join. This gives you:
#calendar.events #returns all events across the join
#calendar.shared_events #where the 'shared' flag on the join is set
#calendar.main_events #without the flag