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


98

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

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

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

有人可以帮我吗?


为什么要使用原始SQL进行此操作,ActiveRecord的目的是帮助您避免这种情况……
安德鲁(Andrew)2010年

如果我使用AR,首先应该通过带有ID字段的AR的find方法获得Model Object,然后进行更新操作。因此,从操作的角度来看,一个UPDATE AR需要两个带有数据库的sql。另一方面,我不确定AR的更新方法是否使用动态绑定。所以我想使用带有动态绑定的原始sql与db进行一次交互以进行更新操作,但是我不知道如何传递参数来替换?在SQL中使用AR。
ywenbo '12

27
这样做有很多正当的理由。首先,查询可能太复杂而转化为使用普通的Ruby方式......第二,这些参数可能有特殊字符,如%,或报价,它是在一个痛苦的屁股逃避..
亨利照

3
@Andrew,使用原始的mysql函数比接受AR提供的“便利”更好。
格林

4
如果您想将应用程序从MySQL迁移到PostgreSQL或其他东西,则@Green不会。ORM的主要点之一是使您的应用程序具有可移植性。
安德鲁

Answers:


102

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


26
当心建议使用的“常规Ruby替换”方法,field=#{value}因为这使您容易受到SQL注入攻击的影响。如果沿着那条路走,请检出ActiveRecord :: ConnectionAdapters :: Quinging模块。
Paul Annesley

3
请注意,mysql2不支持预处理语句,另见:stackoverflow.com/questions/9906437/...
雷托


3
没有prepare在Mysql2声明
格林

1
根据文档:“注意:根据您的数据库连接器,此方法返回的结果可能是手动进行内存管理的。请考虑改用#exec_query包装器。” execute是危险的方法,可能导致内存或其他资源泄漏。
kolen

32

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,这取决于你所需要的。


3
根据文档:“注意:根据您的数据库连接器,此方法返回的结果可能是手动进行内存管理的。请考虑改用#exec_query包装器。” execute是危险的方法,可能导致内存或其他资源泄漏。
kolen

1
哇好抓!这是文档。而且看起来Postgres适配器会在这里泄漏。
Paul A Jungwirth,2018年

想知道,AR是否会在任何on duplicate key update地方让我们一无所获
Shrinath '19

1
@Shrinath,因为此问题与编写原始SQL有关,所以您可以根据需要进行查询ON CONFLICT DO UPDATE。对于非原始SQL,该工具看起来很方便:github.com/jesjos/active_record_upsert
Paul A Jungwirth,

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实际上是受保护的方法这一事实。


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)

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与上面相同的方法:


2
这样效率低得多,不是吗。这不是用一条更新语句进行选择,而是将记录带入rails内存,更新每条记录并为每条记录发回一条更新语句。1条更新与1条记录的更新以及多少带宽等等?AR是否优化了这一点?我不这样认为。
Jimi Kimble

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绑定自己的查询的方式。


-11

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

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


10
sql-injection在这里是可能的!
mystdeim

-18

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

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

update和update_all是您需要的操作。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.