Выполнение update_all с объединениями в Rails
Как раз так, что абстракция Rails от необработанного SQL приводит меня в замешательство. В MySQL я мог бы сделать это:
UPDATE FROM tasks AS t
LEFT JOIN projects as p
ON t.project_id = p.id
SET t.invoice_id = 7
WHERE p.organization_id == 42
AND t.invoice_id IS NULL
Как я могу сделать это в Rails 3.0.1 с нетерпением загрузки? Я пробовал все следующее:
Tasks.joins(:project).where('projects.organization_id' => 42, :invoice_id => nil).update_all( :invoice_id => 7 )
И все варианты вышеизложенного. Все либо давали ошибки, либо ничего не нашли.
Затем я попытался использовать scope
:
Task.scope :find => {:joins => :project, :conditions => ["projects.organization_id == ? AND invoice_id IS NULL", @organization.id] } do
Task.update_all :invoice_id => @invoice.id
end
Это дало мне ошибку undefined method 'to_sym' for #<Hash:0x1065c6438>
.
Я потратил слишком много часов на это, просто чтобы воспроизвести простой SQL-запрос. Пожалуйста, помогите!
РЕДАКТИРОВАТЬ: Временное плохое решение, чтобы обойти n + 1:
task_ids = Task.select('tasks.id').joins(:project).where('projects.organization_id' => @organization.id, :invoice_id => nil).collect{|t| t.id}
Task.update_all ['invoice_id = ?', @invoice.id], ["id in (#{task_ids.join(',')})"]
Ответы
Ответ 1
"ОБНОВЛЕНИЕ ОТ" не является стандартным SQL, поэтому неудивительно, если он не поддерживается непосредственно Active Record. Тем не менее, Active Record дает вам возможность обойти свои абстракции и просто выпускать прямой SQL, в те моменты, когда вы должны делать что-то, что не поддерживает. Внутри модели:
sql = "UPDATE FROM tasks AS t
LEFT JOIN projects as p
ON t.project_id = p.id
SET t.invoice_id = 7
WHERE p.organization_id == 42
AND t.invoice_id IS NULL"
connection.update_sql(sql)
ActiveRecord:: Base также имеет метод select_by_sql, который позволяет вашим нестандартным операторам выбора возвращать регулярные экземпляры активной активной записи.
Ответ 2
Я считаю, что по крайней мере @rails 3.0.8 и ARel 2.0.10 мы не могли напрямую генерировать UPDATE FROM, но получить тот же результат можно, разрешив соединение в качестве подзапроса, например
Task.where(:invoice_id=>nil).
where(:project_id=>Project.where(:organization_id=>42).collect(&:id)).
update_all(:invoice_id => 7)
Это генерирует SQL как:
UPDATE "tasks"
SET "invoice_id" = 7
WHERE "invoice_id" IS NULL AND "project_id" IN (1,2,3);
-- assuming projects 1,2,3 have organization_id = 42
Из нашего, что разрешает подзапрос в Rails не в SQL. Чтобы сгенерировать подвыбор в SQL, вы можете смешать немного Ареля следующим образом:
t = Task.arel_table
p = Project.arel_table
Task.where(:invoice_id=>nil).
where(
t[:project_id].in(
p.where(
p[:organization_id].eq(42)
).project(p[:id])
)
).update_all(:invoice_id => 7)
Что генерирует sql, как это:
UPDATE "tasks"
SET "invoice_id" = 7
WHERE "invoice_id" IS NULL
AND "project_id" IN (
SELECT "projects"."id" FROM "projects"
WHERE "projects"."organization_id" = 42
);
Здесь есть чистый способ ARel, но синтаксис UpdateManager сильно недооценен
Ответ 3
Это чисто рубины/рельсы, он не должен быть помечен как SQL -
Единственная информация о SQL, которую вы могли бы получить, - это начать с другого синтаксического эквивалента вместо "обновления из", который не является стандартным, например, например (это я бы тоже не сделал, но эй, я не использую ruby /рельсы).
UPDATE tasks t
SET t.invoice_id=7
WHERE
t.invoice_id IS NULL
AND
(SELECT
p.organization_id
FROM tasks t2
LEFT JOIN projects p
ON t.project_id=p.id
WHERE t2.id=t.id)=42
Ответ 4
Я считаю, что следующие
UPDATE FROM tasks AS t
LEFT JOIN projects as p
ON t.project_id = p.id
SET t.invoice_id = 7
WHERE p.organization_id == 42
AND t.invoice_id IS NULL
может быть записан как следующий запрос Arel:
Tasks.include(:projects).where("projects.organization_id = ?", 42).where("tasks.invoice_id IS NULL").update_all("tasks.invoice_id = ?", 7)
Это предполагает, что у вас есть правильная связь между задачами и проектами.