How to left outer joins with conditions - ruby-on-rails

I have this relation:
class Action < ApplicationRecord
has_many :actions_users
I tried to make a query like:
select *
from actions left outer join actions_users
on actions_users.action_id = actions.id and actions_users.user_id = 1
where actions.user_id = 1
Meanwhile, in my experience, in all of the result that I tried,
select *
from actions left outer join actions_users
on actions_users.action_id = actions.id
where actions.user_id = 1 and actions_users.user_id = 1
the join condition code and general condition are in where function.
How can I work it out?

You can pass a string in join query and use the rails table naming conventions for this.
Action.joins("left outer join action_users on (action_users.id = actions.id and action_users.id = 1")).where('action_users.user_id = ? ', 1)

Because you have a general where condition, you can use includes. This will generate a LEFT OUTER JOIN query:
Action.includes(:actions_users).where(actions_users: { user_id: true })
Or if you are using Rails 5+, Active Record provides a finder method left_outer_joins:
Action.left_outer_joins(:actions_users).where(actions_users: { user_id: true })

Action.left_outer_joins(:actions_users).where(user_id: 1)
select *
from actions left outer join actions_users
on actions_users.action_id = actions.id and actions_users.user_id = 1
where actions.user_id = 1
Although you did not ask for it yet, ...
Action.left_outer_joins(:actions_users).where(actions_users: {status: 'active'})
select *
from actions left outer join actions_users
on actions_users.action_id = actions.id and actions_users.user_id = 1
where actions_users.status = 'active'

Up to Rails 7 there is no way to specify conditions directly on an OUTER JOIN, see the documentation for Specifying Conditions on the Joined Tables. The examples shown are suitable for INNER JOINs (as they use .where), but won't work for OUTER JOINs for the same reason.
You could try to specify the OUTER JOIN manually, but will run into problems passing parameters:
Action.joins("LEFT OUTER JOIN action_users ON (action_users.id = actions.id AND action_users.id = :user_id")
So you will need to do parameter substitution somewhat like this:
outer_join_sanitized = ApplicationRecord.sanitize_sql([
"LEFT OUTER JOIN action_users ON (action_users.id = actions.id AND action_users.id = :user_id)",
{ user_id: 22 }
])
And you could then use Actions.joins(outer_join_sanitized). At this point you might agree that just running with raw SQL from the start is the easier way to go.

Related

How to do a query with mulitple joins in AR

My simplified database :
For example, I would like to get all pois with way_id = 2 (through track_ways & poi_tracks)
I have a scope in my way model :
scope :by_way, ->(way_id) { joins(:ways).where('ways.id = ?', way_id) }
I'm using this scope in my query :
Poi.joins(:tracks).where(tracks: Track.by_way(2))
But the result is not the expected one
Spoiler:
Poi.joins(poi_tracks: [track: [:track_ways, :ways]]).where('ways.id = ?', 2)
First join with poi_tracks
Poi.joins(:poi_tracks).all
Join with tracks
Poi.joins(poi_tracks: [:track]).all
Join with track_ways
Poi.joins(poi_tracks: [track: [:track_ways]]).all
Join with ways
Poi.joins(poi_tracks: [track: [:track_ways, :ways]])
Apply way condition
Poi.joins(poi_tracks: [track: [:track_ways, :ways]]).where('ways.id = ?', 2)

ActiveRecord rails 4 not equal to condition

I am using rails 4 for developing my application. I have a problem in my active record query with not equal to condition. I am not getting required output. I think there is a problem with not equal to part.
#available_rooms = Room.joins("LEFT OUTER JOIN reservations ON reservations.room_id = rooms.id").where("category_id = ? AND arrival_date != ?",params[:category],params[:arrival_date])
You can use where.not() in Rails 4 :
#available_rooms = Room.joins("LEFT OUTER JOIN reservations ON reservations.room_id = rooms.id")
.where(category_id: params[:category])
.where.not(arrival_date: params[:arrival_date])
In some version of SQL not equal is written as !=
Try this using the comparator <> like so:
#available_rooms = Room.joins("LEFT OUTER JOIN reservations ON reservations.room_id = rooms.id")
.where("category_id = ? AND arrival_date <> ?",params[:category],params[:arrival_date])

NOT EXISTS SQL query in rails 3.2

I have an sql query like this:-
SELECT * FROM `permissions` join entities where NOT EXISTS (select
entity_id,permission_id from role_permissions where role_id=5 and
entities.id = role_permissions.entity_id and permissions.id =
role_permissions.permission_id)
I would like to get the corresponding rails query.
I have tried this.
Permission.joins("join entities").joins("LEFT OUTER JOIN role_permissions on
permission_id != permissions.id and entities.id != entity_id and
role_permissions.role_id= role_id").select("role_permissions.entity_id,role_permissions.role_id,
role_permissions.permission_id").group('role_permissions.entity_id,
role_permissions.permission_id')
But it doesn't works.
thanks
hari
I have been particularly in love with EXISTS queries lately, precisely because it does not require you to fully join another table. As far as I know, you do have to explicitly write the SQL clause, but you can still make it work with Activerecord. You can even put this in a scope within a lambda block.
Permission.joins(:entities).where(<<-SQL
NOT EXISTS(
select
* from role_permissions where role_id=#{your_role_id} and
entities.id = role_permissions.entity_id
and permissions.id = role_permissions.permission_id
)
SQL
)
Try like this for a default SQL query:
sql = "SELECT some_field FROM `permissions` join entities where NOT EXISTS (select entity_id,permission_id from role_permissions where role_id=5 and entities.id = role_permissions.entity_id and permissions.id = role_permissions.permission_id)"
results = ActiveRecord::Base.connection.execute(sql)
results.each do |result|
#register_users << { some_field: result[0] }
end
Try this
UPDATED BASED ON FIRST COMMENT
Permission.joins(:entities).joins("LEFT OUTER JOIN role_permissions on
permission_id != permissions.id and entities.id != entity_id and
role_permissions.role_id= role_id")
.select("role_permissions.entity_id,role_permissions.role_id,
role_permissions.permission_id")
.group('role_permissions.entity_id, role_permissions.permission_id')

How to write this Join Constraint with ActiveRecord

How should I write this with ActiveRecord:
select stores.id, stores.name, store_specials.active
from stores
left outer join store_specials on
(store_specials.store_id = stores.id and store_specials.special_id = 1)
where stores.active = true;
Thanks
Rails 3.x
Stores.select("stores.id, stores.name, store_specials.active").joins("LEFT OUTER JOIN tore_specials ON store_specials.store_id = stores.id AND store_specials.special_id = 1).where("store_specials.active = true")
Hopefully this is what you need

Self joins in Rails

I have the following tables: users(id), articles(id), favorites(article_id, user_id). I need to list articles added to favorites by current user with a flag indicating if they were also favorited by other users. SQL is quite simple:
select articles.id, count(f2.article_id)
from articles a
inner join favorites f1 on f1.article_id = a.id
left join favorites f2 on f1.article_id = f2.article_id and not f1.user_id = f2.user_id
where f1.user_id = 1
group by a.id
Is there a way to do it using Rails query generator?
something like this should get you on your way (assuming you have an Article and Favorites model/table):
t = Article.joins("INNER JOIN #{Favorite.table_name} AS f1 on f1.article_id = #{Article.table_name}.id")
t = t.joins("LEFT JOIN #{Favorite.table_name} AS f2 on f1.article_id = f2.article_id AND NOT f1.user_id = f2.user_id")
t = t.where("f1.user_id = ?", 1)
t = t.group("#{Article.table_name}.id")
t.select("#{Article.table_name}.id, COUNT(f2.article_id)")

Resources