Как выполнить сырое обновление sql с динамическим связыванием в рельсах
Я хочу выполнить одно обновление raw sql, как показано ниже:
update table set f1=? where f2=? and f3=?
Этот SQL будет выполнен ActiveRecord::Base.connection.execute
, но я не знаю, как передать значения динамического параметра в метод.
Может ли кто-нибудь помочь мне с этим?
9 ответов
Это не похоже на то, что 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, как сказали комментаторы.
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
в зависимости от того, что вам нужно.
Ни один из других ответов не показал мне, как использовать именованные параметры, поэтому я закончил объединение
exec_update
с участием
sanitize_sql
:
User.connection.exec_update(
User.sanitize_sql(
[
"update users set name = :name where id = :id and name <> :name",
{
id: 123,
name: 'My Name'
}
]
)
)
У меня это работает на Rails 5, и он выполняет этот SQL:
update users set name = 'My Name' where id = 123 and name <> 'My Name'
Вам нужно использовать существующую модель Rails вместо
User
если у вас этого нет.
Я хотел использовать именованные параметры, чтобы избежать проблем с порядком, когда я использую
?
или
$1
/
$2
,так далее. Позиционное упорядочение вызывает разочарование, когда у меня больше, чем несколько параметров, но именованные параметры позволяют мне реорганизовать команду SQL без необходимости обновлять параметры.
Вы должны просто использовать что-то вроде:
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 на самом деле является защищенным методом.
Иногда лучше использовать имя родительского класса вместо имени таблицы:
# 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)
Вот трюк, который я недавно разработал для выполнения необработанного 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 связывает свои собственные запросы.
Зачем использовать сырой 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
так же, как указано выше:
Мне нужно было использовать raw sql, потому что мне не удалось заставить Combo_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
Если есть лучшее решение, пожалуйста, поделитесь.
В Rails 3.1 вы должны использовать интерфейс запросов:
- новые (атрибуты)
- создавать (атрибуты)
- создать!(атрибуты)
- найти (id_or_array)
- уничтожить (id_or_array)
- destroy_all
- удалить (id_or_array)
- удалить все
- обновление (идентификаторы, обновления)
- update_all (обновления)
- существует?
update и update_all - это операция, которая вам нужна.
Подробности смотрите здесь: http://m.onkey.org/active-record-query-interface