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.
Related
Ver 14.14 Distrib 5.1.73
activerecord (4.1.14)
I have a trade model that belongs to a lender and borrower. I want to find all uniq counterparties to an institution's trades in one SQL query. The query below works, but only because I flatten & unique-ify the array after the SQL query:
Trade.where("borrower_id = :id OR lender_id = :id", id: institution.id).uniq.pluck(:lender_id, :borrower_id).flatten.uniq
(I know this includes the institution itself, so we normalize after with [1,2,3,4] - [1])
But what I'd like to do is use a Group By clause or something so that my SQL query handles the flatten.uniq part.
The below does not work because it returns a nested array of unique combinations of lender_id and borrower_id:
Trade.where("borrower_id = :id OR lender_id = :id", id: institution.id).group(:lender_id, :borrower_id).uniq.pluck(:lender_id, :borrower_id)
=> [[1,2], [1,3], [2,3]]
I want a flat array of JUST unique ids: [1,2,3]
Any ideas? Thanks!
I don't understand what you're trying to, or why you'd want to include a GROUP BY clause in the absence of any aggregating functions.
FWIW, a valid query might look like this...
SELECT DISTINCT t.lender_id
, t.borrower_id
FROM trades t
WHERE 28 IN(t.borrower_id,t.lender_id);
I have this method
class User < ActiveRecord::Base
def self.search(q, with_address)
klass = self
klass = klass.includes(:address) if with_address
klass.where("users.name LIKE ?", "%#{q}%")
end
end
I need to add the users.name in the query so that it knows to which table it refers. But when :address is not included, I have a message:
ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column "users.name" does not exist
How can I enforce the table name prefix in the query? Something like AS users in SQL.
Using Rails 3.2
Thanks
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)
With Rails 3, I am using the following kind of code to query a MySQL database:
MyData.joins('JOIN (SELECT id, name FROM sellers) AS Q
ON seller_id = Q.id').
select('*').
joins('JOIN (SELECT id, name FROM users) AS T
ON user_id = T.id').
select("*").each do |record|
#..........
Then, a bit further down, I try to access a "name" with this code: (note that both sellers and users have a name column).
str = record.name
This line is giving me a "user name" instead of a "seller name", but shouldn't it give nothing? Since I joined multiple tables with a name column, shouldn't I be get an error like "column 'name' is ambiguous"? Why isn't this happening?
And by the way, the code behaves the same way whether I include that first "select('*')" line or not.
Thank you.
Firstly, there's no reason to call select twice - only the last call will actually be used. Secondly, you should not be using select("*"), because the SQL database (and Rails) will not rename the ambiguous columns for you. Instead, use explicit naming for the extra columns that you need:
MyData.joins('JOIN (SELECT..) AS Q ON ...', 'JOIN (SELECT...) AS T ON ...').
select('my_datas.*, T.name as t_name, Q.name as q_name').
each do |record|
# do something
end
Because of this, there's no reason to make a subquery in your JOIN statements:
MyData.joins('JOIN sellers AS Q ON ...', 'JOIN users AS T ON ...').
And finally, you should already have belongs_to associations set up for seller and user. That would mean that you can just do this:
MyData.joins(:seller, :user).
select("my_datas.*, sellers.name as seller_name, users.name as user_name").
each do |record|
# do something
end
Now you can call record.seller_name and record.user_name without any ambiguity.
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)