How to execute a raw update sql with dynamic binding in rails

后端 未结 8 586
遥遥无期
遥遥无期 2020-11-29 02:03

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:

相关标签:
8条回答
  • 2020-11-29 02:33

    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.

    0 讨论(0)
  • 2020-11-29 02:41

    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

    0 讨论(0)
  • 2020-11-29 02:42

    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.

    0 讨论(0)
  • 2020-11-29 02:44

    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)
    
    0 讨论(0)
  • 2020-11-29 02:49

    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.

    0 讨论(0)
  • 2020-11-29 02:56

    Why use raw SQL for this?

    If you have a model for it use where:

    f1 = 'foo'
    f2 = 'bar'
    f3 = 'buzz'
    YourModel.where('f1 = ? and f2 = ?', f1, f2).each do |ym|
      # or where(f1: f1, f2: f2).each do (...)
      ym.update(f3: f3) 
    end
    
    

    If you don't have a model for it (just the table), you can create a file and model that will inherit from ActiveRecord::Base

    class YourTable < ActiveRecord::Base
      self.table_name = 'your_table' # specify explicitly if needed
    end
    

    and again use where the same as above:

    0 讨论(0)
提交回复
热议问题