How to call a model method inside MySQL query? - mysql

Let's say I have an instance variable in my Rails method like this(which I will later use)
def index
#users = User.select("users.id as usr_id,
users.name as user_name,
users.first_price as price,
users.tax as tax,
(#here's model method)=> users.sum_all as grand_total")
.where("users.deleted_at is null AND
users.id IN (?)
#users.pluck(:id))
.order(#order_by)
end
And I want to implement a model method in this query which looks like this
User < ApplicationRecord
def sum_all
self.first_price + self.tax
end
end
If I put this method as above I'm getting an error
{"status":500,"error":"Internal Server Error","exception":"#\u003cActionView::Template::Error: Mysql2::Error: Unknown column 'users.sum_all' in 'field list'}

I suggest you should avoid doing that. Instead, you can make the above query work by adding the two field values in the query itself.
def index
#users = User.select("users.id as usr_id,
users.name as user_name,
users.first_price as price,
users.tax as tax,
users.first_price + users.tax as grand_total")
.where("users.deleted_at is null AND users.id IN (?)", #users.pluck(:id))
.order(#order_by)
end

Related

How to use the distinct method in Rails with Arel Table?

I am looking to run the following query in Rails (I have used the scuttle.io site to convert my SQL to rails-friendly syntax):
Here is the original query:
SELECT pools.name AS "Pool Name", COUNT(DISTINCT stakings.user_id) AS "Total Number of Users Per Pool" from stakings
INNER JOIN pools ON stakings.pool_id = pools.id
INNER JOIN users ON users.id = stakings.user_id
INNER JOIN countries ON countries.code = users.country
WHERE countries.kyc_flow = 1
GROUP BY (pools.name);
And here is the scuttle.io query:
<%Staking.select(
[
Pool.arel_table[:name].as('Pool_Name'), Staking.arel_table[:user_id].count.as('Total_Number_of_Users_Per_Pool')
]
).where(Country.arel_table[:kyc_flow].eq(1)).joins(
Staking.arel_table.join(Pool.arel_table).on(
Staking.arel_table[:pool_id].eq(Pool.arel_table[:id])
).join_sources
).joins(
Staking.arel_table.join(User.arel_table).on(
User.arel_table[:id].eq(Staking.arel_table[:user_id])
).join_sources
).joins(
Staking.arel_table.join(Country.arel_table).on(
Country.arel_table[:code].eq(User.arel_table[:country])
).join_sources
).group(Pool.arel_table[:name]).each do |x|%>
<p><%=x.Pool_Name%><p>
<p><%=x.Total_Number_of_Users_Per_Pool%>
<%end%>
Now, as you may notice, sctuttle.io does not include the distinct parameter which I need. How in the world can I use distinct here without getting errors such as "method distinct does not exist for Arel Node?" or just syntax errors?
Is there any way to write the above query using rails ActiveRecord? I am sure there is, but I am really not sure how.
Answer
The Arel::Nodes::Count class (an Arel::Nodes::Function) accepts a boolean value for distinctness.
def initialize expr, distinct = false, aliaz = nil
super(expr, aliaz)
#distinct = distinct
end
The #count expression is a shortcut for the same and also accepts a single argument
def count distinct = false
Nodes::Count.new [self], distinct
end
So in your case you could use either of the below options
Arel::Nodes::Count.new([Staking.arel_table[:user_id]],true,'Total_Number_of_Users_Per_Pool')
# OR
Staking.arel_table[:user_id].count(true).as('Total_Number_of_Users_Per_Pool')
Suggestion 1:
The Arel you have seems a bit overkill. Given the natural relationships you should be able to simplify this a bit e.g.
country_table = Country.arel_table
Staking
.joins(:pools,:users)
.joins( Arel::Nodes::InnerJoin(
country_table,
country_table.create_on(country_table[:code].eq(User.arel_table[:country])))
.select(
Pool.arel_table[:name],
Staking.arel_table[:user_id].count(true).as('Total_Number_of_Users_Per_Pool')
)
.where(countries: {kyc_flow: 1})
.group(Pool.arel_table[:name])
Suggestion 2: Move this query to your controller. The view has no business making database calls.

Active Record Insert Into

I am trying to allow my Rails App some advanced functionality in way of creating relationships. I am running MySQL as the back-end. The query below runs just fine in PHPMyAdmin, but upon attempted execution in rails, it does nothing, no errors and no database writes....
I use active record to create the relationships between Campaign and Location, and then the query below should work to build afterward in campaign_metros table:
class CampaignLocation < ActiveRecord::Base
belongs_to :campaign
belongs_to :location
after_touch :create_campaign_metro
private
def create_campaign_metro
#sql_insert="INSERT INTO `campaign_metros` (`campaign_id`,`metro_id`, `created_at`,`updated_at`)
SELECT f.`campaign_id`, f.`city_id`, NOW(), NOW()
FROM (SELECT d.`campaign_id`, d.`city_id`
FROM (SELECT c.`campaign_id`, c.`city_id`
FROM (SELECT distinct a.`campaign_id`, b.`city_id`
FROM `campaign_locations` a
INNER JOIN `locations` b
ON a.`location_id` = b.`id`) c) d
LEFT JOIN `campaign_metros` e
ON e.`campaign_id` = d.`campaign_id` and
e.`metro_id` = d.`city_id`
WHERE e.`metro_id` is null and e.`campaign_id` is null) f;"
ActiveRecord::Base.connection.execute(#sql_insert)
end
end
I know from MySQL DELETE FROM with subquery as condition, in answer number two by CodeReaper, that MySQL requires another level of aliases when dealing with subqueries, but running my query in Rails does nothing at all.
How can I get this to work?

ActiveRecord: Add table name in where query even without join

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

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.

Why is this ActiveRecord Query NOT ambiguous?

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.