Some SQL must do more than read a table or change a table. An application might need the identity value generated by an INSERT, the old salary from an UPDATE, or the customer address that was valid last month. Db2 for z/OS provides two different tool families for those jobs: data-change table references expose rows affected by one data-change statement, while temporal FOR clauses select versions that were valid at a point or across a period.
A data-change table reference wraps an INSERT, searched UPDATE, searched DELETE, or MERGE in parentheses and makes its directly affected rows look like an intermediate table. The outer SELECT can project those rows into the application. It is useful when the application needs values produced or replaced by the same statement that performs the change.
A temporal table is different. Its period columns describe when each version is valid. A period specification after the table name tells Db2 which versions to read. No data change is implied. Keeping these models separate prevents a common beginner mistake: FINAL TABLE means “the result of this change,” not “the latest temporal version.”
| Construct | What it represents | Db2 for z/OS use |
|---|---|---|
| FINAL TABLE | After the DML statement | INSERT, searched UPDATE, MERGE |
| OLD TABLE | Before the DML statement | Searched UPDATE, searched DELETE |
| NEW TABLE | New transition rows | Inside eligible triggers; not a data-change table reference |
FINAL TABLE returns the set of rows directly changed by its enclosed statement as those rows appear when that statement completes. It can wrap an INSERT, a searched UPDATE, or a MERGE. “Directly” matters: this is the transition result of the enclosed statement, not a second query of every row that triggers, cascading actions, or concurrent work might touch.
A frequent use is retrieving generated values without issuing a separate SELECT. In this example, Db2 inserts an order, generates ORDER_ID, and returns the generated key and creation timestamp through the outer SELECT:
1234567SELECT ORDER_ID, CREATED_TS INTO :HV-ORDER-ID, :HV-CREATED-TS FROM FINAL TABLE (INSERT INTO ORDERS (CUSTOMER_ID, ORDER_STATUS, CREATED_TS) VALUES (:HV-CUSTOMER-ID, 'NEW', CURRENT TIMESTAMP));
The application avoids a race-prone “find the highest ID” query. It also avoids repeating the search predicate. Required privileges still include the privilege for the data change and SELECT privilege on the target table or view.
The outer SELECT can return the new values for every qualifying row. This works well for an application that must log, display, or pass on the exact set it changed:
12345SELECT EMPNO, SALARY FROM FINAL TABLE (UPDATE EMP SET SALARY = SALARY * 1.03 WHERE WORKDEPT = 'A00');
The UPDATE remains set based. If ten rows qualify, the intermediate result contains ten after-images. An application cursor over a SELECT containing a data-change table reference is read only; it is not an updatable cursor over the target.
OLD TABLE exposes the rows as they existed before a searched UPDATE or searched DELETE. It is ideal for an audit feed that needs the values being replaced or removed. OLD TABLE does not wrap INSERT because an inserted row has no old image.
12345SELECT EMPNO, SALARY AS PREVIOUS_SALARY FROM OLD TABLE (UPDATE EMP SET SALARY = SALARY * 1.03 WHERE WORKDEPT = 'A00');
The salaries returned are the values before multiplication. The underlying UPDATE still occurs. OLD TABLE is therefore not an undo operation, a simulation, or a temporal query. Transaction rules remain normal: the change can still be committed or rolled back with its unit of work.
For DELETE, OLD TABLE is especially clear because the deleted rows no longer exist in the base table after the statement:
12345SELECT ORDER_ID, CUSTOMER_ID, ORDER_STATUS FROM OLD TABLE (DELETE FROM ORDERS WHERE ORDER_STATUS = 'CANCELLED' AND CREATED_TS < CURRENT TIMESTAMP - 2 YEARS);
Treat this as sensitive data. The result can contain every deleted value selected by the outer query, so authorization, masking, audit retention, and application logging deserve the same care as the original table.
It is tempting to write SELECT ... FROM NEW TABLE(UPDATE ...), especially after learning OLD TABLE. That is not Db2 for z/OS data-change table reference syntax. Use FINAL TABLE for the post-change result of application SQL.
NEW TABLE instead belongs to a trigger's REFERENCING clause. It gives a trigger a set-oriented transition table containing inserted or updated rows. OLD TABLE in a trigger is also a transition table; although the words are identical, its scope and grammar differ from an application SELECT that wraps a DML statement.
1234567891011CREATE TRIGGER AUDIT_ORDER_CHANGES AFTER UPDATE OF ORDER_STATUS ON ORDERS REFERENCING OLD TABLE AS O NEW TABLE AS N FOR EACH STATEMENT INSERT INTO ORDER_STATUS_AUDIT (ORDER_ID, OLD_STATUS, NEW_STATUS, CHANGED_AT) SELECT N.ORDER_ID, O.ORDER_STATUS, N.ORDER_STATUS, CURRENT TIMESTAMP FROM N JOIN O ON O.ORDER_ID = N.ORDER_ID;
The example is conceptual trigger SQL: the transition names exist only in the trigger body, are read only, and represent the complete affected set. OLD transition tables apply to UPDATE and DELETE events; NEW transition tables apply to INSERT and UPDATE events. Always verify the allowed trigger timing, granularity, transition references, and target design for the Db2 release you deploy.
Db2 supports two period meanings. A system-period temporal table uses a SYSTEM_TIME period whose begin and end values are maintained by Db2. With system period data versioning enabled, a related history table stores prior versions. This answers “what did the database record look like at that time?”
An application-period temporal table uses a BUSINESS_TIME period whose values are maintained by the application. It answers “when was this fact valid for the business?” A future price can be business-valid next month even though it was entered into the database today. A bitemporal table has both periods and can answer both dimensions together.
| Clause | Purpose | Requested endpoint behavior |
|---|---|---|
| AS OF value | Versions valid at one point | Point-in-time containment test |
| FROM value1 TO value2 | Versions overlapping a half-open requested interval | Upper requested endpoint is excluded |
| BETWEEN value1 AND value2 | Versions overlapping an inclusive requested range | Upper requested endpoint participates in the overlap test |
Place the period specification immediately after the temporal table reference. Db2 evaluates the SYSTEM_TIME begin and end columns and returns versions that existed at the supplied timestamp:
12345SELECT CUSTOMER_ID, CUSTOMER_NAME, CREDIT_LIMIT FROM CUSTOMER FOR SYSTEM_TIME AS OF TIMESTAMP('2026-03-31-23.59.59.000000') WHERE CUSTOMER_ID = 10042;
For the usual inclusive-exclusive system period, a row qualifies when its begin value is less than or equal to the AS OF value and its end value is greater than that value. A null AS OF value produces an empty table. The expression must be comparable to the period columns, and its timestamp precision cannot exceed their precision.
123456789SELECT CUSTOMER_ID, CUSTOMER_NAME, SYS_START, SYS_END FROM CUSTOMER FOR SYSTEM_TIME FROM TIMESTAMP('2026-01-01-00.00.00') TO TIMESTAMP('2026-04-01-00.00.00') WHERE CUSTOMER_ID = 10042 ORDER BY SYS_START;
FROM/TO finds row versions that overlap the requested interval from the first value up to, but not including, the second. If the start is greater than or equal to the end, the result is empty. BETWEEN/AND also tests overlap, but its requested upper endpoint is included. With equal endpoints, BETWEEN behaves like AS OF, while FROM/TO with equal endpoints is empty.
1234567SELECT CUSTOMER_ID, CREDIT_LIMIT, SYS_START, SYS_END FROM CUSTOMER FOR SYSTEM_TIME BETWEEN TIMESTAMP('2026-03-01-00.00.00') AND TIMESTAMP('2026-03-31-23.59.59') WHERE CUSTOMER_ID = 10042;
These are overlap queries, not filters requiring an entire row period to fit inside the requested range. That distinction explains why a version that began before the first date can still qualify: it remained valid during some part of the requested interval.
BUSINESS_TIME uses the same AS OF, FROM/TO, and BETWEEN/AND shapes, but applies them to an application-maintained period. If the period columns are DATE, use values comparable to DATE; if they are TIMESTAMP, use compatible timestamp expressions.
1234SELECT POLICY_ID, COVERAGE_CODE, PREMIUM FROM POLICY_RATE FOR BUSINESS_TIME AS OF DATE('2026-07-01') WHERE POLICY_ID = 77501;
1234567SELECT POLICY_ID, COVERAGE_CODE, PREMIUM, BUSINESS_START, BUSINESS_END FROM POLICY_RATE FOR BUSINESS_TIME FROM DATE('2026-01-01') TO DATE('2027-01-01') WHERE POLICY_ID = 77501 ORDER BY BUSINESS_START;
The first query asks which policy rate is business-valid on July 1. It does not ask when that row was physically inserted. The second asks for every version whose business-valid period overlaps calendar year 2026 under FROM/TO endpoint rules.
A bitemporal table can use each period specification once on the same table reference. The result must satisfy both. The next query asks: “Based on what the database knew on March 15, what rate was considered business-valid for July 1?”
1234567SELECT POLICY_ID, PREMIUM FROM POLICY_RATE FOR SYSTEM_TIME AS OF TIMESTAMP('2026-03-15-12.00.00') FOR BUSINESS_TIME AS OF DATE('2026-07-01') WHERE POLICY_ID = 77501;
Do not confuse a query period specification with FOR PORTION OF BUSINESS_TIME. FOR BUSINESS_TIME after a table name reads versions. FOR PORTION OF BUSINESS_TIME appears in supported UPDATE or DELETE statements and limits the business-time portion to change. Db2 can split a row's business period so the unaffected portions retain their prior values.
123456UPDATE POLICY_RATE FOR PORTION OF BUSINESS_TIME FROM DATE('2026-07-01') TO DATE('2026-10-01') SET PREMIUM = PREMIUM * 1.05 WHERE POLICY_ID = 77501;
This changes only the specified business-valid portion for qualifying rows. Period boundaries, uniqueness, constraints, and generated row splitting make application- period changes more than ordinary date predicates. Design and test them with sample periods that begin before, inside, and after the requested portion.
Imagine replacing a drawing on a classroom wall. OLD TABLE takes a photo just before you replace it. FINAL TABLE takes a photo of the new drawing after your change. A trigger's NEW TABLE is a tray where the teacher puts all the new drawings so a helper can inspect them.
Temporal tables are photo albums. SYSTEM_TIME asks, “What photo was in the album when the clock said Tuesday at noon?” BUSINESS_TIME asks, “Which drawing did our schedule say was valid for summer?” One follows the database clock; the other follows dates chosen by the business.
It is a table reference based on rows directly changed by an enclosed INSERT, searched UPDATE, searched DELETE, or MERGE. FINAL TABLE exposes post-change rows, while OLD TABLE exposes pre-change rows for supported UPDATE and DELETE statements.
No. Db2 for z/OS supports FINAL TABLE and OLD TABLE in data-change table reference syntax. NEW TABLE is a transition table declared by a trigger REFERENCING clause. Use FINAL TABLE when an application needs post-change rows from a DML statement.
FINAL TABLE returns the directly changed rows after the enclosed statement completes. OLD TABLE returns the affected rows as they existed before a searched UPDATE or DELETE was applied.
It queries a system-period temporal table or eligible view for row versions that existed at one timestamp. System-period begin and end values determine which current or historical version qualifies.
It queries an application-period temporal table or eligible view for rows whose application-maintained business period contains the requested date or timestamp.
Yes, on a bitemporal table they can be specified for the same table reference, once each. The result must satisfy both the system-time and business-time criteria.
1. Which expression returns rows as they appear after an INSERT, UPDATE, or MERGE?
2. Which data-change table reference can show values before a searched UPDATE or DELETE?
3. In Db2 for z/OS, what is NEW TABLE?
4. What does FOR SYSTEM_TIME AS OF :ts ask Db2 to return?
5. How does FROM :start TO :end differ from BETWEEN :start AND :end?
6. Who normally maintains BUSINESS_TIME values?