UPDATE(7)
π¦ UPDATE π¦
Okay bestie, this is the PostgreSQL 17.11 documentation page for UPDATE(7), and itβs literally the glow-up guide for changing rows in a table. No cap, itβs giving βmake it new, keep the old stuff you didnβt touchβ energy. π
πΈ NAME πΈ
UPDATEβ update rows of a table. Thatβs it. Thatβs the whole vibe. β¨
β¨ SYNOPSIS β¨
[ WITH [ RECURSIVE ] with_query [, ...] ]
UPDATE [ ONLY ] table_name [ * ] [ [ AS ] alias ]
SET { column_name = { expression | DEFAULT } |
( column_name [, ...] ) = [ ROW ] ( { expression | DEFAULT } [, ...] ) |
( column_name [, ...] ) = ( sub-SELECT )
} [, ...]
[ FROM from_item [, ...] ]
[ WHERE condition | WHERE CURRENT OF cursor_name ]
[ RETURNING { * | output_expression [ [ AS ] output_name ] } [, ...] ]
π DESCRIPTION π
UPDATE changes the values of the specified columns in all rows that satisfy the condition. Only the columns to be modified need to be mentioned in the SET clause; columns not explicitly modified retain their previous values. Periodt. π«Ά
There are two ways to modify a table using information contained in other tables in the database: using sub-selects, or specifying additional tables in the FROM clause. Which technique is more appropriate depends on the specific circumstances. Itβs giving βtwo paths, same destination, pick your fighter.β π
The optional RETURNING clause causes UPDATE to compute and return value(s) based on each row actually updated. Any expression using the tableβs columns, and/or columns of other tables mentioned in FROM, can be computed. The new (post-update) values of the tableβs columns are used. The syntax of the RETURNING list is identical to that of the output list of SELECT. So yes, you can get receipts. π§Ύβ¨
You must have the UPDATE privilege on the table, or at least on the column(s) that are listed to be updated. You must also have the SELECT privilege on any column whose values are read in the expressions or condition. Permissions check: slay only if youβre allowed. π
π PARAMETERS π
with_queryβ theWITHclause allows you to specify one or more subqueries that can be referenced by name in theUPDATEquery. See Section 7.8 andSELECT(7)for details. Itβs giving βnamed subquery besties.β π§table_nameβ the name (optionally schema-qualified) of the table to update. IfONLYis specified before the table name, matching rows are updated in the named table only. IfONLYis not specified, matching rows are also updated in any tables inheriting from the named table. Optionally,*can be specified after the table name to explicitly indicate that descendant tables are included. Know your table tree, bestie. π³aliasβ a substitute name for the target table. When an alias is provided, it completely hides the actual name of the table. For example, givenUPDATE foo AS f, the remainder of theUPDATEstatement must refer to this table asfnotfoo. No deadnaming the table. πcolumn_nameβ the name of a column in the table named bytable_name. The column name can be qualified with a subfield name or array subscript, if needed. Do not include the tableβs name in the specification of a target column β for example,UPDATE table_name SET table_name.col = 1is invalid. That one is lowkey a no. πexpressionβ an expression to assign to the column. The expression can use the old values of this and other columns in the table. Old values are still in the chat. π¬DEFAULTβ set the column to its default value (which will beNULLif no specific default expression has been assigned to it). An identity column will be set to a new value generated by the associated sequence. For a generated column, specifying this is permitted but merely specifies the normal behavior of computing the column from its generation expression. Default era. β¨sub-SELECTβ aSELECTsub-query that produces as many output columns as are listed in the parenthesized column list preceding it. The sub-query must yield no more than one row when executed. If it yields one row, its column values are assigned to the target columns; if it yields no rows,NULLvalues are assigned to the target columns. The sub-query can refer to old values of the current row of the table being updated. One row max, bestie. π¦from_itemβ a table expression allowing columns from other tables to appear in theWHEREcondition and update expressions. This uses the same syntax as theFROMclause of aSELECTstatement; for example, an alias for the table name can be specified. Do not repeat the target table as afrom_itemunless you intend a self-join (in which case it must appear with an alias in thefrom_item). Self-join is a choice, not a default. πͺconditionβ an expression that returns a value of typeboolean. Only rows for which this expression returns true will be updated. Boolean says yes or no, andUPDATElistens. βcursor_nameβ the name of the cursor to use in aWHERE CURRENT OFcondition. The row to be updated is the one most recently fetched from this cursor. The cursor must be a non-grouping query on theUPDATEβs target table. Note thatWHERE CURRENT OFcannot be specified together with a Boolean condition. SeeDECLARE(7)for more information about using cursors withWHERE CURRENT OF. Cursor said βthis one,β so itβs this one. π±οΈoutput_expressionβ an expression to be computed and returned by theUPDATEcommand after each row is updated. The expression can use any column names of the table named bytable_nameor table(s) listed inFROM. Write*to return all columns. Gimme the whole fit. πoutput_nameβ a name to use for a returned column. Name it and claim it. π·οΈ
π OUTPUTS π
On successful completion, an UPDATE command returns a command tag of the form:
UPDATE count
The count is the number of rows updated, including matched rows whose values did not change. Note that the number may be less than the number of rows that matched the condition when updates were suppressed by a BEFORE UPDATE trigger. If count is 0, no rows were updated by the query (this is not considered an error). Zero is valid, bestie. π
If the UPDATE command contains a RETURNING clause, the result will be similar to that of a SELECT statement containing the columns and values defined in the RETURNING list, computed over the row(s) updated by the command. Receipts, but make them SQL. π§Ύπ₯
πΈ NOTES πΈ
When a FROM clause is present, what essentially happens is that the target table is joined to the tables mentioned in the from_item list, and each output row of the join represents an update operation for the target table. When using FROM you should ensure that the join produces at most one output row for each row to be modified. In other words, a target row shouldnβt join to more than one row from the other table(s). If it does, then only one of the join rows will be used to update the target row, but which one will be used is not readily predictable. Thatβs not a cute mystery, thatβs a βplease donβt do thisβ mystery. π
Because of this indeterminacy, referencing other tables only within sub-selects is safer, though often harder to read and slower than using a join. Safety first, readability second, speed third. Pick your tradeoff, babe. π¦
In the case of a partitioned table, updating a row might cause it to no longer satisfy the partition constraint of the containing partition. In that case, if there is some other partition in the partition tree for which this row satisfies its partition constraint, then the row is moved to that partition. If there is no such partition, an error will occur. Behind the scenes, the row movement is actually a DELETE and INSERT operation. Plot twist: the move is a delete-and-insert combo. π
There is a possibility that a concurrent UPDATE or DELETE on the row being moved will get a serialization failure error. Suppose session 1 is performing an UPDATE on a partition key, and meanwhile a concurrent session 2 for which this row is visible performs an UPDATE or DELETE operation on this row. In such case, session 2βs UPDATE or DELETE will detect the row movement and raise a serialization failure error (which always returns with an SQLSTATE code '40001'). Applications may wish to retry the transaction if this occurs. In the usual case where the table is not partitioned, or where there is no row movement, session 2 would have identified the newly updated row and carried out the UPDATE/DELETE on this new row version. Concurrent chaos, honestly. π¨
Note that while rows can be moved from local partitions to a foreign-table partition (provided the foreign data wrapper supports tuple routing), they cannot be moved from a foreign-table partition to another partition. One-way trip, iykyk. π§³
An attempt of moving a row from one partition to another will fail if a foreign key is found to directly reference an ancestor of the source partition that is not the same as the ancestor thatβs mentioned in the UPDATE query. Foreign keys said βnot on my watch.β π
β¨ EXAMPLES β¨
Okay bestie, real talk β this is the part where it all clicks. Let's go π
πΈ Swapping out a word
So we wanna change the word Drama to Dramatic in the kind column of the table films:
UPDATE films SET kind = 'Dramatic' WHERE kind = 'Drama';
Periodt. One line, whole column fixed. π
π Tweaking temps and resetting a default
Adjusting the temperature entries and resetting prcp back to its default value, all in one row of the table weather:
UPDATE weather SET temp_lo = temp_lo+1, temp_hi = temp_lo+15, prcp = DEFAULT
WHERE city = 'San Francisco' AND date = '2003-07-03';
Same operation, but this time we get the updated entries back too:
UPDATE weather SET temp_lo = temp_lo+1, temp_hi = temp_lo+15, prcp = DEFAULT
WHERE city = 'San Francisco' AND date = '2003-07-03'
RETURNING temp_lo, temp_hi, prcp;
And if you're feeling the alternative column-list syntax vibe, same update, different outfit:
UPDATE weather SET (temp_lo, temp_hi, prcp) = (temp_lo+1, temp_lo+15, DEFAULT)
WHERE city = 'San Francisco' AND date = '2003-07-03';
π The FROM clause glow-up
Incrementing the sales count of whichever salesperson manages the account for Acme Corporation, using the FROM clause syntax:
UPDATE employees SET sales_count = sales_count + 1 FROM accounts
WHERE accounts.name = 'Acme Corporation'
AND employees.id = accounts.sales_person;
Same thing, but with a sub-select tucked inside the WHERE clause:
UPDATE employees SET sales_count = sales_count + 1 WHERE id =
(SELECT sales_person FROM accounts WHERE name = 'Acme Corporation');
π¦ Sub-select vs. join β the plot twist
Updating contact names in an accounts table so they match the currently assigned salespeople:
UPDATE accounts SET (contact_first_name, contact_last_name) =
(SELECT first_name, last_name FROM employees
WHERE employees.id = accounts.sales_person);
A similar result could be accomplished with a join β like, literally similar, not identical, and this is the important part:
UPDATE accounts SET contact_first_name = first_name,
contact_last_name = last_name
FROM employees WHERE employees.id = accounts.sales_person;
However (π¨ read this twice), the second query may give unexpected results if employees.id is not a unique key, whereas the first query is guaranteed to raise an error if there are multiple id matches. Also, if there's no match for a particular accounts.sales_person entry, the first query will set the corresponding name fields to NULL, whereas the second query will not update that row at all. Different behaviour, different vibe β know which one you're picking. π
π§ Summary-table stats refresh
Updating the statistics in a summary table so they match the current data:
UPDATE summary s SET (sum_x, sum_y, avg_x, avg_y) =
(SELECT sum(x), sum(y), avg(x), avg(y) FROM data d
WHERE d.group_id = s.group_id);
π The insert-or-update savepoint situation
Attempting to insert a new stock item along with the quantity of stock β and if the item already exists, updating the stock count of the existing item instead. To do this without failing the entire transaction, use savepoints:
BEGIN;
-- other operations
SAVEPOINT sp1;
INSERT INTO wines VALUES('Chateau Lafite 2003', '24');
-- Assume the above fails because of a unique key violation,
-- so now we issue these commands:
ROLLBACK TO sp1;
UPDATE wines SET stock = stock + 24 WHERE winename = 'Chateau Lafite 2003';
-- continue with other operations, and eventually
COMMIT;
Iconic. Transaction saved, vibe intact. π
π Cursor-targeted update
Changing the kind column of the table films in the row the cursor c_films is currently sitting on:
UPDATE films SET kind = 'Dramatic' WHERE CURRENT OF c_films;
π Batching the big ones (this part matters, ngl)
Updates affecting many rows can have negative effects on system performance, such as table bloat, increased replica lag, and increased lock contention. In such situations it can make sense to perform the operation in smaller batches, possibly with a VACUUM operation on the table between batches.
While there is no LIMIT clause for UPDATE, it is possible to get a similar effect through the use of a Common Table Expression and a self-join. With the standard PostgreSQL table access method, a self-join on the system column ctid is very efficient:
WITH exceeded_max_retries AS (
SELECT w.ctid FROM work_item AS w
WHERE w.status = 'active' AND w.num_retries > 10
ORDER BY w.retry_timestamp
FOR UPDATE
LIMIT 5000
)
UPDATE work_item SET status = 'failed'
FROM exceeded_max_retries AS emr
WHERE work_item.ctid = emr.ctid;
This command will need to be repeated until no rows remain to be updated. (This use of ctid is only safe because the query is repeatedly run, avoiding the problem of changed ctids.) π₯
Use of an ORDER BY clause allows the command to prioritize which rows will be updated; it can also prevent deadlock with other update operations if they use the same ordering. If lock contention is a concern, then SKIP LOCKED can be added to the CTE to prevent multiple commands from updating the same row. However, then a final UPDATE without SKIP LOCKED or LIMIT will be needed to ensure that no matching rows were overlooked. Don't skip that last step, bestie, I'm begging π
π COMPATIBILITY π
This command conforms to the SQL standard β except that the FROM and RETURNING clauses are PostgreSQL extensions, as is the ability to use WITH with UPDATE. So like, standard-adjacent. Standard with β¨flairβ¨.
Some other database systems offer a FROM option in which the target table is supposed to be listed again within FROM. That is not how PostgreSQL interprets FROM. Be careful when porting applications that use this extension β this is the kind of thing that bites you at 2am, fr fr. π
According to the standard, the source value for a parenthesized sub-list of target column names can be any row-valued expression yielding the correct number of columns. PostgreSQL only allows the source value to be a row constructor or a sub-SELECT. An individual column's updated value can be specified as DEFAULT in the row-constructor case, but not inside a sub-SELECT. No cap.
That's the whole page, bestie!! π Go forth and UPDATE responsibly β you've got this, and honestly? You're gonna slay it. π«Ά
This little masterpiece came to you from PostgreSQL 17.11, dated 2026, and it lives at UPDATE(7). Mwah. ππ
π¦ where this page comes from
- opensuse-leap 16.0: postgresql17 17.11-160000.1.1 Β· PostgreSQL
- opensuse-leap 16.0: postgresql17 17.11-160000.1.1 Β· PostgreSQL
- opensuse-leap 16.0: postgresql17 17.11-160000.1.1 Β· PostgreSQL
- opensuse-leap 16.0: postgresql17 17.11-160000.1.1 Β· PostgreSQL
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms