How can I do the equivalent to this mysql in rails - ruby-on-rails

My query is mysql is as follows:
SELECT SUM(nbdownloaded) FROM (
SELECT field1, field2, nbdownloaded
FROM mytable
Where ....
) as reports
In rails I have the result of the subquery in variable.
Say for example I have:
my_reports = Reports.group("id").select("field1, field2, sum(field3) as nbdownloaded").where(...)")
And I want to have the sum of the field nbdownloaded.
Thank you

it's quite easy in rails active record query,
Model.sum(:nbdownloaded).where(:column =>'your condition')

Related

Executing a SQL query with an `IN` clause from Rails code

I know precious nothing abour Rails, so please excuse my naivete about this question.
I'm trying to modify a piece of code that I got from somewhere to make it execute it for a randomly selected bunch of users. Here it goes:
users = RedshiftRecord.connection.execute(<<~SQL
select distinct user_id
from tablename
order by random()
limit 1000
SQL
).to_a
sql = 'select user_id, count(*) from tablename where user_id in (?) group by user_id'
<Library>.on_replica(:something) do
Something::SomethingElse.
connection.
exec_query(sql, users.join(',')).to_h
end
This gives me the following error:
ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR: syntax error at or near ")"
LINE 1: ...ount(*) from tablename where user_id in (?) group by...
^
Users is an array, I know this coz I executed the following and it resulted in true:
p users.instance_of? Array
Would someone please help me execute this code? I want to execute a simple SQL query that would look like this:
select user_id, count(*) from tablename where user_id in (user1,user2,...,user1000) group by user_id
The problem here is that IN takes a list of parameters. Using a single bind IN (?) and a comma separated string will not magically turn it into a list of arguments. Thats just not how SQL works.
What you want is:
where user_id in (?, ?, ?, ...)
Where the number of binds matches the length of the array you want to pass.
The simple but hacky way to do this would be just interpolate in n number of question marks into the SQL string:
binds = Array.new(users.length, '?').join(',')
sql = <<~SQL
select user_id, count(*)
from tablename
where user_id in (#{binds)})
group by user_id'
SQL
<Library>.on_replica(:something) do
Something::SomethingElse.
connection.
exec_query(sql, users).to_h
end
But you would typically do this in a Rails app by creating a model and using the ActiveRecord query interface or using Arel to programatically create the SQL query.

Convert SQL to ActiveRecord

What is the most appropriate way to convert the following SQL to an ActiveRecord query?
select count(*) from products where id in (
select product_id from store_locations
where store_id in (
select id from stores where store_definition_id = 1
)
)
This uses joins instead of sub queries, but if you're associations are setup correctly should work. I may have messed up the syntax a little bit, but I think it's right.
Product.joins(store_locations: :stores).where(stores: {store_definition_id: 1}).count
EDIT
The above is going to return more rows than you want as it each store_locations row will be returned with the product. Not good. So perhaps:
Product.where(id: StoreLocation.joins(:store).where(store: {store_definition_id: 1}).pluck(:product_id)).count

Convert PSQL query to work using Rails 4 Objects

I have the following query:
select * from email_contacts
where extract(month from created_at) = 4
and call_type = 'Membership NPS'
and email NOT IN
(
select invite_email from nps_responses
where extract(month from fs_created_at) = 4
and invite_email is not null
)
Then I have a rails 4 app that has two corresponding models, EmailContact and NpsResponse.
How can I convert this query to run within my controller method in Rails? I need to select all email_contacts with the above criteria.
This might not be the perfect "Rails way" solution, but you could to do it by using find_by_sql. Using find_by_sql will still retrieve instantiated objects for your model object:
EmailContact.find_by_sql(your_sql_statement)
Use Bind Variables
To prevent SQL injection, you should use bind variables with the find_by_sql method (bind variables are placeholder parameters represented by the ? character. The find_by_sql method will bind the parameter values before executing the statement.) This is highly recommended from a security perspective.
Here's an example of using your SQL statement with bind variables in the find_by_sql method:
EmailContact.find_by_sql("select * from email_contacts
where extract(month from created_at) = ?
and call_type = ?
and email NOT IN
(
select invite_email from nps_responses
where extract(month from fs_created_at) = ?
and invite_email is not null
)", 4, 'Membership NPS', 4)
Things to notice:
The first parameter to the find_by_sql method is the SQL statement as a string value. In this particular SQL string, there are three ? characters as the placeholder variables.
The next three parameters in find_by_sql are the values to be bound to the ? character in the SQL statement.
The order of the parameters are important - the first bind variable parameter will bind to the first ? character, etc.
You can have a variable amount of bind variable parameters, as long as the number of ? matches the number of bind variable parameters.
For some better code organization, you can have some sort of helper method to construct the SQL statement with the bind variables. Kind of like this:
def email_contacts_SQL
sql = "select * from email_contacts
where extract(month from created_at) = ?
and call_type = ?
and email NOT IN
(
select invite_email from nps_responses
where extract(month from fs_created_at) = ?
and invite_email is not null
)"
end
And then use find_by_sql with your helper method:
EmailContact.find_by_sql(email_contacts_SQL, 4, 'Membership NPS', 4)
The official Rails API doc has more examples of how to use find_by_sql.
Warning: If you use find_by_sql, you lose database agnostic conversions provided by ActiveRecord. The find_by_sql method executes the SQL statement as is.
Something close to what you would write in ActiveRecord, this uses bind variables and protects against sql injection
EmailContact.where("month from created_at ?", some_value).
where(:call_type => 'Membership NPS').
where("email NOT IN (
select invite_email from nps_responses
where extract(month from fs_created_at) = ?
and invite_email is not null
)", some_value).all

How to convert SQL query to ActiveRecord

Background:
Writing a Ruby on Rails app that is connect to MS SQL DB(don't ask why, its something I cant get around), the DB is quiet large and can have up to 2/3 million rows.
There is one main columns that matter for me at the moment and that is 'TimeUTC' and the table is called ApplicationLog and the query will be in the ApplicationLogController.
Problem:
I want to write a query that i can pass in two dates and it will group all the records by day using the 'TimeUTC' column and gives me a total of all the records for those days in between those two dates.
I have the SQL Query:
DECLARE #StartDate DateTime = '2014-01-04 00:00:00'
DECLARE #EndDate DateTime = '2014-02-04 23:59:59'
select (dateadd(DAY,0, datediff(day,0, TimeUtc))) as [DATE], count(*)
from applicationlog (nolock)
where TimeUtc between #StartDate and #EndDate
group by dateadd(DAY,0, datediff(day,0, TimeUtc))
order by [DATE] desc
I try starting with something like this:
#results = ApplicationLog.select((dateadd(DAY,0, datediff(day,0, TimeUtc)) as [date], count(*)).
group(dateadd(DAY,0, datediff(day,0, TimeUtc))).order(date desc)
Now I am a newbie at this so I could be so far off the track its not funny, but any help would be great. Am I even going about this the right way, is there a better way??
Try with the following code, which uses Arel code with some SQL embedded.
class ApplicationLog < ActiveRecord::Base
def self.between(range)
columns = Arel.sql('dateadd(DAY,0,datediff(day,0,TimeUtc)) as date, COUNT(*)')
conditions = arel_table['TimeUTC'].in(range)
query = arel_table.project(columns).where(conditions).group([1]).order('date desc')
ActiveRecord::Base.connection.execute(query.to_sql)
end
end
Then use ApplicationLog.between(1.week.ago..Time.now).

.group not returning all columns

I have a .group query that is not returning all the columns in the select and I was wondering if someone could validate my syntax.
Here is a query with a .group and the result from my console;
Expense.select('account_number, SUM(credit_amount)').group(:account_number).first
Expense Load (548.8ms) EXEC sp_executesql N'SELECT TOP (1) account_number, SUM(credit_amount) FROM [expenses] GROUP BY account_number'
(36.9ms) SELECT table_name FROM information_schema.views
Even though I select two columns, I'm only getting the first one to return. I'm wondering if I may be dealing with an db adapter problem.
Try giving your sum an alias:
expense = Expense.select('account_number, SUM(credit_amount) AS credit_amount').group(:account_number).first
puts expense.credit_amount
ActiveRecord doesn't create a default alias for aggregation operations such as SUM, COUNT etc... you have to do it explicitly to be able to access the results, as shown above.
The SUM(credit_amount) column from the SQL has no alias and will not have a column name by default. If you change it to have an alias SUM(credit_amount) As 'A' for example and select the alias name, it should pick it up.

Resources