Rails에서 동적 바인딩을 사용하여 원시 업데이트 SQL을 실행하는 방법


98

아래와 같이 하나의 업데이트 원시 SQL을 실행하고 싶습니다.

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

이 SQL은에 의해 실행 ActiveRecord::Base.connection.execute되지만 동적 매개 변수 값을 메서드에 전달하는 방법을 모르겠습니다.

누군가 내게 도움을 줄 수 있습니까?


원시 SQL을 사용하여이 작업을 수행하려는 이유는 ActiveRecord의 요점은이를 방지하는 것입니다.
Andrew

AR을 사용하는 경우 먼저 ID 필드가있는 AR의 find 메소드로 Model Object를 얻은 다음 업데이트 작업을 수행해야합니다. 따라서 작업 관점에서 하나의 UPDATE AR에는 데이터베이스가있는 두 개의 SQL이 필요합니다. 반면에 AR의 업데이트 방법이 동적 바인딩을 사용하는지 잘 모르겠습니다. 그래서 업데이트 작업을 위해 db와의 한 번의 상호 작용을 위해 동적 바인딩과 함께 원시 SQL을 사용하고 싶지만 매개 변수를 전달하여? AR로 SQL에서.
ywenbo 2010

27
이를 수행하는 데에는 여러 가지 타당한 이유가 있습니다. 첫째, 쿼리가 너무 복잡해서 일반 Ruby 방식으로 번역 할 수 없습니다. 둘째, 매개 변수에 % 또는 따옴표와 같은 특수 문자가있을 수 있으며,이 문자를 탈출하는 것은 엉덩이에 고통
스럽

3
@Andrew, AR이 제공하는 "편의성"을 받아들이는 것보다 원시 mysql 함수를 사용하는 것이 좋습니다.
Green

4
@Green은 MySQL에서 PostgreSQL 또는 다른 것으로 앱을 이동하려는 경우가 아닙니다. ORM의 주요 요점 중 하나는 앱을 이식 가능하게 만드는 것입니다.
Andrew

답변:


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
field=#{value}SQL 인젝션 공격에 노출 될 수 있으므로 권장되는 "일반적인 Ruby 대체"방법을 사용하십시오 . 해당 경로로 이동하면 ActiveRecord :: ConnectionAdapters :: Quoting 모듈을 확인하십시오.
Paul Annesley

3
mysql2는 준비된 문을 지원하지 않습니다. 참조 : stackoverflow.com/questions/9906437/…
reto

1
: 외모 @reto 그들은 비록 가까이있는 것처럼 github.com/brianmario/mysql2/pull/289
브라이언 Deterling

3
prepareMysql2에 성명서 없음
Green

1
문서에 따르면 : "참고 : 데이터베이스 커넥터에 따라이 메서드에서 반환 된 결과는 수동으로 메모리를 관리 할 수 ​​있습니다. 대신 #exec_query 래퍼를 사용하는 것이 좋습니다." . execute위험한 방법이며 메모리 또는 기타 리소스 누수가 발생할 수 있습니다.
kolen

32

ActiveRecord::Base.connectionquote문자열 값 (선택적으로 열 개체) 을받는 메서드가 있습니다. 따라서 다음과 같이 말할 수 있습니다.

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_insert그리고 exec_delete당신이 필요에 따라.


3
문서에 따르면 : "참고 : 데이터베이스 커넥터에 따라이 메서드에서 반환 된 결과는 수동으로 메모리를 관리 할 수 ​​있습니다. 대신 #exec_query 래퍼를 사용하는 것이 좋습니다." . execute위험한 방법이며 메모리 또는 기타 리소스 누수를 일으킬 수 있습니다.
kolen

1
와우 좋은 캐치! 여기에 문서가 있습니다. 또한 Postgres 어댑터 가 여기에서 누출 되는 것처럼 보입니다 .
Paul A Jungwirth

원더, AR은에 아무것도 우리를 떠나면 on duplicate key update모든 곳
Shrinath

1
이 질문은 원시 SQL 작성에 관한 것이므로 ON CONFLICT DO UPDATE원하는 경우 쿼리를 만들 수 있습니다. : 비 원시 SQL의이 보석은 편리한 보이는 github.com/jesjos/active_record_upsert을
폴 Jungwirth

4

다음과 같이 사용해야합니다.

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

그것은 트릭을 할 것입니다. 은 Using 액티브 :: 자료 # 보내기 인보하는 방법을 sanitize_sql_for_assignment을 루비 (적어도 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
이것은 훨씬 덜 효율적이지 않습니까? 이것은 선택에 대해 하나의 업데이트 문을 교환하지 않고 레코드를 레일스 메모리로 가져오고 각 레코드를 업데이트하고 각 레코드에 대해 업데이트 문을 다시 보내지 않습니다. 업데이트 1 개 vs 레코드 당 1 개, 많은 대역폭 등? AR이이를 최적화합니까? 나는 그렇게 생각하지 않는다.
지미 킴블

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

activerecord 2.3.8과 함께 작동하도록 composite_primary_keys를 가져 오지 못했기 때문에 원시 SQL을 사용해야했습니다. 따라서 복합 기본 키를 사용하여 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

더 나은 솔루션을 사용할 수 있으면 공유하십시오.


11
여기에서 SQL 주입이 가능합니다!
mystdeim

-19

Rails 3.1에서는 쿼리 인터페이스를 사용해야합니다.

  • new (속성)
  • 생성 (속성)
  • create! (속성)
  • 찾기 (id_or_array)
  • destroy (id_or_array)
  • destroy_all
  • 삭제 (id_or_array)
  • 모두 삭제
  • 업데이트 (ID, 업데이트)
  • update_all (업데이트)
  • 존재합니까?

update 및 update_all은 필요한 작업입니다.

자세한 내용은 http://m.onkey.org/active-record-query-interface를 참조하십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.