Yesterday I discovered something strange about SQL, or at least PostreSQL. Take a look below and explain why first query does nothing and the second one works just right.
-- this silently does nothing
update bodycontent
set body = replace(body, '~' || u.oldusername, '~' || u.newusername)
from usermigration u;
-- this works as expected
update bodycontent
set body = replace(body, '~' || oldusername, '~' || newusername)
from usermigration u;
Update: I think that everyone is missing the point on this question, the cartesian product was an original intent: there are going to be N x M updates and this is by design.
I need to replace all the pairs of usernames existing in the migration table in each row from bodycontent.
And, I repeat, the second version works as expected, but the first one does no update. All I wanted to know was why.
| usermigration table |
--------------------------
oldusername | newusersname
--------------------------
johndoe | johnd
john.smith | johnsmith
Is this a bug in PostgreSQL?
You are missing a WHERE clause (that cannot be written as direct
JOINcondition). The additional tableusermigrationhas to be bound to the tablebodycontentto be updated in some way, or every row ofbodycontenthas as many update candidates as there are rows inusermigration– the cartesian product between the two tables.There is no way to tell which one will be applied and persist. Both statements are wrong in this regard. For instance, if there are 1000 rows in
usermigrationand 1000 rows inbodycontentthis will results in 1 000 000 updates candidates before 1000 can be picked.If you join in one or more tables in an
UPDATEstatement it hardly ever makes any sense without aWHEREclause connecting the result of theFROMclause to the updated table.Consider these notes in the manual about the UPDATE statement:
Note that the
FROMclause in theUPDATEstatement is a PostgreSQL extension to the SQL standard. Other DBMS use different syntax, for instance explicitJOINs to the table to be updated (in tSQL) that do not work for PostgreSQL.Answer to additional question in comment
This query should work, mostly1
1 The outcome is still ambiguous. Multiple matches can be found. It is uncertain, which one will be applied. The problem is that your requirements are inherently ambiguous. There can be multiple (overlapping) usernames that match and the order in which updates are applied is relevant (but not defined). The
UPDATEstatement perfectly reflects your flawed requirements.And what’s up with the
'~' ||part?