Rails 4, raw query using ActiveRecord - ruby-on-rails

Is there currently a way to do a raw SQL select query using ActiveRecord in Rails 4.0.0.beta1? I see ActiveRecord::Base.execute no longer exists. What's the correct way of going about this?

Here try this, select example.. :
query = "select ...."
results = ActiveRecord::Base.connection.execute(query)

Just to add my ten pence, a raw query using Model.connection.execute won't return an ActiveRecord model - it'll return a raw data set.
The following will return ActiveRecord models:
MyModel.find_by_sql(query)
edit: assuming of course that you're running a select.

In Rails 4 (perhaps previous versions as well), if you're going with a custom query for speed, you can add a :skip_logging argument to avoid writing to the log:
query = "SELECT ..."
results = MyModel.connection.execute(query, :skip_logging)
(Note: If I'm reading the sources correctly, this might not hold true in PostgreSQL.)

Related

How to pass array as bind variable to Rails/ActiveRecord raw SQL queries?

I need to pass an array of ids into my raw sql query like this:
select offers.* from offers where id in (1,2,3,4,5)
The real query includes a lot of joins and aggregation functions and can't be written using Arel expressions or ActiveRecord model methods like Offer.where(id: [...]). I'm looking exactly for how to use bind variables in raw queries.
Instead of interpolating ids into string I want to use bind variables like this (pseudo-code):
ActiveRecord::Base.connection.select_all("select offers.* from offers where id in (:ids)", {ids: [1,2,3,4,5]})
However, I can't find any solution to perform this. From this ticket I've got a comment with related test-case in ActiveRecord code with the following example:
sub = Arel::Nodes::BindParam.new
binds = [Relation::QueryAttribute.new("id", 1, Type::Value.new)]
sql = "select * from topics where id = #{sub.to_sql}"
#connection.exec_query(sql, "SQL", binds)
I've tried this approach, but it didn't worked at all, my "?" was not replaced by actual values.
I'm using Rails 5.1.6 and MariaDB database.
You could do this in a much simpler fashion purely with arel. (Also it makes the code far more maintainable than SQL strings)
offers = Arel::Table.new('offers')
ids = [1,2,3,4,5]
query = offers.project(Arel.star).where(offers[:id].in(ids))
ActiveRecord::Base.connection.exec_query(query.to_sql)
This will result in the following SQL
SELECT
[offers].*
FROM
[offers]
WHERE
[offers].[id] IN (1,2,3,4,5)
When executed you will receive an ActiveRecord::Result object with is usually easiest to deal with by calling to_hash and each resulting row will be turned into a Hash of {column_name => value}
However if you are using rails and Offer is a true model then:
Offer.where(id: ids)
Will result in the same query and will return an ActiveRecord::Relation collection of Offer objects which is generally more preferable.
Update
Seems like you need to enable prepared_statements in mysql2 (mariadb) in order to use the bind params, which can be done like this:
default: &default
adapter: mysql2
encoding: utf8
prepared_statements: true # <- here we go!
Please note the following pieces of code:
https://github.com/rails/rails/blob/5-1-stable/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb#L115
https://github.com/rails/rails/blob/5-1-stable/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb#L40
https://github.com/rails/rails/blob/5-1-stable/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb#L630
https://github.com/rails/rails/blob/5-1-stable/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb#L30
As you can see in the last code exec_query ignores bind_params if prepared_statements is turned off (which appears to be the default for the mysql2 adapter).

Combining distinct with another condition

I'm migrating a Rails 3.2 app to Rails 5.1 (not before time) and I've hit a problem with a where query.
The code that works on Rails 3.2 looks like this,
sales = SalesActivity.select('DISTINCT batch_id').where('salesperson_id = ?', sales_id)
sales.find_each(batch_size: 2000) do |batchToProcess|
.....
When I run this code under Rails 5.1, it appears to cause the following error when it attempts the for_each,
ArgumentError (Primary key not included in the custom select clause):
I want to end up with an array(?) of unique batch_ids for the given salesperson_id that I can then traverse, as was working with Rails 3.2.
For reasons I don't understand, it looks like I might need to include the whole record to traverse through (my thinking being that I need to include the Primary key)?
I'm trying to rephrase the 'where', and have tried the following,
sales = SalesActivity.where(salesperson_id: sales_id).select(:batch_id).distinct
However, the combined ActiveRecordQuery applies the DISTINCT to both the salesperson_id AND the batch_id - that's #FAIL1
Also, because I'm still using a select (to let distinct know which column I want to be 'distinct') it also still only selects the batch_id column of course, which I am trying to avoid - that's #FAIL2
How can I efficiently pull all unique batch_id records for a given salesperson_id, so I can then for_each them?
Thanks!
How about:
SalesActivity.where(salesperson_id: sales_id).pluck('DISTINCT batch_id')
May need to change up the ordering of where and pluck, but pluck should return an array of the batch_ids

How do I specify this `WHERE` condition without using SQL?

I'm using the following raw Postgres SQL in my where() call to find records where some_date is two days from now:
SomeModel.where('date(some_date) - date(NOW()) = 2')
Can it be written using pure ActiveRecord syntax without raw SQL?
This should work, haven't tested it though.
SomeModel.where('date(some_date) = ?', 2.days.ago.to_date)
#or
SomeModel.where('some_date::date = ?', 2.days.ago.to_date)
If you want it to be any purer, without the date sql function, I'm not seeing it, at least not without creating a db view.

Modifying the returned value of find_by_sql

So I am pulling my hair over this issue / gotcha. Basically I used find_by_sql to fetch data from my database. I did this because the query has lots of columns and table joins and I think using ActiveRecord and associations will slow it down.
I managed to pull the data and now I wanted to modify returned values. I did this by looping through the result ,for example.
a = Project.find_by_sql("SELECT mycolumn, mycolumn2 FROM my_table").each do |project|
project['mycolumn'] = project['mycolumn'].split('_').first
end
What I found out is that project['mycolumn'] was not changed at all.
So my question:
Does find_by_sql return an array Hashes?
Is it possible to modify the value of one of the attributes of hash as stated above?
Here is the code : http://pastie.org/4213454 . If you can have a look at summarize_roles2() that's where the action is taking place.
Thank you. Im using Rails 2.1.1 and Ruby 1.8. I can't really upgrade because of legacy codes.
Just change the method above to access the values, print value of project and you can clearly check the object property.
The results will be returned as an array with columns requested encapsulated as attributes of the model you call this method from.If you call Product.find_by_sql then the results will be returned in a Product object with the attributes you specified in the SQL query.
If you call a complicated SQL query which spans multiple tables the columns specified by the SELECT will be attributes of the model, whether or not they are columns of the corresponding table.
Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id"
> [#<Post:0x36bff9c #attributes={"title"=>"Ruby Meetup", "first_name"=>"Quentin"}>, ...]
Source: http://api.rubyonrails.org/v2.3.8/
Have you tried
a = Project.find_by_sql("SELECT mycolumn, mycolumn2 FROM my_table").each do |project|
project['mycolumn'] = project['mycolumn'].split('_').first
project.save
end

Ruby On Rails Querying From a model but returning another tables fields

active_courses_past_week = CourseEnrollment.select("courses.*").
joins(:course).
where("date(course_enrollments.created_at) BETWEEN ? and ?", Date.parse(start_date.strftime('%Y-%m-%d')), Date.parse(end_date.strftime('%Y-%m-%d'))).
group("courses.id")
The above query seems odd because I am querying from course enrollments, but only care about the course data where they are enrolled between two dates. It just seems weird because I am not using any of the fields in the CourseErnollment model. Any suggestions?
This is how I would write it using a range instead of "raw" SQL.
active_courses_past_week = CourseEnrollment.where(:created_at => start_date..end_date))
.joins(:course)
.group(courses.id)

Resources