Ruby on rails mysql searches - mysql

I am totally new to Ruby on Rails and I am trying to search through some relational database tables, I am trying to search for a given ID number in a Customer table then from the results look at who the sales_rep for that customer is. With this
#salesrepcust = Customer.find(:all, :conditions => ["id = ?",#data])
I am able to get back the correct customer given there ID number but I dont see how in ruby on rails to then pull from those results just one column value, in this it would be the value for sales_rep, and then use that as my #result for
#salesrepcustdata = Salesrep.find(:all, :conditions => ["id = ?", #result])
I have searched for this but i guess im not wording it correctly because i am not able to find anything specifically on this, can anyone help?

It's pretty straightforward to select a single column; you can try something like this:
#salesrepcustids = Customer.where(id: #data).select(:id)
This will generate a SELECT id FROM ... statement.
And now you can do this:
#salesrepcustdata = Salesrep.where(id: #salesrepcustids)
This will generate an SELECT...IN statement with those ids.
(You might find it easier to set up proper ActiveRecord has_many and belongs_to relationships in your models, or whatever relationship is appropriate.)

Assuming the sales rep is represented in the Customer table as sales_rep_id you can just do:
Salesrep.find(Customer.find(#data).sales_rep_id)
The find method assumes you're looking for id and if there's just one item with that id, there's no need to specify :all.
This is all discussed in http://guides.rubyonrails.org/active_record_querying.html

That Customer query could be simplified to just:
#customer = Customer.find(#data)
You don't mention if you've setup a relationship between Customer and Salesrep but here goes:
# app/models/customer.rb
class Customer < ActiveRecord::Base
belongs_to :salesrep, class_name: 'Salesrep' # => the customers table should have a salesrep_id column
end
# app/models/salesrep.rb
class Salesrep < ActiveRecord::Base
has_many :customers
end
customer_id = 1
#customer = Customer.find(customer_id)
#salesrep = #customer.salesrep
# from the other way, assuming you need both the salesrep and customer:
salesrep_id = 10
#salesrep = Salesrep.find(salesrep_id)
# the following will only find the customer if it's owned by the Salesrep
#customer = #salesrep.customers.find(customer_id)

Related

Run mysql query from a controller in ruby on rails

I need run a mysql query from the controller but I do not know how to connect to db.
this is my method:
def set_dcs
sql="select employees.nombre, employees.apellido_paterno, employees.apellido_materno from employees, activities, field_tests
where activities.field_test_id=field_tests.id and employees.id=activities.dcs_id and field_tests.id=id"
end
How do I get the result of the query?
How could I do the same query but with the ActiveRecord?
You can execute raw SQL through ActiveRecord::Base.connection but I rarely recommend it and certainly not in this case, but for edification purposes
def set_dcs
sql= <<SQL
select employees.nombre, employees.apellido_paterno, employees.apellido_materno
from employees, activities, field_tests
where activities.field_test_id=field_tests.id and employees.id=activities.dcs_id and field_tests.id=id
SQL
ActiveRecord::Base.connection.exec_query(sql)
end
I was not sure what the trailing id is in reference to and it will raise a SQL error due to it's ambiguity. I am going to assume it is a parameter and the primary search condition where as the rest are table joins.
That being said since you are using rails these can be true associations and joins resulting in far more readable controller code e.g. Model Definitions
class Employee < ActiveRecord::Base
has_many :activities, foreign_key: :dcs_id
has_many :field_tests, through: :activities
end
class FieldTest < ActiveRecord::Base
has_many :activities
end
class Activity < ActiveRecord::Base
belongs_to :employee, foreign_key: :dcs_id
belongs_to :field_test
end
Then the controller is simply
Employee.
select("employees.nombre, employees.apellido_paterno, employees.apellido_materno").
joins(:field_tests).where(field_tests: {id: SOME_ID})
the resulting SQL will be similar to
SELECT
employees.nombre,
employees.apellido_paterno,
employees.apellido_materno
FROM
employees
INNER JOIN activities ON employees.id = activities.dcs_id
INNER JOIN field_tests ON activities.field_test_id = field_tests.id
WHERE
field_tests.id = SOME_ID
And this will return a collection of Employee Objects rather than an ActiveRecord::Result (returned from ActiveRecord::Base.connection.exec_query) which is more similar to an Array than anything else.

Rails 5 - Activerecord association query

I have two models associated with each other as follows.
class Comment < ApplicationRecord
belongs_to :user
end
class User < ActiveRecord::Base
has_many :comments
end
Following record query
comments_list = Comment.where(:post_id => post_id, :is_delete => false).joins(:user).select('comments.*,users.*')
Generates the following mysql query in logger
SELECT comments.*,users.* FROM `comments` INNER JOIN `users` ON `users`.`id` = `comments`.`user_id` WHERE `comments`.`post_id` = '81' AND `comments`.`is_delete` = 0.
This seems generating very ligitimate query, but comments_list object contain columns only from comments table.
Thanks
It depends on what you want to do, if you want to display the username next to the comment, Mert B.'s answer is fine, all you have to do is include(:user) and the users from the comment list will be fetched along when you do something like this:
comments_list = Comment.where(:post_id => post_id, :is_delete => false).joins(:user).select('comments.*,users.*')
comments_list.each do |comment|
puts "#{comment.text} by #{comment.user.name}"
end
Or maybe if you want only users who have at least one comment, you can always select users from the user_ids on the comments table:
User.where(id: Comment.select(:user_id))

Optimized way to retrieve IDs through has_many :through association

I have 3 model as following :
(I'm also describing the database structure in case of anyone not familiar with RubyOnRails is able to help me)
Thread.rb
class Thread
has_many :thread_profils
has_many :profils, :through => :thread_profils
end
Table threads
integer: id (PK)
ThreadProfil.rb
class ThreadProfil
belongs_to :thread
belongs_to :profil
end
Table thread_profils
integer: id (PK)
integer: thread_id (FK)
integer: profil_id (FK)
Profil.rb
class Profil
end
Table profils
integer: id (PK)
In one of my controllers I am looking for the most optimized way to find the Threads IDs that has include exactly two profils (the current one, a some other one) :
I got my current_profil.id and another profil.id and I can't figure out a simple way to get that collection/list/array of Thread.id, while processing the fewer SQL request.
For now the only solution I found is the following one, which I don't consider as being "optimized" at all.
thread_profils = ThreadProfil.where(:profil_id => current_profil.id)
thread_ids = thread_profils.map do | association |
profils = Thread.find(association.thread_id).profils.map do | profil |
profil.id if profil.id != current_profil.id
end.compact
if (profils - [id]).empty?
association.thread_id
end
end.compact
That is processing the following SQL queries :
SELECT `thread_profils`.* FROM `thread_profils` WHERE `thread_profils`.`profil_id` = [current_profil.id]
And for each result :
SELECT `threads`.* FROM `threads` WHERE `threads`.`id` = [thread_id] LIMIT 1
SELECT `profils`.* FROM `profils` INNER JOIN `thread_profils` ON `profils`.`id` = `thread_profils`.`profil_id` WHERE `thread_profils`.`thread_id` = [thread_id]
Is there any light way to do that, either with rails or directly with SQL ?
Thanks
I found the following query in sql:
SELECT array_agg(thread_id) FROM "thread_profils" WHERE "thread_profils"."profil_id" = 1 GROUP BY profil_id HAVING count(thread_id) =2
note: array_agg is a postgres aggregate function. Mysql has group_concat which would give you a comma-delimited string of IDs instead of an array.
This sql was generated by the following Rails code:
ThreadProfil.select('array_agg(mythread_id)').where(profil_id: 1).group(:profil_id).having("count(thread_id) =2").take
This generates the right query, but the result is not meaningful as a ThreadProfil - still, you might be able to work further with this to get what you want.

Query intersection with activerecord

I'd really like to do the following query with the help with active record
(select *
from people p join cities c join services s
where p.city_id = c.id and p.id = s.person_id and s.type = 1)
intersect
(select *
from people p join cities c join services s
where p.city_id = c.id and p.id = s.person_id and s.type = 2)
Problem is, first of all, mysql doesn't support intersect. However, that can be worked around of. The thing is that I can get active record to output anything even close to that.
In active record the best I could do was to issue multiple queries then use reduce :& to join them, but then I get an Array, not a Relation. That's a problem for me because I want to call things like limit, etc. Plus, I think it would be better to the intersection to be done by the database, rather than ruby code.
Your question is probably solvable without intersection, something like:
Person.joins(:services).where(services: {service_type: [1,2]}).group(
people: :id).having('COUNT("people"."id")=2')
However the following is a general approach I use for constructing intersection like queries in ActiveRecord:
class Service < ActiveRecord::Base
belongs_to :person
def self.with_types(*types)
where(service_type: types)
end
end
class City < ActiveRecord::Base
has_and_belongs_to_many :services
has_many :people, inverse_of: :city
end
class Person < ActiveRecord::Base
belongs_to :city, inverse_of: :people
def self.with_cities(cities)
where(city_id: cities)
end
def self.with_all_service_types(*types)
types.map { |t|
joins(:services).merge(Service.with_types t).select(:id)
}.reduce(scoped) { |scope, subquery|
scope.where(id: subquery)
}
end
end
Person.with_all_service_types(1, 2)
Person.with_all_service_types(1, 2).with_cities(City.where(name: 'Gold Coast'))
It will generate SQL of the form:
SELECT "people".*
FROM "people"
WHERE "people"."id" in (SELECT "people"."id" FROM ...)
AND "people"."id" in (SELECT ...)
AND ...
You can create as many subqueries as required with the above approach based on any conditions/joins etc so long as each subquery returns the id of a matching person in its result set.
Each subquery result set will be AND'ed together thus restricting the matching set to the intersection of all of the subqueries.
UPDATE
For those using AR4 where scoped was removed, my other answer provides a semantically equivalent scoped polyfil which all is not an equivalent replacement for despite what the AR documentation suggests. Answer here: With Rails 4, Model.scoped is deprecated but Model.all can't replace it
I was struggling with the same issue, and found only one solution: multiple joins against the same association. This may not be too rails-ish since I'm constructing the SQL string for the joins, but I haven't found another way. This will work for an arbitrary number of service types (cities doesn't seem to factor in, so that join was omitted for clarity):
s = [1,2]
j = ''
s.each_index {|i|
j += " INNER JOIN services s#{i} ON s.person_id = people.id AND s#{i}.type_id = #{s[i]}"
}
People.all.joins(j)

How can I calculate the size of a table using a condition

How can I count the table size in MySQL or PosgreSQL using a restriction? for example: WHERE organisation_id=1
In PostgreSQL you can't. Data is stored in blocks (normaly 8kb each) and you have no idea in what block the records with "organisation_id=1" are stored. When all records are in one block, it's 8kb. When 100 records are stored in 100 blocks, it's 800kb, but that includes other records as well.
You could do some magic with the hidden column "ctid", but that's a lot of work, not accurate and not very usefull as well. Just use pg_relation_size() to get the entire table, that's what you need. If you need the TOAST-table includes, use pg_total_relation_size().
ModelName.count(:conditions => {:organisation_id => 1})
If you have associations set up in your models as follows:
class Organisation < ActiveRecord::Base
has_many :members
end
class Member < ActiveRecord::Base
belongs_to :organisation
end
then you can find how many members are in organisation 1 with the following:
organisation = Organisation.find(1)
n_members = organisation.members.size
Thanks to ActiveRecord, this kind of thing is database independent.
It depends on your table structure. If you have all fixed width fields you can get pretty close. If you have variable width fields the best you will do is an estimate. This is for postgres.
SELECT pg_total_relation_size('my_table') * i / cnt
FROM (
SELECT SUM(CASE WHEN organization_id = 1 THEN 1 ELSE 0 END) AS i,
COUNT(1) AS cnt
FROM my_table
) sub
Even shorter version
ModelName.count(:organisation_id => 1)