如何在rails中使用动态绑定执行原始更新sql

时间:2010-12-19 13:04:57

标签: ruby-on-rails activerecord rawsql

我想执行一个更新原始sql,如下所示:

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

此SQL将由ActiveRecord::Base.connection.execute执行,但我不知道如何将动态参数值传递给方法。

有人可以给我任何帮助吗?

8 个答案:

答案 0 :(得分:91)

看起来Rails API看起来不像通常那样公开方法。您可以尝试访问底层连接并使用它的方法,例如对于MySQL:

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

我不确定是否还有其他后果(连接保持打开等)。我会跟踪Rails代码以进行正常更新,看看除了实际查询之外它还在做什么。

使用准备好的查询可以节省您在数据库中的少量时间,但除非您连续一百万次这样做,否则您可能最好只使用正常的Ruby替换来构建更新,例如

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

或像评论者一样使用ActiveRecord。

答案 1 :(得分:26)

ActiveRecord::Base.connection有一个quote方法,它接受一个字符串值(以及可选的列对象)。所以你可以这样说:

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

请注意,如果您使用的是Rails迁移或ActiveRecord对象,则可以将其缩短为:

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

更新:@kolen指出,您应该使用exec_update代替。这将为您处理报价并避免泄漏内存。签名的工作方式略有不同:

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

这里最后一个参数是一个表示绑定参数的元组数组。在每个元组中,第一个条目是列类型,第二个条目是值。您可以为列类型提供nil,但Rails通常会做正确的事情。

还有exec_queryexec_insertexec_delete,具体取决于您的需求。

答案 2 :(得分:4)

你应该使用类似的东西:

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

那就可以了。使用 ActiveRecord :: Base #seve 方法调用 sanitize_sql_for_assignment 使Ruby(至少是1.8.7版本)跳过 sanitize_sql_for_assignment 实际上是一种受保护的方法。

答案 3 :(得分:2)

有时候最好使用父类的名称而不是表名:

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

例如“Person”基类,子类(和数据库表)“Client”和“Seller” 而是使用:

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

您可以通过这种方式使用基类的对象:

person.class.unscoped.where(self.class.primary_key => id).update_all(created _at: timestamp)

答案 4 :(得分:1)

为什么要为此使用原始SQL?

如果您有相应的模型,请使用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

如果没有模型(仅表格),则可以创建一个文件和模型,这些文件和模型将从ActiveRecord::Base

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

并再次使用where,与上面相同:

答案 5 :(得分:0)

这是我最近通过绑定执行原始sql的一个技巧:

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

其中ApplicationRecord定义了这一点:

# 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

这类似于AR绑定自己的查询的方式。

答案 6 :(得分:-8)

我需要使用原始sql,因为我无法使composite_primary_keys与activerecord 2.3.8一起运行。因此,为了使用复合主键访问sqlserver 2000表,需要原始sql。

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

如果有更好的解决方案,请分享。

答案 7 :(得分:-15)

在Rails 3.1中,您应该使用查询界面:

  • new(attributes)
  • 创建(属性)
  • 创建!(属性)
  • find(id_or_array)
  • destroy(id_or_array)
  • destroy_all
  • 删除(id_or_array)
  • delete_all
  • 更新(ids,更新)
  • update_all(更新)
  • 存在?

update和update_all是您需要的操作。

请在此处查看详细信息:http://m.onkey.org/active-record-query-interface