AREL:使用 from 子句编写复杂的更新语句

问题描述 投票:0回答:2

我尝试寻找使用

Arel::UpdateManager
形成带有 from 子句的更新语句的示例(如
UPDATE t SET t.itty = "b" FROM .... WHERE ...
中所示),但找不到任何示例。在我看来,
Arel::UpdateManager
在初始化时设置主引擎,并允许设置要更新的各种字段和值。实际上有办法做到这一点吗?

另一个问题是找出如何将 Postgres posix 正则表达式匹配表达为 ARel,但这现在可能是不可能的。

postgresql activerecord sql-update arel
2个回答
2
投票

据我所知,当前版本的

arel
gem 不支持 FROM 关键字进行 查询。您可以仅使用 SETWHERE 关键字生成查询,例如:

UPDATE t SET t.itty = "b" WHERE ...

field2
表的值从
field1
复制到
units
的代码如下:

relation = Unit.all
um = Arel::UpdateManager.new(relation.engine)
um.table(relation.table)
um.ast.wheres = relation.wheres.to_a
um.set(Arel::Nodes::SqlLiteral.new('field1 = "field2"'))
ActiveRecord::Base.connection.execute(um.to_sql)

确切地说,您可以使用附加方法来更新关系。因此,我们创建 Arel 的

UpdateManager
,为其分配表、
where
子句和要设置的值。值可以作为参数传递给该方法。然后我们需要在生成的
FROM
请求中添加
SQL
关键字,只有当我们能够通过
UPDATE
子句本身访问指定外部表时才添加它。最后我们执行查询。所以我们得到:

def update_relation!(relation, values)
   um = Arel::UpdateManager.new(relation.engine)
   um.table(relation.table)
   um.ast.wheres = relation.wheres.to_a
   um.set(values)
   sql = um.to_sql

   # appends FROM field to the query if needed
   m = sql.match(/WHERE/)
   tables = relation.arel.source.to_a.select {|v| v.class == Arel::Table }.map(&:name).uniq
   tables.shift
   sql.insert(m.begin(0), "FROM #{tables.join(",")} ") if m && !tables.empty?

   # executes the query
   ActiveRecord::Base.connection.execute(sql)
 end

您可以将关系更新发布为:

values = Arel::Nodes::SqlLiteral.new('field1 = "field2", field2 = NULL')
relation = Unit.not_rejected.where(Unit.arel_table[:field2].not_eq(nil))
update_relation!(relation, values)

0
投票

在 Малъ Скрылевъ 的答案的基础上,这就是我想出的,它可以让你写得与现有的

relation.update_all
(不支持
from
)非常相似:

relation.update_all_from('something = other_table.something', from: 'other_table')

示例:

Membership.joins(:user)
  .update_all_from('something = users.something_else', from: 'users')

来源如下:

class ActiveRecord::Relation
  # If you try to use update_all to do an SQL update that requires a "from" clause, you will end up getting:
  #   PG::UndefinedTable: ERROR:  missing FROM-clause entry for table "table_name"
  # This variation lets you add the needed "from" clause.
  #
  # Usage:
  #   relation.update_all_from('something = other_table.something', from: 'other_table')
  #
  # This is a cross between update_all from activerecord gem and
  # https://stackoverflow.com/questions/22501829/arel-writing-complex-update-statements-with-from-clause/23967126#23967126
  #
  def update_all_from(updates, from: nil)
    stmt = Arel::UpdateManager.new

    stmt.set Arel.sql(@klass.sanitize_sql_for_assignment(updates))
    stmt.table(table)

    if has_join_values? || offset_value
      @klass.connection.join_to_update(stmt, arel, arel_attribute(primary_key))
    else
      stmt.key = arel_attribute(primary_key)
      stmt.take(arel.limit)
      stmt.order(*arel.orders)
      stmt.wheres = arel.constraints
    end

    # Appends from clause to the query if needed
    sql = stmt.to_sql
    m = sql.match(/WHERE/)
    if from
      from = Array(from)
    else
      from = arel.source.to_a.select {|v| v.class == Arel::Table }.map(&:name).uniq[1..-1]
    end
    if m && !from.empty?
      sql.insert(m.begin(0), "FROM #{from.join(",")} ")
    end

    @klass.connection.update sql, "#{@klass} Update All"
  end
end
© www.soinside.com 2019 - 2024. All rights reserved.