我尝试寻找使用
Arel::UpdateManager
形成带有 from 子句的更新语句的示例(如 UPDATE t SET t.itty = "b" FROM .... WHERE ...
中所示),但找不到任何示例。在我看来,Arel::UpdateManager
在初始化时设置主引擎,并允许设置要更新的各种字段和值。实际上有办法做到这一点吗?
另一个问题是找出如何将 Postgres posix 正则表达式匹配表达为 ARel,但这现在可能是不可能的。
据我所知,当前版本的
arel
gem 不支持 FROM 关键字进行 sql 查询。您可以仅使用 SET 和 WHERE 关键字生成查询,例如:
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)
在 Малъ Скрылевъ 的答案的基础上,这就是我想出的,它可以让你写得与现有的
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