111

I want to execute one update raw sql like below:

update table set f1=? where f2=? and f3=?

This SQL will be executed by ActiveRecord::Base.connection.execute, but I don't know how to pass the dynamic parameter values into the method.

Could someone give me any help on it?

madth3
  • 7,275
  • 12
  • 50
  • 74
ywenbo
  • 3,051
  • 6
  • 31
  • 46
  • Why do you want to do this using raw SQL, the point of ActiveRecord is to help you avoid that... – Andrew Dec 19 '10 at 16:06
  • if i use AR, firstly i should get Model Object by AR's find method with id field, then to do update operation. So from the view of operations one UPDATE AR need two sqls with database; on the other hand i am not sure the update method of AR uses dynamic binding. so i want to use raw sql with dynamic binding for one interaction with db for update operation, but i don't know how to pass parameters to replace the ? in sql by AR. – ywenbo Dec 19 '10 at 16:34
  • 32
    There's many valid reasons for doing this. First, the query may be too complex to translate into using the regular Ruby way... second, the parameters may have special characters like %, or quotes, and it's a pain in the ass to escape the.. – Henley Dec 24 '11 at 22:11
  • 3
    @Andrew, it is better to use raw mysql functions than accept the "convenience" that AR offers. – Green Apr 20 '13 at 20:24
  • 5
    @Green not if you ever want to move your app from MySQL to PostgreSQL or something else. One of the major points of an ORM is to make your app portable. – Andrew Apr 21 '13 at 15:34
  • @Andrew One word: migrations. During a migration you can't rely on ActiveRecord convenience. – art-solopov May 25 '17 at 12:15

8 Answers8

113

It doesn't look like the Rails API exposes methods to do this generically. You could try accessing the underlying connection and using it's methods, e.g. for MySQL:

st = ActiveRecord::Base.connection.raw_connection.prepare("update table set f1=? where f2=? and f3=?")
st.execute(f1, f2, f3)
st.close

I'm not sure if there are other ramifications to doing this (connections left open, etc). I would trace the Rails code for a normal update to see what it's doing aside from the actual query.

Using prepared queries can save you a small amount of time in the database, but unless you're doing this a million times in a row, you'd probably be better off just building the update with normal Ruby substitution, e.g.

ActiveRecord::Base.connection.execute("update table set f1=#{ActiveRecord::Base.sanitize(f1)}")

or using ActiveRecord like the commenters said.

Deepak Mahakale
  • 22,834
  • 10
  • 68
  • 88
Brian Deterling
  • 13,556
  • 4
  • 55
  • 59
  • :-), appreciate you help very much, you're right, if you have some updates after tracing rails code please let me know. If really there is no exposes your provided method will be the best way for my case. My worry is that the update method of ActiveRecord doesn't use dynamic binding, if really so that's bad. – ywenbo Dec 20 '10 at 00:40
  • It looks like you should call free on any results you get back, but I don't think that applies to an update. As long as you close the statement like I showed, you should be fine. – Brian Deterling Dec 20 '10 at 08:34
  • really really appreciate your reply for my problems, thank you, up to now it's clear with your help. – ywenbo Dec 21 '10 at 01:31
  • 31
    Beware the suggested "normal Ruby substitution" method using `field=#{value}` as this leaves you wide open to SQL injection attacks. If you go down that path, check out the [ActiveRecord::ConnectionAdapters::Quoting](http://railsapi.com/doc/rails-v3.0.3/classes/ActiveRecord/ConnectionAdapters/Quoting.html) module. – Paul Annesley Feb 17 '11 at 03:26
  • Agree with Paul. I updated the answer to include sanitizing the parameters. – Brian Deterling Feb 17 '11 at 14:46
  • 5
    Please note, mysql2 does not support prepared statements, see also: http://stackoverflow.com/questions/9906437/how-do-you-create-prepared-statements-with-the-mysql2-gem – reto Mar 12 '13 at 17:39
  • 1
    @reto Looks like they are close though: https://github.com/brianmario/mysql2/pull/289 – Brian Deterling Mar 13 '13 at 14:33
  • 3
    No `prepare` statement in Mysql2 – Green Apr 20 '13 at 20:25
  • And mysql2 now supports prepared statements: https://github.com/brianmario/mysql2/releases/tag/0.4.0 – Brian Deterling Oct 26 '15 at 20:15
  • You can use each to iterate over the results or call to_a just like with a normal non-prepared result. – Brian Deterling Mar 16 '17 at 01:35
  • 1
    According to documentation: _"Note: depending on your database connector, the result returned by this method may be manually memory managed. Consider using the #exec_query wrapper instead."_. `execute` is dangerous method and can cause memory or other resource leaks. – kolen Apr 12 '18 at 17:15
  • The code given here seems wrong because `prepare` takes at least two arguments: the statement's name and the query. – Nowhere man Feb 14 '19 at 14:21
  • `Using prepared queries can save you a small amount of time in the database,` ... note that in some databases, namely Oracle, bind variables are absolutely the way to go, and can bring enormous benefits. It's not just about a "small amount of time", but also about saving space, re-using resources, making debugging easier (less stuff to go through in the data dictionary when looking at SQL plans) and others. – AnoE Nov 30 '20 at 17:46
  • @Nowhereman I just tested it under Rails 6 and it works as shown in the answer. – Brian Deterling Feb 19 '21 at 18:06
  • 1
    @AnoE agree that prepared statements are generally better, but if you are only executing once, performance might be better without explicitly preparing. This was a Rails question and there is some penalty for dropping out of normal ActiveRecord mode and accessing the raw connection, so it's up to the developer to determine if the benefits of prepared statements outweigh that cost. – Brian Deterling Feb 19 '21 at 18:12
  • @BrianDeterling, yes, I think it's a shame that Rails doesn't support bind variables as first class citizen. It certainly isn't worth it dropping out of "Rails mode" in general, considering developing and maintenance cost. – AnoE Feb 22 '21 at 08:00
38

ActiveRecord::Base.connection has a quote method that takes a string value (and optionally the column object). So you can say this:

ActiveRecord::Base.connection.execute(<<-EOQ)
  UPDATE  foo
  SET     bar = #{ActiveRecord::Base.connection.quote(baz)}
EOQ

Note if you're in a Rails migration or an ActiveRecord object you can shorten that to:

connection.execute(<<-EOQ)
  UPDATE  foo
  SET     bar = #{connection.quote(baz)}
EOQ

UPDATE: As @kolen points out, you should use exec_update instead. This will handle the quoting for you and also avoid leaking memory. The signature works a bit differently though:

connection.exec_update(<<-EOQ, "SQL", [[nil, baz]])
  UPDATE  foo
  SET     bar = $1
EOQ

Here the last param is a array of tuples representing bind parameters. In each tuple, the first entry is the column type and the second is the value. You can give nil for the column type and Rails will usually do the right thing though.

There are also exec_query, exec_insert, and exec_delete, depending on what you need.

Paul A Jungwirth
  • 23,504
  • 14
  • 74
  • 93
  • 3
    According to documentation: _"Note: depending on your database connector, the result returned by this method may be manually memory managed. Consider using the #exec_query wrapper instead."_. `execute` is dangerous method and can cause memory or other resource leaks. – kolen Apr 12 '18 at 17:14
  • 1
    Wow good catch! Here is the [documentation](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/DatabaseStatements.html#method-i-execute). Also it looks like [the Postgres adapter](https://github.com/rails/rails/blob/375a4143cf5caeb6159b338be824903edfd62836/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb#L68-L78) would leak here. – Paul A Jungwirth Apr 12 '18 at 17:57
  • Wonder, if AR leaves us with nothing for `on duplicate key update` everywhere – Shrinath Apr 10 '19 at 14:20
  • 1
    @Shrinath since this question is about writing raw SQL you could make the query `ON CONFLICT DO UPDATE` if you like. For non-raw SQL this gem looks handy: https://github.com/jesjos/active_record_upsert – Paul A Jungwirth Apr 10 '19 at 20:24
6

None of the other answers showed me how to use named parameters, so I ended up combining exec_update with sanitize_sql:

User.connection.exec_update(
  User.sanitize_sql(
    [
      "update users set name = :name where id = :id and name <> :name",
      {
        id: 123,
        name: 'My Name'
      }
    ]
  )
)

This works for me on Rails 5, and it executes this SQL:

update users set name = 'My Name' where id = 123 and name <> 'My Name'

You need to use an existing Rails model instead of User if you don't have that.

I wanted to use named parameters to avoid issues with the ordering when I use ? or $1/$2,etc. Positional ordering is kind of frustrating when I have more than a handful of parameters, but named parameters allow me to refactor the SQL command without having to update the parameters.

psmith
  • 2,292
  • 1
  • 19
  • 19
5

You should just use something like:

YourModel.update_all(
  ActiveRecord::Base.send(:sanitize_sql_for_assignment, {:value => "'wow'"})
)

That would do the trick. Using the ActiveRecord::Base#send method to invoke the sanitize_sql_for_assignment makes the Ruby (at least the 1.8.7 version) skip the fact that the sanitize_sql_for_assignment is actually a protected method.

leandroico
  • 1,207
  • 13
  • 17
2

Sometime would be better use name of parent class instead name of table:

# Refers to the current class
self.class.unscoped.where(self.class.primary_key => id).update_all(created _at: timestamp)

For example "Person" base class, subclasses (and database tables) "Client" and "Seller" Instead using:

Client.where(self.class.primary_key => id).update_all(created _at: timestamp)
Seller.where(self.class.primary_key => id).update_all(created _at: timestamp)

You can use object of base class by this way:

person.class.unscoped.where(self.class.primary_key => id).update_all(created _at: timestamp)
mikowiec
  • 481
  • 7
  • 6
1

Here's a trick I recently worked out for executing raw sql with binds:

binds = SomeRecord.bind(a_string_field: value1, a_date_field: value2) +
        SomeOtherRecord.bind(a_numeric_field: value3)
SomeRecord.connection.exec_query <<~SQL, nil, binds
  SELECT *
  FROM some_records
  JOIN some_other_records ON some_other_records.record_id = some_records.id
  WHERE some_records.a_string_field = $1
    AND some_records.a_date_field < $2
    AND some_other_records.a_numeric_field > $3
SQL

where ApplicationRecord defines this:

# Convenient way of building custom sql binds
def self.bind(column_values)
  column_values.map do |column_name, value|
    [column_for_attribute(column_name), value]
  end
end

and that is similar to how AR binds its own queries.

spume
  • 1,704
  • 1
  • 14
  • 19
-12

I needed to use raw sql because I failed at getting composite_primary_keys to function with activerecord 2.3.8. So in order to access the sqlserver 2000 table with a composite primary key, raw sql was required.

sql = "update [db].[dbo].[#{Contacts.table_name}] " +
      "set [COLUMN] = 0 " +
      "where [CLIENT_ID] = '#{contact.CLIENT_ID}' and CONTACT_ID = '#{contact.CONTACT_ID}'"
st = ActiveRecord::Base.connection.raw_connection.prepare(sql)
st.execute

If a better solution is available, please share.

donvnielsen
  • 195
  • 1
  • 9
-23

In Rails 3.1, you should use the query interface:

  • new(attributes)
  • create(attributes)
  • create!(attributes)
  • find(id_or_array)
  • destroy(id_or_array)
  • destroy_all
  • delete(id_or_array)
  • delete_all
  • update(ids, updates)
  • update_all(updates)
  • exists?

update and update_all are the operation you need.

See details here: http://m.onkey.org/active-record-query-interface

activars
  • 1
  • 1