Self joins in Rails - ruby-on-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)")

Related

How to left outer joins with conditions

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.

Rails find_by_SQL with Rails

I'm using a find_by_sql method to search users in my userstable.
is there a possibility to use rails code in the select statement?
User.find_by_sql ["SELECT DISTINCT
users.*
FROM
users
JOIN
clients_courses cc
ON
cc.client_id = users.client_id
LEFT JOIN
memberships m
ON
m.user_id = users.id AND m.course_id = cc.course_id
WHERE
cc.course_id = ?
AND
m.user_id IS NULL
AND
users.active = ?
AND
users.firstname LIKE ? or users.lastname LIKE ?
AND NOT IN ( RAILS CODE )", self.id, true, "#{search}%", "#{search}%"]
end
I Marked the position with RAILS CODE
I want to do someting linke this:
Membership.where("course_id = ?", self.id).users
is there a way to do this?
You can do this -
member_user_ids = []
Membership.where("course_id = ?", self.id).map{|membership| membership.users.map{|user| member_user_ids << user.id}}
# you might want to put a uniq! on member_user_ids
User.find_by_sql ["SELECT DISTINCT
users.*
FROM
users
JOIN
clients_courses cc
ON
cc.client_id = users.client_id
LEFT JOIN
memberships m
ON
m.user_id = users.id AND m.course_id = cc.course_id
WHERE
cc.course_id = ?
AND
m.user_id IS NULL
AND
users.active = ?
AND
users.firstname LIKE ? or users.lastname LIKE ?
AND users.id NOT IN ( #{member_user_ids.join(',')} )", self.id, true, "#{search}%", "#{search}%"]
You can also have a look at link which explains how to put array of strings in where clause.

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')

Rails 2 + Microsoft SQL Server: ActiveRecord returns -1 but SQL server returns records?

Why does ActiveRecord always return -1 whether it's through a browser or through the console?
SQL statement:
SELECT category.categoryname, sum(lineitems.qty) as totalSales,
sum(lineitems.qty*size.sizeship) as volume,
sum(lineitems.qty*lineitems.purcprice) AS totmerchandise FROM category, products,
orders, shipments, lineitems, size WHERE
category.categoryid = products.categoryid AND products.productid =
lineitems.productid AND lineitems.posshipid = shipments.posshipid AND
shipments.posorderid = orders.posorderid AND size.sizeid = products.size AND
category.categoryid NOT IN (77,79) AND orders.orderstatus in
(1,4,5) AND orders.ordercomplete = 1 AND numbotincase > 0 AND orders.date >=
'20130501' AND orders.date < '20130531' GROUP BY
category.categoryname ORDER BY category.categoryname`
When I execute this in Microsoft SQL server, it loads a bunch of records.
However, in Rails when I try to do this:
query_for_category_bottles_volume_totalsales = "SELECT category.categoryname,
sum(lineitems.qty) as totalSales, sum(lineitems.qty*size.sizeship) as volume,
sum(lineitems.qty*lineitems.purcprice) AS totmerchandise FROM category, products,
orders, shipments, lineitems, size WHERE category.categoryid = products.categoryid
AND products.productid = lineitems.productid AND lineitems.posshipid =
shipments.posshipid AND shipments.posorderid = orders.posorderid AND
size.sizeid = products.size AND category.categoryid NOT IN (77,79) AND
orders.orderstatus in (1,4,5) AND orders.ordercomplete = 1 AND numbotincase > 0
AND orders.date >= '20130501' AND orders.date < '20130531' GROUP BY
category.categoryname ORDER BY category.categoryname"
category_bottles_volume_totalsales = ActiveRecord::Base.connection.execute(query_for_category_bottles_volume_totalsales)
This happens not only for this query, but something simple like this as well: ActiveRecord::Base.connection.execute("select * from orders where id = 987;"). However, in the console when I do Order.find(987) it returns a record.
So the solution was to change the way ActiveRecord was making the query.
database = ActiveRecord::Base.connection
query_for_category_bottle_casecost_case_price = "SELECT ..."
category_bottle_casecost_case_price = database.select_all(query_for_category_bottle_casecost_case_price)

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

Resources