Skip to content

Commit 048734d

Browse files
authored
Use a self-join for UPDATE with outer joins (#1323)
1 parent 3973683 commit 048734d

File tree

1 file changed

+17
-37
lines changed

1 file changed

+17
-37
lines changed

lib/arel/visitors/sqlserver.rb

+17-37
Original file line numberDiff line numberDiff line change
@@ -42,57 +42,37 @@ def visit_Arel_Nodes_UpdateStatement(o, collector)
4242
#
4343
# UPDATE t1
4444
# SET ..
45-
# FROM t2
46-
# WHERE t1.join_id = t2.join_id
47-
#
48-
# Or if more than one join is present:
49-
#
50-
# UPDATE t1
51-
# SET ..
52-
# FROM t2
53-
# JOIN t3 ON t2.join_id = t3.join_id
54-
# WHERE t1.join_id = t2.join_id
45+
# FROM t1 JOIN t2 ON t2.join_id = t1.join_id ..
46+
# WHERE ..
5547
if has_join_sources?(o)
56-
visit o.relation.left, collector
48+
collector = visit o.relation.left, collector
5749
collect_nodes_for o.values, collector, " SET "
5850
collector << " FROM "
59-
first_join, *remaining_joins = o.relation.right
60-
from_items = remaining_joins.extract! do |join|
61-
join.right.expr.right.relation == o.relation.left
62-
end
63-
64-
from_where = [first_join.left] + from_items.map(&:left)
65-
collect_nodes_for from_where, collector, " ", ", "
66-
67-
if remaining_joins && !remaining_joins.empty?
68-
collector << " "
69-
remaining_joins.each do |join|
70-
visit join, collector
71-
collector << " "
72-
end
73-
end
74-
75-
from_where = [first_join.right.expr] + from_items.map { |i| i.right.expr }
76-
collect_nodes_for from_where + o.wheres, collector, " WHERE ", " AND "
51+
collector = inject_join o.relation.right, collector, " "
7752
else
7853
collector = visit o.relation, collector
7954
collect_nodes_for o.values, collector, " SET "
80-
collect_nodes_for o.wheres, collector, " WHERE ", " AND "
8155
end
8256

57+
collect_nodes_for o.wheres, collector, " WHERE ", " AND "
8358
collect_nodes_for o.orders, collector, " ORDER BY "
8459
maybe_visit o.limit, collector
8560
end
8661

87-
# Same as PostgreSQL and SQLite except we need to add limit if using subquery.
62+
# Similar to PostgreSQL and SQLite.
8863
def prepare_update_statement(o)
89-
if has_join_sources?(o) && !has_limit_or_offset_or_orders?(o) && !has_group_by_and_having?(o) &&
90-
# The dialect isn't flexible enough to allow anything other than a inner join
91-
# for the first join:
92-
# UPDATE table SET .. FROM joined_table WHERE ...
93-
(o.relation.right.all? { |join| join.is_a?(Arel::Nodes::InnerJoin) || join.right.expr.right.relation != o.relation.left })
94-
o
64+
if o.key && has_join_sources?(o) && !has_group_by_and_having?(o) && !has_limit_or_offset_or_orders?(o)
65+
# Join clauses cannot reference the target table, so alias the
66+
# updated table, place the entire relation in the FROM clause, and
67+
# add a self-join (which requires the primary key)
68+
stmt = o.clone
69+
70+
stmt.relation, stmt.wheres = o.relation.clone, o.wheres.clone
71+
stmt.relation.right = [stmt.relation.left, *stmt.relation.right]
72+
# Don't need to use alias
73+
stmt
9574
else
75+
# If using subquery, we need to add limit
9676
o.limit = Nodes::Limit.new(9_223_372_036_854_775_807) if o.orders.any? && o.limit.nil?
9777

9878
super

0 commit comments

Comments
 (0)