Як виконати необроблене оновлення sql з динамічним прив'язуванням у рейках


98

Я хочу виконати одне оновлення raw sql, як показано нижче:

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

Цей SQL буде виконаний ActiveRecord::Base.connection.execute, але я не знаю, як передавати динамічні значення параметрів у метод.

Хтось міг би мені надати допомогу?


Чому ви хочете зробити це, використовуючи необроблений SQL, суть ActiveRecord полягає у тому, щоб уникнути цього ...
Ендрю

якщо я використовую AR, по-перше, я повинен отримати Model Object методом пошуку AR з полем id, а потім виконати операцію оновлення. Отже, з точки зору операцій одному ОНОВЛЕННЮ AR потрібно два sqls з базою даних; з іншого боку, я не впевнений, що метод оновлення AR використовує динамічне прив'язку. тому я хочу використовувати raw sql з динамічним прив'язуванням для однієї взаємодії з db для операції оновлення, але я не знаю, як передавати параметри для заміни? в sql за AR.
ywenbo

27
Для цього є багато вагомих причин. По-перше, запит може бути занадто складним, щоб перетворити його на використання звичайного способу Ruby ... по-друге, параметри можуть мати спеціальні символи, такі як% або лапки, і це боляче в дупі, щоб уникнути ...
Henley Chiu

3
@Andrew, краще використовувати необроблені функції mysql, ніж приймати "зручність", яку пропонує AR.
Зелений

4
@Green ні, якщо ви коли-небудь захочете перенести свою програму з MySQL на PostgreSQL або щось інше. Одним з основних моментів ORM є зробити ваш додаток портативним.
Ендрю

Відповіді:


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 :: Quiting .
Paul Annesley,

3
Зверніть увагу, mysql2 не підтримує підготовлені заяви, див. Також: stackoverflow.com/questions/9906437/…
reto

1
@reto Схоже, що вони близькі: github.com/brianmario/mysql2/pull/289
Брайан Детерлінг

3
Жодної prepareзаяви в Mysql2
Green

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_query,, exec_insertі exec_delete, залежно від того, що вам потрібно.


3
Відповідно до документації: "Примітка. Залежно від з'єднувача бази даних результатом, що повертається цим методом, може бути керовано пам'ять вручну. Подумайте про використання оболонки #exec_query." . executeє небезпечним методом і може спричинити витік пам'яті або інших ресурсів.
kolen

1
Вау хороший улов! Ось документація . Також схоже, що адаптер Postgres міг би просочитися тут.
Paul A Jungwirth,

Диво, якщо AR не залишає нам нічого для on duplicate key updateскрізь
Shrinath

1
@Shrinath, оскільки це питання стосується написання необробленого SQL, ви можете зробити запит, ON CONFLICT DO UPDATEякщо хочете. Для несировинного
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)

Наприклад, базовий клас "Особа", підкласи (та таблиці бази даних) "Клієнт" та "Продавець" Натомість використовуючи:

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 оновлення проти 1 на запис і велику пропускну здатність тощо? АР оптимізує це чи ні? Я не думаю, що це так.
Джимі Кімбл

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

Мені потрібно було використовувати raw 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-ін'єкція тут можлива!
mystdeim

-18

У Rails 3.1 вам слід використовувати інтерфейс запиту:

  • новий (атрибути)
  • створити (атрибути)
  • створити! (атрибути)
  • знайти (id_or_array)
  • знищити (id_or_array)
  • знищити_всі
  • видалити (id_or_array)
  • видалити всі
  • оновлення (ідентифікатори, оновлення)
  • update_all (оновлення)
  • існує?

update та update_all - це операція, яка вам потрібна.

Детальніше див. Тут: http://m.onkey.org/active-record-query-interface

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.