【问题标题】:Doing an update_all with joins in Rails在 Rails 中使用连接执行 update_all
【发布时间】:2011-10-13 10:47:40
【问题描述】:

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(',')})"]

【问题讨论】:

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


    【解决方案1】:

    “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”方法,可以让您的非标准选择语句返回常规的活动记录模型实例。

    【讨论】:

    • 谢谢,韦恩。如果可能的话,我想抽象一下,因为不同风格的 SQL 处理这种查询的方式完全不同,我担心它在未来会中断。我上面的示例可以更改为update tasks, projects set...,我仍然会遇到麻烦。如果您有进一步的想法,我将不胜感激!
    • @Jed,那也是非标准 SQL。我不知道 Active Record 会如何抽象它。您可以使用 SQL 执行的某些操作与 CRUD 抽象不太匹配。尤其是这个。
    • 有趣。那么 1+n(非急切加载)真的是在抽象层执行此操作的唯一方法吗?我不敢相信scope 不能以某种方式提供解决方案。不应该像我的第二次努力那样工作吗?现在只使用 1+n,但绝对不是一个可持续的解决方案。感谢您的帮助...
    • @Jed,“更新自”和多次更新取决于 SQL 服务器。我不确定如何在客户端实现这些。即使他们可以,我想性能也会很糟糕,至少涉及将每条相关记录从 SQL 服务器传输到客户端。
    【解决方案2】:

    我相信以下

    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)
    

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

    【讨论】:

      【解决方案3】:

      这纯粹是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
      

      【讨论】:

        【解决方案4】:

        我相信至少 @ 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 语法的文档记录严重不足

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2019-10-20
          • 2011-08-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-08-25
          • 2013-10-30
          相关资源
          最近更新 更多