在Rails中使用连接执行update_all

时间:2011-07-26 13:52:16

标签: sql ruby-on-rails ruby scope sql-update

就像这样,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(',')})"]

4 个答案:

答案 0 :(得分:3)

“UPDATE FROM”不是标准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”方法,允许非标准select语句返回常规的活动记录模型实例。

答案 1 :(得分:3)

我至少相信@ 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中生成子选择,您可以混合使用像这样的小Arel:

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语法严重缺乏记录

答案 2 :(得分:2)

这纯粹是ruby / rails,不应该标记为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

答案 3 :(得分:0)

我相信以下

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)

这假设您在任务和项目之间拥有正确的关联。