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

ywe*_*nbo 92 activerecord ruby-on-rails rawsql

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

update table set f1=? where f2=? and f3=?
Run Code Online (Sandbox Code Playgroud)

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

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

Bri*_*ing 97

它看起来不像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
Run Code Online (Sandbox Code Playgroud)

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

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

ActiveRecord::Base.connection.execute("update table set f1=#{ActiveRecord::Base.sanitize(f1)}")
Run Code Online (Sandbox Code Playgroud)

或者像评论者一样使用ActiveRecord说.

  • 请注意使用`field =#{value}`建议的"普通Ruby替换"方法,因为这使您对SQL注入攻击持开放态度.如果沿着这条路走下去,请查看[ActiveRecord :: ConnectionAdapters :: Quoting](http://railsapi.com/doc/rails-v3.0.3/classes/ActiveRecord/ConnectionAdapters/Quoting.html)模块. (21认同)
  • 请注意,mysql2不支持预准备语句,另请参阅:http://stackoverflow.com/questions/9906437/how-do-you-create-prepared-statements-with-the-mysql2-gem (3认同)
  • 在Mysql2中没有`prepare`语句 (2认同)

Pau*_*rth 30

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

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

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

connection.execute(<<-EOQ)
  UPDATE  foo
  SET     bar = #{connection.quote(baz)}
EOQ
Run Code Online (Sandbox Code Playgroud)

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

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

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

还有exec_query,exec_insertexec_delete,这取决于你所需要的.

  • 根据文档:_"注意:根据您的数据库连接器,此方法返回的结果可能是手动内存管理.请考虑使用#exec_query包装器."_.`execute`是危险的方法,可能导致内存或其他资源泄漏. (3认同)
  • exec_update应该是被接受的答案 (2认同)

psm*_*ith 8

其他答案都没有向我展示如何使用命名参数,所以我最终结合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'
      }
    ]
  )
)
Run Code Online (Sandbox Code Playgroud)

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

update users set name = 'My Name' where id = 123 and name <> 'My Name'
Run Code Online (Sandbox Code Playgroud)

User如果您没有,则需要使用现有的 Rails 模型。

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


lea*_*ico 5

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

YourModel.update_all(
  ActiveRecord::Base.send(:sanitize_sql_for_assignment, {:value => "'wow'"})
)
Run Code Online (Sandbox Code Playgroud)

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