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.