110

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

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

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

有人可以帮我吗?

4

8 回答 8

110

看起来 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。

于 2010-12-19T18:54:20.523 回答
37

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_query,exec_insertexec_delete, 取决于您的需要。

于 2014-07-01T23:01:35.380 回答
4

你应该只使用类似的东西:

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

这样就行了。使用ActiveRecord::Base#send方法调用sanitize_sql_for_assignment使得 Ruby(至少 1.8.7 版本)跳过了sanitize_sql_for_assignment实际上是一个受保护的方法这一事实。

于 2012-05-15T03:12:50.763 回答
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)
于 2013-11-29T20:58:30.587 回答
1

这是我最近制定的使用绑定执行原始 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 如何绑定自己的查询。

于 2020-05-14T17:20:06.423 回答
1

其他答案都没有向我展示如何使用命名参数,所以我最终结合exec_updatesanitize_sql

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

这适用于我在 Rails 5 上,它执行以下 SQL:

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

您需要使用现有的 Rails 模型,而不是使用现有的 Rails 模型User

我想使用命名参数来避免在使用?or $1/$2等时出现排序问题。当我有多个参数时,位置排序有点令人沮丧,但命名参数允许我重构 SQL 命令而无需更新参数。

于 2021-10-27T08:21:44.190 回答
-12

我需要使用原始 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

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

于 2013-04-25T18:06:41.830 回答
-22

在 Rails 3.1 中,您应该使用查询接口:

  • 新的(属性)
  • 创建(属性)
  • 创建!(属性)
  • 查找(id_or_array)
  • 销毁(id_or_array)
  • 销毁全部
  • 删除(id_or_array)
  • 删除所有
  • 更新(ID,更新)
  • update_all(更新)
  • 存在吗?

update 和 update_all 是您需要的操作。

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

于 2011-11-11T09:40:04.980 回答