Mysql equivalent rails query - ruby-on-rails

I am new to rails. What is the equivalent rails query for the following sql query?
mysql> select a.code, b.name, b.price, b.page from article a inner join books b on a.id =b.article_id;
I am trying
Article.joins(:books).select("books.name, books.price, books.page, articles.code")
The active record relation returns only table one data
=> #<ActiveRecord::Relation [#<Article id: 1, code: "x", created_at: "2014-11-12 13:28:08", updated_at: "2014-11-14 04:16:06">, #<Article id: 2, code: "y", created_at: "2014-11-12 13:28:08", updated_at: "2014-11-14 04:00:16">]>
What is the solution to join both table?

You normally don't really query like that directly with Rails. Instead, you'd set up your models and use other associated models to achieve this. If speed is an issue, you can use eager loading. If you absolutely need exactly this join, it's:
class Article < ActiveRecord::Base
has_many :books
scope :with_books, lambda {
joins(:books)
.select('articles.code, books.name, books.price, books.page')
}
end
class Book < ActiveRecord::Base
belongs_to :article
end
But this is not so useful. It generates the join you want, but retrieving the book details like that won't be fun. You could do something like:
a = Article.with_books.where("books.name = 'Woken Furies'").first
a.code
And that should give you the article code. Depending on what you need, a better way could be to remove the scope from the Article class and instead query like:
b = Book.where(name: 'Woken Furies')
.joins(:article)
.where("articles.code = 'something'")
b = Book.where("name = 'Woken Furies' AND articles.code = 'something'")
.joins(:article)
Both of these queries should be equivalent. You can get from one associated record to the other with:
book = b.first
article_code = book.article.code
I'm not sure what you need to accomplish with the join, but I think you might get more beautiful code by using plain ActiveRecord. If you need the speed gain, avoid n+1 problems, etc., it might make sense to write those joins out by hand.
I hope I understood your question correctly.
There's more about joining in the Rails guides:
http://guides.rubyonrails.org/active_record_querying.html#joining-tables
Update: You can use pluck if you need to retrieve e.g. just the code and the name:
Article.with_books
.where("books.name = 'Woken Furies'")
.pluck('articles.code, books.name')

Related

Aggregate values on an attribute returned by a has_many through

Data structure is as follows:
class Job
has_many job_sections
has_many job_products, through job_sections
end
class JobSection
belongs_to job
has_many job_products
end
class JobProduct
belongs_to product
belongs_to job_section
end
When I call job.job_products i could end up with something like this:
#<JobProduct:0x007ff4128b0ca0
id: 18133,
product_id: 250,
quantity: 3,
frozen_cache: {},
discount: 0.0,
#<JobProduct:0x007ff4128b00c0
id: 18134,
product_id: 250,
quantity: 1,
frozen_cache: {},
discount: 0.0]
As you can see the product_id is identical in both instances.
How do I merge the contents of the arrays by product id so I retrieve and act on them as aggregated values?
In a way, I need to be able to act on job products by their product_id rather than their id.
Effectively the result being something like this...
[#<SomeFancySeerviceObjectMaybe?
product_id: 250,
quantity: 4,
frozen_cache: {},
discount: 0.0]
Do I opt for a little Plain Old Ruby Object to handle them all, or do I have to rethink the architecture of this, or is there (hopefully!) a bit of Rails secret sauce that than can help me out?
*FYI Job Section is a recent addition to the architecture, and I don't think its has been particularly well thought through. However, I can't spend too much time reversing what's already in place.
This set up isn't ideal, I'm probably the sixth dev in as many years to start picking this apart.
Your suggestions are most welcome. Thank you
In SQL this would be something like this:
SELECT SUM(quantity)
FROM job_products
WHERE product_id = 250
GROUP BY product_id
You can do that in ActiveRecord too. If you just want an integer, you can use pluck:
total_quantity = job.job_products.
group(:product_id).
pluck("SUM(job_products.quantity)").
first
You can also pluck several columns if you want (in Rails 4+), which is why it returns an array. So if you want average discount at the same time, it's easy.
If you would prefer a JobProduct instance, you can get that too, but in your case a lot of the attributes will be nil because of the grouping. But you can say:
summary = job.job_products.
group(:product_id).
select("SUM(job_products.quantity) AS total_quantity").
first
And you'll get a read-only JobProduct with an extra attribute named total_quantity. So you can do summary.total_quantity. But because of the grouping, summary will have a nil id, discount, etc. Basically it will only have attributes matching the things you select. This is a little weird, but sometimes it lets you write methods that work both on "real" JobProducts and for these summary versions.

Rails Eager loading explanation

I have simplified my project to this so i can understand the better the eager loading part of RoR 3.2.13 . These are my classes:
class Person < ActiveRecord::Base
attr_accessible :name
has_many :posts
end
And
class Post < ActiveRecord::Base
attr_accessible :name, :person_id
belongs_to :person
end
When i do something like
people_data = Person.includes(:posts)
IRB shows the following SQL:
Person Load (1.3ms) SELECT `people`.* FROM `people`
Post Load (0.7ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`person_id` IN (1, 2)
And the resulting object its like:
=> [#<Person id: 1, name: "Ernie">, #<Person id: 2, name: "Bert">]
Notice that there are only 2 person objects, no posts. How can i get a simple data structure with person AND its posts. I want to do this in a single instruction, i dont want to do a foreach in the people array.
Im expecting something like this:
[#<Person id: 1, name: "Ernie">, [#<Array 0 => #<Post id:1, name: "Hi">, 1 => #<Post id:2, name: "Hello"> > ....
Eager loading is working. It's retrieving all the posts eagerly (in one query). Eager loading does not construct an array with all posts and persons.
# With eager loading
people_data = Person.includes(:posts) # 2 queries: 1 for persons, 1 for posts
people_data.first.posts.each do |post|
# No queries
end
# Without eager loading
people_data = Person.all # 1 query for persons
people_data.first.posts.each do |post|
# Query for each post
end
Note that in both cases people_data will contain (a similar looking) ActiveRecord::Relation object. (It's not an array!) You will only see the benefit of the eager loading when you start using the associated records.
Unless you have a special reason for wanting to build an array with all persons and posts, you should use eager loading like it was meant to be used. This is more efficient than trying to build your own array.
You can map your results to receive expected array
Person.includes(:posts).all.map{|person| [person, person.posts]}
The result of your query eager loads the posts as the result of the Person#posts method. I'm not quite clear on the format of the output you are expecting. If you simply want two arrays, one of people and one of all posts by those people you can split it into two queries.
#people = Person.where(...)
#posts = Post.where(person_id: #people)
This is the same query done by eager loading, but it will return posts in their own array.

How to write complex query in Ruby

Need advice, how to write complex query in Ruby.
Query in PHP project:
$get_trustee = db_query("SELECT t.trustee_name,t.secret_key,t.trustee_status,t.created,t.user_id,ui.image from trustees t
left join users u on u.id = t.trustees_id
left join user_info ui on ui.user_id = t.trustees_id
WHERE t.user_id='$user_id' AND trustee_status ='pending'
group by secret_key
ORDER BY t.created DESC")
My guess in Ruby:
get_trustee = Trustee.find_by_sql('SELECT t.trustee_name, t.secret_key, t.trustee_status, t.created, t.user_id, ui.image FROM trustees t
LEFT JOIN users u ON u.id = t.trustees_id
LEFT JOIN user_info ui ON ui.user_id = t.trustees_id
WHERE t.user_id = ? AND
t.trustee_status = ?
GROUP BY secret_key
ORDER BY t.created DESC',
[user_id, 'pending'])
Option 1 (Okay)
Do you mean Ruby with ActiveRecord? Are you using ActiveRecord and/or Rails? #find_by_sql is a method that exists within ActiveRecord. Also it seems like the user table isn't really needed in this query, but maybe you left something out? Either way, I'll included it in my examples. This query would work if you haven't set up your relationships right:
users_trustees = Trustee.
select('trustees.*, ui.image').
joins('LEFT OUTER JOIN users u ON u.id = trustees.trustees_id').
joins('LEFT OUTER JOIN user_info ui ON ui.user_id = t.trustees_id').
where(user_id: user_id, trustee_status: 'pending').
order('t.created DESC')
Also, be aware of a few things with this solution:
I have not found a super elegant way to get the columns from the join tables out of the ActiveRecord objects that get returned. You can access them by users_trustees.each { |u| u['image'] }
This query isn't really THAT complex and ActiveRecord relationships make it much easier to understand and maintain.
I'm assuming you're using a legacy database and that's why your columns are named this way. If I'm wrong and you created these tables for this app, then your life would be much easier (and conventional) with your primary keys being called id and your timestamps being called created_at and updated_at.
Option 2 (Better)
If you set up your ActiveRecord relationships and classes properly, then this query is much easier:
class Trustee < ActiveRecord::Base
self.primary_key = 'trustees_id' # wouldn't be needed if the column was id
has_one :user
has_one :user_info
end
class User < ActiveRecord::Base
belongs_to :trustee, foreign_key: 'trustees_id' # relationship can also go the other way
end
class UserInfo < ActiveRecord::Base
self.table_name = 'user_info'
belongs_to :trustee
end
Your "query" can now be ActiveRecord goodness if performance isn't paramount. The Ruby convention is readability first, reorganizing code later if stuff starts to scale.
Let's say you want to get a trustee's image:
trustee = Trustee.where(trustees_id: 5).first
if trustee
image = trustee.user_info.image
..
end
Or if you want to get all trustee's images:
Trustee.all.collect { |t| t.user_info.try(:image) } # using a #try in case user_info is nil
Option 3 (Best)
It seems like trustee is just a special-case user of some sort. You can use STI if you don't mind restructuring you tables to simplify even further.
This is probably outside of the scope of this question so I'll just link you to the docs on this: http://api.rubyonrails.org/classes/ActiveRecord/Base.html see "Single Table Inheritance". Also see the article that they link to from Martin Fowler (http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html)
Resources
http://guides.rubyonrails.org/association_basics.html
http://guides.rubyonrails.org/active_record_querying.html
Yes, find_by_sql will work, you can try this also:
Trustee.connection.execute('...')
or for generic queries:
ActiveRecord::Base.connection.execute('...')

Best Rails 3.1 practice to complex query

Given:
(models):
Contract (has_many :invoices, belongs_to :user)
Invoice (belongs_to :contract)
This way, for example:
my_contracts = Contract.where(user_id: current_user.id) #=> [#<Contract id: 1, title: "1">, #<Contract id: 2, title: "2">]
In this case we have two Contracts for User. And each of Contracts have multiple number of Invoices.
Now we need to gather all Invoices for each of contracts and sort them by 'updated_at'.
Something like:
all_invoices = my_contracts.map{|i| i.invoices.sort_by(&:updated_at)}
but using ActiveRecord.
How it could be done right?
The way you are doing it is not bad, just use includes to get eager loading of the invoices instead of lazy (n+1) loading
contracts = Contract.where(:user_id => current_user.id).includes(:invoices)
# or this might do the same => current_user.contracts.includes(:invoices)
invoices = contracts.map{|i| i.invoices }
invoices.sort_by(&:updated_at).each do |invoice|
# ....
end
try this and also what David Sulc posted, view the generated sql and experiment with the result in rails console; using joins vs includes has very different behavior, depending on situation one maybe better than the other
see also
http://guides.rubyonrails.org/active_record_querying.html#joining-tables
http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations
It should be something like
Invoice.joins(:contracts).where(user_id: current_user.id).order(:updated_at)
Arel isn't a strong suit of mine (still need to work on it), but if this doesn't work, it should at least get you closer to the answer.

Rails :include vs. :joins

This is more of a "why do things work this way" question rather than a "I don't know how to do this" question...
So the gospel on pulling associated records that you know you're going to use is to use :include because you'll get a join and avoid a whole bunch of extra queries:
Post.all(:include => :comments)
However when you look at the logs, there's no join happening:
Post Load (3.7ms) SELECT * FROM "posts"
Comment Load (0.2ms) SELECT "comments.*" FROM "comments"
WHERE ("comments".post_id IN (1,2,3,4))
ORDER BY created_at asc)
It is taking a shortcut because it pulls all of the comments at once, but it's still not a join (which is what all the documentation seems to say). The only way I can get a join is to use :joins instead of :include:
Post.all(:joins => :comments)
And the logs show:
Post Load (6.0ms) SELECT "posts".* FROM "posts"
INNER JOIN "comments" ON "posts".id = "comments".post_id
Am I missing something? I have an app with half a dozen associations and on one screen I display data from all of them. Seems like it would be better to have one join-ed query instead of 6 individuals. I know that performance-wise it's not always better to do a join rather than individual queries (in fact if you're going by time spent, it looks like the two individual queries above are faster than the join), but after all the docs I've been reading I'm surprised to see :include not working as advertised.
Maybe Rails is cognizant of the performance issue and doesn't join except in certain cases?
It appears that the :include functionality was changed with Rails 2.1. Rails used to do the join in all cases, but for performance reasons it was changed to use multiple queries in some circumstances. This blog post by Fabio Akita has some good information on the change (see the section entitled "Optimized Eager Loading").
.joins will just joins the tables and brings selected fields in return. if you call associations on joins query result, it will fire database queries again
:includes will eager load the included associations and add them in memory. :includes loads all the included tables attributes. If you call associations on include query result, it will not fire any queries
The difference between joins and include is that using the include statement generates a much larger SQL query loading into memory all the attributes from the other table(s).
For example, if you have a table full of comments and you use a :joins => users to pull in all the user information for sorting purposes, etc it will work fine and take less time than :include, but say you want to display the comment along with the users name, email, etc. To get the information using :joins, it will have to make separate SQL queries for each user it fetches, whereas if you used :include this information is ready for use.
Great example:
http://railscasts.com/episodes/181-include-vs-joins
I was recently reading more on difference between :joins and :includes in rails. Here is an explaination of what I understood (with examples :))
Consider this scenario:
A User has_many comments and a comment belongs_to a User.
The User model has the following attributes: Name(string), Age(integer). The Comment model has the following attributes:Content, user_id. For a comment a user_id can be null.
Joins:
:joins performs a inner join between two tables. Thus
Comment.joins(:user)
#=> <ActiveRecord::Relation [#<Comment id: 1, content: "Hi I am Aaditi.This is my first comment!", user_id: 1, created_at: "2014-11-12 18:29:24", updated_at: "2014-11-12 18:29:24">,
#<Comment id: 2, content: "Hi I am Ankita.This is my first comment!", user_id: 2, created_at: "2014-11-12 18:29:29", updated_at: "2014-11-12 18:29:29">,
#<Comment id: 3, content: "Hi I am John.This is my first comment!", user_id: 3, created_at: "2014-11-12 18:30:25", updated_at: "2014-11-12 18:30:25">]>
will fetch all records where user_id (of comments table) is equal to user.id (users table). Thus if you do
Comment.joins(:user).where("comments.user_id is null")
#=> <ActiveRecord::Relation []>
You will get a empty array as shown.
Moreover joins does not load the joined table in memory. Thus if you do
comment_1 = Comment.joins(:user).first
comment_1.user.age
#=> User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT 1 [["id", 1]]
#=> 24
As you see, comment_1.user.age will fire a database query again in the background to get the results
Includes:
:includes performs a left outer join between the two tables. Thus
Comment.includes(:user)
#=><ActiveRecord::Relation [#<Comment id: 1, content: "Hi I am Aaditi.This is my first comment!", user_id: 1, created_at: "2014-11-12 18:29:24", updated_at: "2014-11-12 18:29:24">,
#<Comment id: 2, content: "Hi I am Ankita.This is my first comment!", user_id: 2, created_at: "2014-11-12 18:29:29", updated_at: "2014-11-12 18:29:29">,
#<Comment id: 3, content: "Hi I am John.This is my first comment!", user_id: 3, created_at: "2014-11-12 18:30:25", updated_at: "2014-11-12 18:30:25">,
#<Comment id: 4, content: "Hi This is an anonymous comment!", user_id: nil, created_at: "2014-11-12 18:31:02", updated_at: "2014-11-12 18:31:02">]>
will result in a joined table with all the records from comments table. Thus if you do
Comment.includes(:user).where("comment.user_id is null")
#=> #<ActiveRecord::Relation [#<Comment id: 4, content: "Hi This is an anonymous comment!", user_id: nil, created_at: "2014-11-12 18:31:02", updated_at: "2014-11-12 18:31:02">]>
it will fetch records where comments.user_id is nil as shown.
Moreover includes loads both the tables in the memory. Thus if you do
comment_1 = Comment.includes(:user).first
comment_1.user.age
#=> 24
As you can notice comment_1.user.age simply loads the result from memory without firing a database query in the background.
In addition to a performance considerations, there's a functional difference too.
When you join comments, you are asking for posts that have comments- an inner join by default.
When you include comments, you are asking for all posts- an outer join.
tl;dr
I contrast them in two ways:
joins - For conditional selection of records.
includes - When using an association on each member of a result set.
Longer version
Joins is meant to filter the result set coming from the database. You use it to do set operations on your table. Think of this as a where clause that performs set theory.
Post.joins(:comments)
is the same as
Post.where('id in (select post_id from comments)')
Except that if there are more than one comment you will get duplicate posts back with the joins. But every post will be a post that has comments. You can correct this with distinct:
Post.joins(:comments).count
=> 10
Post.joins(:comments).distinct.count
=> 2
In contract, the includes method will simply make sure that there are no additional database queries when referencing the relation (so that we don't make n + 1 queries)
Post.includes(:comments).count
=> 4 # includes posts without comments so the count might be higher.
The moral is, use joins when you want to do conditional set operations and use includes when you are going to be using a relation on each member of a collection.
.joins works as database join and it joins two or more table and fetch selected data from backend(database).
.includes work as left join of database. It loaded all the records of left side, does not have relevance of right hand side model. It is used to eager loading because it load all associated object in memory. If we call associations on include query result then it does not fire a query on database, It simply return data from memory because it have already loaded data in memory.
'joins' just used to join tables and when you called associations on joins then it will again fire query (it mean many query will fire)
lets suppose you have tow model, User and Organisation
User has_many organisations
suppose you have 10 organisation for a user
#records= User.joins(:organisations).where("organisations.user_id = 1")
QUERY will be
select * from users INNER JOIN organisations ON organisations.user_id = users.id where organisations.user_id = 1
it will return all records of organisation related to user
and #records.map{|u|u.organisation.name}
it run QUERY like
select * from organisations where organisations.id = x then time(hwo many organisation you have)
total number of SQL is 11 in this case
But with
'includes' will eager load the included associations and add them in memory(load all associations on first load) and not fire query again
when you get records with includes like
#records= User.includes(:organisations).where("organisations.user_id = 1")
then query will be
select * from users INNER JOIN organisations ON organisations.user_id = users.id where organisations.user_id = 1
and
select * from organisations where organisations.id IN(IDS of organisation(1, to 10)) if 10 organisation
and when you run this
#records.map{|u|u.organisation.name}
no query will fire

Resources