UPDATE is how you change values that already live in a table. In DB2 for z/OS there are two shapes: a searched UPDATE that applies a SET list to every row whose WHERE condition is TRUE, and a positioned UPDATE that changes the row (or rowset) a cursor is sitting on. This page covers SET, subqueries, the z/OS way to “join” while updating, generated columns, temporal data, XML, and LOBs.
The object of UPDATE is a base table or an updatable view. Catalog tables can be updated only in the few columns IBM marks updatable. You need UPDATE privilege on the columns you assign, and SELECT privilege on columns you read in SET or WHERE.
| Form | How rows are chosen | Typical use |
|---|---|---|
| Searched UPDATE | WHERE search-condition (or omit WHERE for all rows) | Set-based business change |
| Positioned UPDATE | WHERE CURRENT OF cursor-name | COBOL/CICS row-at-a-time after FETCH |
If you omit WHERE on a searched UPDATE, Db2 updates every row. That is legal and almost never what a production program meant. After a successful statement, SQLERRD(3) in the SQLCA is normally the number of rows updated. Zero rows is not an SQL error; you still check SQLCODE 0 and the count. Exclusive locks are taken on updated rows until commit or rollback (uncommitted-read readers can still see the row if you did not update LOBs).
SET lists the columns to change. Each target column may appear once. You cannot assign a generated column (except via DEFAULT) or a view column that is an expression. The right-hand side is one of:
| Value | What it does |
|---|---|
| expression | Compute the new value (column, literal, function, scalar subquery) |
| DEFAULT | Store the column default; required idea for GENERATED ALWAYS |
| NULL | Store the null value; only for nullable columns |
| row-fullselect | Assign several columns from one returned row |
123456789101112UPDATE DSN8C10.EMP SET PHONENO = '3565' WHERE EMPNO = '000190'; UPDATE DSN8C10.EMP SET SALARY = SALARY + 100 WHERE WORKDEPT = 'D11'; -- Several columns, one assignment list UPDATE DSN8C10.EMP SET (SALARY, BONUS, COMM) = (NULL, NULL, NULL) WHERE EMPNO = '000250';
An expression on the right sees the old column values of that row. SALARY = SALARY + 100 uses the salary before this UPDATE. DEFAULT stores the column’s default (identity, CURRENT TIMESTAMP, a literal default, and so on). NULL is only legal for nullable columns.
Multiple-column assignment (the parenthesized list) is the same idea as assigning each column separately, except a row-fullselect can fill the whole list in one trip. If that fullselect returns no row, each target is set to null (error if any target is NOT NULL). More than one row is an error.
A scalar subquery in SET must return one column and at most one row. IBM’s sample updates PROJSIZE from a count of projects:
12345UPDATE DSN8C10.EMP SET PROJSIZE = (SELECT COUNT(*) FROM DSN8C10.PROJ WHERE DEPTNO = 'E21') WHERE WORKDEPT = 'E21';
Correlate when the subquery should follow the current target row. Give the target a correlation name so the subquery can see it. If the same table is the object of both UPDATE and a subquery in WHERE, Db2 fully evaluates that subquery before any row is updated, so you do not chase a moving average mid-statement.
12345UPDATE EMP X SET SALARY = 1.10 * SALARY WHERE SALARY < (SELECT AVG(SALARY) FROM EMP Y WHERE X.JOBCODE = Y.JOBCODE);
Db2 LUW has UPDATE … FROM with a join. Db2 for z/OS does not. On z/OS you either:
12345678-- Copy department name onto an employee work table UPDATE HR.EMP_WORK E SET DEPTNAME = (SELECT D.DEPTNAME FROM HR.DEPARTMENT D WHERE D.DEPTNO = E.WORKDEPT) WHERE EXISTS (SELECT 1 FROM HR.DEPARTMENT D WHERE D.DEPTNO = E.WORKDEPT);
If the subquery can return more than one department row, you get SQLCODE -811. Make the subquery unique (primary key join) or use MERGE with a well-defined ON predicate.
In COBOL, C, or PL/I you often FETCH a row, show it, then change it. Declare the cursor FOR UPDATE OF the columns you will SET (or accept the default updatable column list). Then:
123456789101112EXEC SQL DECLARE C1 CURSOR FOR SELECT EMPNO, SALARY FROM HR.EMPLOYEE WHERE WORKDEPT = :HV-DEPT FOR UPDATE OF SALARY END-EXEC. EXEC SQL UPDATE HR.EMPLOYEE SET SALARY = SALARY + :HV-RAISE WHERE CURRENT OF C1 END-EXEC.
The cursor must be open and positioned. FOR ROW n OF ROWSET updates one row of a rowset cursor; WHERE CURRENT OF on a rowset updates every row in the current rowset. Do not use a read-only cursor (FOR READ ONLY, UNION, DISTINCT, and similar) for positioned UPDATE.
GENERATED ALWAYS columns (identity, row-begin, row-change-timestamp, expression columns) are maintained by Db2. In SET, specify DEFAULT if you must list the column, or leave it out of the assignment list. GENERATED BY DEFAULT may accept a user value. OVERRIDING USER VALUE on related statements tells Db2 to ignore an application value; for UPDATE the usual rule is still DEFAULT for ALWAYS columns. Extended indicator UNASSIGNED means “do not treat this column as updated,” which also affects which UPDATE triggers see the column.
On a system-period temporal table, an UPDATE of a current row inserts a historical copy into the history table and stamps row-begin / row-end / transaction-start-ID columns. You query history later with FOR SYSTEM_TIME. Do not UPDATE the history table as if it were an ordinary base table.
On an application-period (BUSINESS_TIME) table, FOR PORTION OF BUSINESS_TIME FROM v1 TO v2 (or BETWEEN v1 AND v2) updates only that slice of time. Db2 may split the original row and insert extra rows for the untouched portions, firing INSERT triggers for those automatic inserts and UPDATE triggers for the changed slice. Unique keys that include the period columns can fail if a split would duplicate a key—plan the key design before you use portion updates.
12345UPDATE HR.POLICY FOR PORTION OF BUSINESS_TIME FROM DATE('2024-01-01') TO DATE('2024-07-01') SET PREMIUM = PREMIUM * 1.05 WHERE POLICY_ID = 'P100';
An XML column can be replaced as a whole (SET XMLCOL = :HV-XML) or changed in place with XMLMODIFY, which applies an XQuery updating expression and returns a new XML value. Validate against an XML schema when your shop requires it. Do not treat XML as VARCHAR concatenation.
12345UPDATE HR.EMP_DOCS SET RESUME_XML = XMLMODIFY( 'replace value of node /resume/phone with $p', PASSING :HV-PHONE AS "p") WHERE EMPNO = :HV-EMPNO;
LOB columns (CLOB, BLOB, DBCLOB) accept host variables, LOB locators, or expressions. IBM’s sample EMP1 table updates a CLOB resume through a user-defined function while also bumping SALARY, keyed by ROWID. Large LOB updates log heavily unless the table space is NOT LOGGED (a design choice with recovery consequences). Host variables need the correct SQL TYPE and, for nulls, indicator variables.
UPDATE is changing the writing on a library card that already exists. SET is which lines you erase and rewrite. WHERE is “only cards for this kid.” If you forget WHERE, you rewrite every card in the drawer. A cursor UPDATE is “change the card I am holding in my hand.” A subquery is looking at another drawer to decide the new number. Temporal tables keep a photocopy of the old card in a history box before you write the new one.
1. What happens if you omit WHERE on a searched UPDATE?
2. How do you update the row a cursor is sitting on?
3. How should a GENERATED ALWAYS column be assigned in SET?
4. Does Db2 for z/OS support UPDATE … FROM t1 JOIN t2?
5. What does SQLERRD(3) usually contain after a successful UPDATE?