Rails 3, пользовательский SQL-запрос
У меня есть модель оценки. Оценка имеет много баллов. Всякий раз, когда создается новая оценка, для каждого пользователя, который нуждается в оценке, создается запись о баллах (см. Ниже для текущего метода, который я использую для этого). Так, например, 40 записей за каждый счет могут быть созданы сразу. Затем владелец оценки обновляет каждую запись с помощью оценки пользователя.
Я ищу использовать исходный SQL, потому что каждая вставка - это собственная транзакция и медленная.
Я хотел бы преобразовать следующее в инструкцию массовой вставки с использованием raw SQL:
def build_evaluation_score_items
self.job.active_employees.each do |employee|
employee_score = self.scores.build
employee_score.user_id = employee.id
employee_score.save
end
end
Любые мысли о том, как это можно сделать? Я пробовал адаптировать образец кода с сайта Chris Heald Coffee Powered, но без кубиков.
Спасибо всем, кто хочет помочь!
ИЗМЕНИТЬ 1
Я забыл упомянуть, что текущий метод заключен в транзакцию.
Итак, по сути, я пытаюсь добавить это в блок кода, поэтому все вставлено в один оператор (** Этот фрагмент кода находится на сайте Chris Heald Coffee Powered, на котором обсуждалась тема. Я задал бы вопрос, но пост > 3 года.):
inserts = []
TIMES.times do
inserts.push "(3.0, '2009-01-23 20:21:13', 2, 1)"
end
sql = "INSERT INTO user_node_scores (`score`, `updated_at`, `node_id`, `user_id`)VALUES #{inserts.join(", ")}"
Я был бы рад показать код из некоторых моих попыток, которые не работают...
Еще раз спасибо!
Ну, я собрал что-то похожее на код выше, но я получаю неверную ошибку SQL-запроса вокруг части ('evaluation_id'. Любые мысли?
def build_evaluation_score_items
inserts = []
self.job.active_employees.each do |employee|
inserts.push "(#{self.id}, #{employee.id}, #{Time.now}, #{Time.now})"
end
sql = "INSERT INTO scores ('evaluation_id', `user_id`, 'created_at', `updated_at`)VALUES #{inserts.join(", ")}"
ActiveRecord::Base.connection.execute(sql)
end
Любая идея относительно того, что в приведенном выше коде SQL вызывает ошибку?
Ответы
Ответ 1
Хорошо, после долгих проб и ошибок, вот окончательный ответ. Самое приятное, что все записи вставляются через один оператор. Конечно, валидации пропущены (так что это будет нецелесообразно, если вам потребуются проверки модели при создании), но в моем случае это не обязательно, потому что все, что я делаю, это создание записи оценки для каждой оценки сотрудника. Конечно, валидации работают так, как ожидалось, когда руководитель задания обновляет оценочную оценку сотрудника.
def build_evaluation_score_items
inserts = []
time = Time.now.to_s(:db)
self.job.active_employees.each do |employee|
inserts.push "(#{self.id}, #{employee.id}, '#{time}')"
end
sql = "INSERT INTO scores (evaluation_id, user_id, created_at) VALUES #{inserts.join(", ")}"
ActiveRecord::Base.connection.execute(sql)
end
Ответ 2
Вместо того, чтобы напрямую строить SQL (и открывать себя для SQL-инъекций и других проблем), я бы рекомендовал animerecord-import gem. Он может вызывать команды нескольких строк INSERT
, среди других стратегий.
Затем вы можете написать что-то вроде:
def build_evaluation_score_items
new_scores = job.active_employees.map do |employee|
scores.build(:user_id => employee.id)
end
Score.import new_scores
end
Ответ 3
Я думаю, что вы ищете:
def build_evaluation_score_items
ActiveRecord::Base.transaction do
self.job.active_employees.each do |employee|
employee_score = self.scores.build
employee_score.user_id = employee.id
employee_score.save
end
end
end
Все дочерние транзакции автоматически "подталкиваются" до родительской транзакции. Это предотвратит накладные расходы на столько транзакций и должно повысить производительность.
Подробнее об транзакциях ActiveRecord можно прочитать здесь.
UPDATE
Извините, я неправильно понял. Сохранение вышеуказанного ответа для потомков. Попробуйте следующее:
def build_evaluation_score_items
raw_sql = "INSERT INTO your_table ('user_id', 'something_else') VALUES "
insert_values = "('%s', '%s'),"
self.job.active_employees.each do |employee|
raw_sql += insert_values % employee.id, "something else"
end
ActiveRecord::Base.connection.execute raw_sql
end