converting sql with multilevel joins to rails activerecord expression - sql

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

Related

transform SQL query to rails query with many to many relations, what is the best practice?

Hi this is my data model:
and I want to transform this query to the activeRecord API
select c.id, c.name, c.phone, c.created_at,c.updated_at,s.status
from contacts c,contacts_lists cl, contactstatuses s
where cl.list_id = ? and s.company_id = ? and c.id = cl.contact_id and c.id = s.contact_id
What would be the best way to do that? .... also what is the best practice here?, run the sql plain or use the activeRecord API
You can use joins to join together all the tables you need.
I asume you have set all the relations in your models and that your models are named Contact, Contactstatus and ContactList.
Then you should be able to build a query like this one:
select c.id, c.name, c.phone, c.created_at, c.updated_at, s.status
from contacts c
JOINS contacts_lists cl ON cl.contact_id = c.id
JOINS contactstatuses s ON s.contact_id = c.id
where cl.list_id = ?
and s.company_id = ?;
With the following code:
# I filled the id values (questionmarks) with a 1
Contact.select(:id, :name, :phone, :created_at, :updated_at, Contactstatus.arel_table[:status]).joins(:contacts_lists, :contactstatuses).where(contacts_lists: { list_id: 1 }, contactstatuses: { company_id: 1 })

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.

Converting methods in model to scope : Rails3

I have two methods defined in my model Project working correctly.
1. def completed(user)
Project.find_by_sql(["Select p.id from bids b LEFT JOIN tasks t ON b.task_id=t.id LEFT JOIN projects p ON p.id = t.project_id where(b.bidder_id=? and b.status=? and p.status=?) group by p.id", user.id, 'ACCEPTED', 'COMPLETE']).count
end
2. def current(user)
Project.find_by_sql(["Select p.id from bids b LEFT JOIN tasks t ON b.task_id=t.id LEFT JOIN projects p ON p.id = t.project_id where(b.bidder_id=? and b.status=? and p.status in ('LAUNCHED', 'CONFIRM', 'STAFFED', 'OVERDUE')) group by p.id", user.id, 'ACCEPTED']).count
end
When I am converting these two into scope in Project model as
1. `scope :completed, proc{|user| joins("LEFT JOIN tasks t ON t.project_id=projects.id LEFT JOIN bids b ON b.task_id=t.id where(b.bidder_id='#{user.id}' and b.status='ACCEPTED' and p.status='#{COMPLETE_STATUS}')").count("DISTINCT p.id")}`
2. `scope :current, proc{|user| joins("LEFT JOIN tasks t ON t.project_id=projects.id LEFT JOIN bids b ON b.task_id=t.id where(b.bidder_id='#{user.id}' and b.status='ACCEPTED' and p.status IN ('#{LAUNCHED_STATUS}', '#{CONFIRM_STATUS}', '#{STAFFED_STATUS}', '#{OVERDUE_STATUS}')) group by p.id").count("DISTINCT p.id")}`
I am getting error as : ActionView::Template::Error (PG::Error: ERROR: missing FROM-clause entry for table "p"
Please suggest me, how to write these two scope statements correctly. Thanks.
I don't think a scope is the better solution to result a count because a scope is used in order to have a filtered list of objects.
Anyway, your error seems to come from you try to call a table called "p". "p" doesn't exist because this is an alias in your method that you haven't declare in your scope.
To solve this you just need to replace "p" to "projects".
scope :completed, proc{|user| joins("LEFT JOIN tasks t ON t.project_id=projects.id LEFT JOIN bids b ON b.task_id=t.id where(b.bidder_id='#{user.id}' and b.status='ACCEPTED' and projects.status='#{COMPLETE_STATUS}')").count("DISTINCT projects.id")}
scope :current, proc{|user| joins("LEFT JOIN tasks t ON t.project_id=projects.id LEFT JOIN bids b ON b.task_id=t.id where(b.bidder_id='#{user.id}' and b.status='ACCEPTED' and projects.status IN ('#{LAUNCHED_STATUS}', '#{CONFIRM_STATUS}', '#{STAFFED_STATUS}', '#{OVERDUE_STATUS}')) group by projects.id").count("DISTINCT projects.id")}
I hope this help

Multiple join query in a scope/named_scope

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

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