Multiple join query in a scope/named_scope - sql

Is it possible to create a named_scope from the following query:
SELECT g.*, b.*
FROM users u
LEFT JOIN band_users bu ON u.id = bu.uid
LEFT JOIN bands b ON bu.bid = b.id
LEFT JOIN bands_gigs bg ON b.id = bg.bid
LEFT JOIN gigs g ON bg.gid = g.id
WHERE u.id = 1
I am struggling to do it, is it possible to represent multiple :joins in a named_scope or rails 3 scope?
Thanks

Yes, this can be done with:
class Band < ActiveRecord::Base
has_and_belongs_to_many :gigs
...
end
class User < ActiveRecord::Base
has_and_belongs_to_many :bands
scope :my_scope, joins(:bands => :gigs)
...
end

Related

How to chain joins in Sequel as in ActiveRecord

I have a model "MyModel" with the following relationships:
Class MyModel
#if MyModel < ActiveRecord::Base
has_one a
has_one b
#if MyModel < Sequel::Model
many_to_one a
many_to_one b
end
With ActiveRecord:
MyModel.joins(:a, :b)
or
MyModel.joins(:a).joins(:b)
gives the following SQL:
SELECT * FROM my_models INNER JOIN bs ON bs.id = my_models.b_id INNER JOIN as ON a.id = my_models.a_id
But with Sequel:
MyModel.join(:as, id: :a_id).join(:bs, id: :b_id)
gives the following SQL:
SELECT * FROM my_models INNER JOIN bs ON bs.id = my_models.b_id INNER JOIN as ON a.id = bs.a_id
Why does join use the last joined table's name? How do I get the same SQL generated by ActiveRecord in Sequel?
By default in Sequel's join, the implicit qualifier is the last joined table.
So,
MyModel.join(:as, id: :a_id).join(:bs, id: :b_id)
equals:
MyModel.join(:as, id: :a_id).join(:bs, id: Sequel[:as][:b_id])
Use an explicit qualifier:
MyModel.join(:as, id: :a_id).join(:bs, id: Sequel[:my_models][:b_id])
This is from the official Sequel IRC chat with #jeremyevans.

All columns in join in ruby on rails

class Category < ActiveRecord::Base
has_many :articles
end
class Article < ActiveRecord::Base
belongs_to :category
has_many :comments
has_many :tags
end
Category.joins(:articles) in rails equivalently
SELECT categories.* FROM categories
INNER JOIN articles ON articles.category_id = categories.id
if i want have rails-code is equivalently sql
SELECT * FROM categories
INNER JOIN articles ON articles.category_id = categories.id
what should I do?
Category.select('*').joins(:articles)
or
categoyes = Category.select('*').includes(:articles)
and
categoyes.each do |category|
puts category.articles
end
Try to use select method
categories = Category.select('*').joins(:articles)
categories.first.some_column_from_articles
I use this method
Category.connection.select_all("SELECT * FROM categories
INNER JOIN articles ON articles.category_id = categories.id").to_a

converting sql with multilevel joins to rails activerecord expression

'SELECT "complaints".* FROM "complaints" INNER JOIN "machines" ON "machines"."id" = "complaints"."machine_id" INNER JOIN "mclns" ON "machines"."mcln_id" = "mclns"."id" ORDER BY "mclns"."response_time" ASC'
I need the above sql to be converted to Active record statement
complaint model does not know about mcln
it has to go through machine
Complaint.joins(:machine=>:mcln) gives
SELECT "complaints".* FROM "complaints" INNER JOIN "machines" ON "machines"."id" = "complaints"."machine_id" INNER JOIN "mclns" ON "mclns"."machine_id" = "machines"."id"
but I need
SELECT "complaints".* FROM "complaints" INNER JOIN "machines" ON "machines"."id" = "complaints"."machine_id" INNER JOIN "mclns" ON "machines"."mcln_id" = "mclns"."id"
Update:
A machine can have a mcln and also many machines can have same mcln.
I have implemented it by using
has_one :mcln on Machine model and
belongs_to :machine on Mcln
And I'm not sure if thats correct implementation.
Your association is incorrect. If many machines can have the same mcln then the machines table should have a mcln_id column and your classes should look like
class Machine < AR::Base
belongs_to :mcln
end
class Mcln < AR::Base
has_many :machines
end
Then Complaint.joins(:machine => :mcln) should generate the SQL you want
try out this :
SELECT complaints.*,machines.*,mclns.* FROM `complaints`
INNER JOIN `machines`
ON ( machines.id = complaints.machine_id )
INNER JOIN `mclns`
( ON machines.mcln_id = mclns.id )

Rails3 ActiveRecord - Fetching all records that have A and B (through a has_many relationship)

I have the following model relationships:
class Article < ActiveRecord::Base
has_many :tags, :through => :article_tags
end
class ArticleTag < ActiveRecord::Base
belongs_to :tag
belongs_to :article
end
class Tag < ActiveRecord::Base
has_many :articles, :through => :article_tags
end
Now I want to load all the Articles that are tagged with both tag "A" and tag "B".
I'm pretty new to Rails and it has been a few years since I did any serious web-dev/SQL work but for the life of me I can't figure out how one would construct an ActiveRecord query to do this.
One way to do it in native SQL would be as follows:
SELECT a.* FROM articles a
INNER JOIN article_tags at ON at.article_id = a.id
INNER JOIN tags t ON at.tag_id = t.id
WHERE t.name = 'A'
intersect
SELECT a.* from articles a
INNER JOIN article_tags at ON at.article_id = a.id
INNER JOIN tags t ON at.tag_id = t.id
WHERE t.name = 'B'
Now that might not be the most efficient SQL but it works. At this late hour I can't think of a better solution in SQL.
Update: Further investigation has lead me to this SQL:
SELECT a.* FROM article_tags at, articles a, tags t
WHERE at.tag_id = t.id
AND (t.name = 'A' OR t.name = 'B')
AND a.id = at.vehicle_id
GROUP BY a.id HAVING COUNT(a.id) = 2
This seems like simpler (less verbose) SQL but in no more efficient. However, I can probably more easily construct a "find_by_sql" ActiveRecord query using this SQL.
Any guidance on how to best do this sort of query with ActiveRecord (preferably without resorting to SQL) would be greatly appreciated.
I ended up solving my own problem. I constructed a named scope as follows:
scope :tagged, lambda { |tag, *tags|
tags = tags.unshift(*tag)
joins(:tags).
where("lower(tags.name) = '" + tags.uniq.collect{ |t| t.to_s.downcase }.join("' OR lower(tags.name) = '") + "'").
group("articles.id").
having("count(articles.id) = #{tags.count}")
}
So, now I can do this in my controllers:
#tagged_articles = Article.tagged('A', 'B', 'C')
And #tagged_articles will include all the articles tagged with all of the tags 'A', 'B', and 'C'.

MySQL 4.0 query help double join in lieu of subqueries

select u.user, g.group, u2g.something
from users, groups, u2g
where users.u = u2g.u and groups.g = u2g.g
that returns data like this:
user, group, something
----------------------
1 , 3, a
1 , 5, b
2 , 3, c
3 , 3, d
4 , 5, e
now I would like to limit this query in such a way that it only shows users which are both in groups 3 and 5 - so it would only return {1,3, a} , {1,5, b} for my example data.
edit: I added another column to the data because there may be an incorrect solution using a group by.
edit2: sorry, I was misled by documentation. MySQL 4.0 does not support subqueries :(
edit3: This SQL will be generated programatically for any number of groups (well, up to 20 in current specification) so I would like to avoid solutions that give me too much additional coding to do. If a solution will not be found, I will just modify the resulting .Net 1.1 DataTable, but I would like to avoid that if possible.
edit4: any new idea? Perhaps one without subqueries that includes IN (3,5)?
Using double join with groups-table will give you the correct result:
select u.user, u2g.something
from users
INNER JOIN u2g ON users.u = u2g.u
INNER JOIN groups g1 ON u2g.g = g1.g AND g1.group = 3
INNER JOIN groups g2 ON u2g.g = g2.g AND g2.group = 5
/* try this for two rows, one for each group */
INNER JOIN groups ON u2g.g = groups.g
However, this doesn't exactly match your request, that you want two rows, one for each group. This will only give you one row, you might be able to join it once more with groups therefor rendering two rows.
Another example (if you're selecting using the same groupID that you map against ):
SELECT u.uID, gm.something
FROM cdcms_users u
inner join cdcms_group_memberships gm1 on gm1.uID = u.uID AND gm1.gID = 32
inner join cdcms_group_memberships gm2 on gm2.uID = u.uID AND gm2.gID = 33
select u.user, g.group, u2g.something
from users u, groups g, u2g
where u.user = u2g.user and g.group = u2g.group
where exists
(select 1
from u2g u2g2
where u2g2.user=u.user and u2g2.group in(3,5))
Something along these lines?
select u.[user], g.group
from u
inner join ug on ug.userid = u.id
inner join g on g.id = ug.groupid
inner join
(
select ug.userid
from ug
where ug.groupid in (1,2)
group by ug.userid
having count(*) = 2
) sub on sub.userid = u.id
-Edoode
Quite hideous non-general solution that results in two rows in Oracle:
select users.u, groups.g
from users , groups, u2g, groups g2, u2g u2g2
where users.u = u2g.u
and users.u = u2g2.u
and groups.g = u2g.g
and g2.g = u2g2.g
and (groups.g in (3,5) and g2.g in (3,5) and groups.g <> g2.g)
;
Why is groups used in the query? Its only accessed field (g) exists in u2g. I imagine you probably want to bring back a boatload of stuff from there as well.
In order to get the sort of resultset you describe without the use of subqueries you end up with a real mess: a quadratic explosion of query text!
You'll need something of the following form:
select users.u, groups.g, u2g0.something
from users u, groups g, u2g u2g0, u2g u2g1
where groups.g = 3
and users.u = u2g0.u
and u2g0.g = 3
and users.u = u2g1.u
and u2g1.g = 5
union all
select users.u, groups.g, u2g1.something
from users u, groups g, u2g u2g0, u2g u2g1
where groups.g = 5
and users.u = u2g0.u
and u2g0.g = 3
and users.u = u2g1.u
and u2g1.g = 5
Since this is a programmatically generated query, I'll use a web-page-scripting-like notation here to describe the query construction. I will also make the rash and unwarranted simplifying assumption that the group identifiers are not a potential SQL-injection attack vector. :-)
<% for(int i = 0; i < requiredGroups.Length; i++) { %>
<% if(i > 0) { %>
union all
<% } %>
select users.u, groups.g, u2g<%=i%>.something
from users u, groups g
<% for(int j = 0; j < requiredGroups.Length; j++) { %>
, u2g u2g<%=j%>
<% } %>
where groups.g = <%=requiredGroups[i]%>
<% for(int j = 0; j < requiredGroups.Length; j++) { %>
and users.u = u2g<%=j%>.u
and u2g<%=j>.g = <%=requiredGroups[j]%>
<% } %>
<% } %>