Temporary Tables in Db2 for i – Efficient, Flexible, but Plan Carefully

Futuristic infographic about temporary tables in Db2 for i with SESSION, QTEMP, CTE, TRUNCATE, and index examples.
Temporary tables in Db2 for i: SESSION, QTEMP, CTE, TRUNCATE and indexing in practice.

Declared Global Temporary Tables, CTEs, QTEMP, TRUNCATE, and Indexes – What Really Matters on IBM i

Temporary tables are true everyday heroes in Db2 for i. They store intermediate results only for the current application context – on IBM i this usually means the current connection or job. This relieves productive data structures and makes SQL processes significantly clearer. At the same time, they are not a magic performance booster. Whether they outperform CTEs, views, or persistent tables depends heavily on the specific workload, data volumes, access patterns, existing indexes, and the SQL execution plan.

What Are Temporary Tables?

In short: Temporary tables store data only during the current application context. On IBM i this corresponds in practice mostly to the current connection or job. When this context ends, both definition and content disappear automatically – ideal for volatile intermediate states, reports, staging steps, and more complex SQL workflows.

On IBM i you work with DECLARE GLOBAL TEMPORARY TABLE (abbreviated DGTT). The table is not persistent, does not appear in the regular system catalog, and cannot be shared across application contexts. Each connection or relevant job context receives its own instance.

Technical Foundation

The table name must be qualified with the SESSION schema. Db2 for i creates the object internally in QTEMP. In SQL, however, you normally reference the table as SESSION.YOUR_NAME.

SQL · Simple DGTT
DECLARE GLOBAL TEMPORARY TABLE SESSION.TEMP_EMP (
    EMPNO CHAR(6) NOT NULL,
    SALARY DECIMAL(10, 2),
    DEPTNO CHAR(3)
)
WITH REPLACE
ON COMMIT PRESERVE ROWS
NOT LOGGED;

WITH REPLACE replaces any existing temporary table with the same name in the current application context and deletes its previous rows. ON COMMIT PRESERVE ROWS keeps the rows after a commit. Without this clause the default ON COMMIT DELETE ROWS applies – rows may disappear on commit unless a dependent WITH HOLD cursor remains open. NOT LOGGED reduces logging overhead, but changes to this table are not rolled back like normal table changes.

DGTT on IBM i: SESSION and QTEMP

A common misconception: because the table physically lands in QTEMP, developers sometimes declare it that way. That is not the correct approach for DECLARE GLOBAL TEMPORARY TABLE on IBM i. The qualifier must be SESSION. If you omit the qualifier, Db2 for i implicitly uses SESSION.

Warning: DECLARE GLOBAL TEMPORARY TABLE QTEMP.MY_TEMP ... is incorrect for a DGTT. If you supply a qualifier it must be SESSION. You can also omit the qualifier entirely; Db2 for i will then assume SESSION implicitly.

Name Resolution

When a DGTT exists, SESSION.TEMP_EMP resolves to that temporary table. If it does not exist in the current context, Db2 for i does not treat the name as a DGTT reference and may resolve it as a permanent table in the SESSION schema. In stored procedures and RPG programs it is therefore safer to declare the temporary table explicitly before you use it.

Optional: FOR SYSTEM NAME

When you work with long SQL names you can optionally assign an IBM i system name. This name is limited to ten characters and can be useful in very low-level scenarios. For normal SQL usage the option is rarely needed.

SQL · DGTT with System Name
DECLARE GLOBAL TEMPORARY TABLE SESSION.TEMP_EMPLOYEE_REPORT (
    EMPNO CHAR(6),
    DEPTNO CHAR(3),
    TOTAL_SALARY DECIMAL(15,2)
)
FOR SYSTEM NAME TEMPEMP01
WITH REPLACE
ON COMMIT PRESERVE ROWS
NOT LOGGED;

SQL Examples to Replicate

1. Derive Structure from an Existing Table

You can derive the structure of a DGTT from a query or adopt it directly from an existing table:

SQL · Adopt Structure
-- Variant A: Using SELECT
DECLARE GLOBAL TEMPORARY TABLE SESSION.TEMP_EMP AS (
  SELECT EMPNO, SALARY, DEPTNO
  FROM MYLIB.EMPLOYEES
)
WITH NO DATA
ON COMMIT PRESERVE ROWS
NOT LOGGED;

-- Variant B: Using LIKE (very clean)
DECLARE GLOBAL TEMPORARY TABLE SESSION.TEMP_EMP
LIKE MYLIB.EMPLOYEES
WITH REPLACE
ON COMMIT PRESERVE ROWS
NOT LOGGED;

2. CTE for a One-Time Intermediate Result

If you need an intermediate result only inside a single SQL statement, a CTE is often more elegant:

SQL · CTE
WITH TempSales AS (
  SELECT ORDERID, SUM(AMOUNT) AS TOTAL
  FROM MYLIB.SALES
  GROUP BY ORDERID
)
SELECT *
FROM TempSales
WHERE TOTAL > 1000;

3. Combining CTE and Temporary Table

For multi-stage processing a DGTT is often useful: populate once, evaluate multiple times, and optionally create indexes.

SQL · Populate DGTT from CTE
DECLARE GLOBAL TEMPORARY TABLE SESSION.TEMP_AVGSAL (
  DEPTNO CHAR(3),
  AVG_SAL DECIMAL(11,2)
)
WITH REPLACE
ON COMMIT PRESERVE ROWS
NOT LOGGED;

INSERT INTO SESSION.TEMP_AVGSAL
WITH Aggregated AS (
  SELECT DEPTNO, DECIMAL(AVG(SALARY), 11, 2) AS AVG_SAL
  FROM MYLIB.EMPLOYEES
  GROUP BY DEPTNO
)
SELECT DEPTNO, AVG_SAL
FROM Aggregated;

TRUNCATE TABLE vs. WITH REPLACE

WITH REPLACE is convenient when you want to robustly redeclare a temporary table. For repeated processing steps that use the same structure, it is usually cleaner to create the table once and then empty it with TRUNCATE TABLE.

When to Use TRUNCATE?

  • The structure stays the same.
  • The table is populated multiple times in the same job or procedure.
  • Existing indexes on the temporary table should be preserved.
  • You want to reduce the overhead of repeated declarations.
SQL · Reusable DGTT
DECLARE GLOBAL TEMPORARY TABLE SESSION.MONTHLY_SALES (
    MONTH_ID INT,
    TOTAL_SALES DECIMAL(15,2)
)
WITH REPLACE
ON COMMIT PRESERVE ROWS
NOT LOGGED;

-- Load January
INSERT INTO SESSION.MONTHLY_SALES
SELECT 1, SUM(AMOUNT)
FROM MYLIB.SALES
WHERE ORDER_DATE BETWEEN DATE('2026-01-01') AND DATE('2026-01-31');

SELECT * FROM SESSION.MONTHLY_SALES;

-- Empty the table for the next step
TRUNCATE TABLE SESSION.MONTHLY_SALES REUSE STORAGE;

-- Load February
INSERT INTO SESSION.MONTHLY_SALES
SELECT 2, SUM(AMOUNT)
FROM MYLIB.SALES
WHERE ORDER_DATE BETWEEN DATE('2026-02-01') AND DATE('2026-02-28');
Practical Tip: REUSE STORAGE is usually the better choice for repeated loads because already allocated storage can be reused. The default is DROP STORAGE, which releases the storage. Be careful with IMMEDIATE: a TRUNCATE TABLE ... IMMEDIATE cannot be rolled back. Without IMMEDIATE a rollback can undo the truncate. As always, measure it in your own job.

Indexes on Temporary Tables

Temporary tables are not automatically well optimized. When you filter, join, or sort larger intermediate results multiple times, an index on the DGTT can help.

SQL · Index on DGTT
CREATE INDEX SESSION.IDX_TEMP_EMP_DEPT
ON SESSION.TEMP_EMP (DEPTNO, EMPNO);
Don’t overdo it: An index costs time to build and adds overhead on every insert. For small temporary data sets it is often unnecessary. With larger intermediate results and repeated joins or filters, however, it can make a decisive difference.

When Should You Use Temporary Tables?

  • Multi-stage processing: When an intermediate result is needed more than once.
  • Batch and reporting jobs: When a job collects, aggregates, and then evaluates data.
  • Complex SQL flows: When a single monster query becomes hard to read or test.
  • Session isolation: When parallel jobs need their own working data without interfering with each other.
  • Ad-hoc analysis: When you want to work with intermediate results briefly in ACS or Navigator.
Myth: “Temporary tables are always faster.”
No. Sometimes a CTE is faster, sometimes a view, sometimes a persistent work table with a clean index. Temporary tables are a tool – not a magic wand.

Practical Example: Monthly Sales Report

Scenario: A company on IBM i produces a monthly sales report by department. The raw data lives in MYLIB.SALES. Instead of running the same aggregation repeatedly, the aggregated result is stored in a temporary table.

SQL · Monthly Report
DECLARE GLOBAL TEMPORARY TABLE SESSION.TEMP_SALES_REPORT (
    DEPTNO CHAR(3),
    TOTAL_SALES DECIMAL(15,2)
)
WITH REPLACE
ON COMMIT PRESERVE ROWS
NOT LOGGED;

INSERT INTO SESSION.TEMP_SALES_REPORT
SELECT DEPTNO, SUM(AMOUNT) AS TOTAL_SALES
FROM MYLIB.SALES
WHERE ORDER_DATE BETWEEN DATE('2026-05-01') AND DATE('2026-05-31')
GROUP BY DEPTNO;

CREATE INDEX SESSION.IDX_TEMP_SALES_DEPT
ON SESSION.TEMP_SALES_REPORT (DEPTNO);

SELECT *
FROM SESSION.TEMP_SALES_REPORT
ORDER BY TOTAL_SALES DESC;

Advantages in This Scenario

  • Readability: Aggregation is cleanly separated from the final output.
  • Reusability: The same intermediate result can feed multiple evaluations.
  • Isolation: Every job works with its own temporary table.
  • Optional tuning: You can add an index on the temporary table when needed.

Disadvantages and Limitations

  • Extra step: The table must be populated. This is not worth doing for every small SELECT.
  • Planning required: Without an index or with the wrong index a DGTT can stay slow.
  • Volatility: Everything disappears when the application context ends. For audit, debugging, or restart you need other mechanisms.
  • Commit behavior: Without ON COMMIT PRESERVE ROWS a commit can delete your rows.

Comparison: DGTT, CTE, View, Persistent Table

The following table is deliberately qualitative. Fixed millisecond numbers without concrete hardware, data volume, PTF level, job configuration, and SQL plan are quickly misleading.

Aspect DGTT / Temporary Table CTE View Persistent Work Table
Lifetime / Scope Session / Job Only within one SQL statement Permanently defined query logic Permanent until manually cleaned up
Best for Intermediate results used multiple times Readable one-time queries Reusable SQL view Large, recurring processes that may need restart
Index possible? Yes – must be created in SESSION or QTEMP Not directly on the CTE itself Not like on a table; optimizer decides Yes, normal persistent index
Recovery / Audit Weak because data is volatile Not applicable No own data Strong when properly journaled
Typical Pitfall Commit deletes rows if PRESERVE ROWS is missing Can become confusing with repeated use Can hide complex plans Cleanup, locks, journaling, authorities

Db2 Is Not the Same on Every Platform: IBM i, LUW, z/OS

Platform matters with temporary tables. Db2 for i, Db2 LUW, and Db2 for z/OS use partly similar terminology but not always the same syntax. On IBM i, DECLARE GLOBAL TEMPORARY TABLE is the central mechanism. Do not blindly copy syntax and behavior from LUW or z/OS articles to IBM i.

Key takeaway: On IBM i the platform-specific details count for temporary tables: SESSION, QTEMP, commit behavior, NOT LOGGED, and real measurements inside the actual job.

Best Practices for IBM i Developers

  • Always qualify DGTTs with SESSION.
  • Use ON COMMIT PRESERVE ROWS when data must survive a commit.
  • Use NOT LOGGED deliberately: less logging overhead, but changes are not rolled back like normal table changes.
  • Use TRUNCATE TABLE when you want to reuse a temporary table that has the same structure.
  • Create indexes only when repeated filters, joins, or sorts measurably benefit from them.
  • Measure with ACS Visual Explain, IBM i Navigator, Plan Cache, or database monitoring.
  • In long procedures or reused connections, explicitly drop temporary tables when you want to release the name, storage, or state.
SQL · Optional Cleanup
DROP TABLE SESSION.TEMP_SALES_REPORT;

Conclusion

Temporary tables in Db2 for i are powerful when used with purpose: for connection- or job-scoped intermediate results, reports, batch processes, and multi-stage SQL logic. The details matter. In SQL you reference the DGTT through SESSION, even though Db2 for i stores it internally in QTEMP. Add deliberate commit/rollback behavior, wise use of NOT LOGGED, optional TRUNCATE TABLE for reuse, and indexes only where they deliver measurable value. The golden rule remains: don’t assume – measure.

Sources

Glossary

Terms and abbreviations from the article – explained concisely.

ACS (Access Client Solutions)
IBM i client tool for administration, SQL console, and system access.
Application Process
In IBM documentation, the context in which a temporary table is declared and used; on IBM i this is practically tied to the job or connection.
AS/400
Historical name of the platform from which IBM i evolved.
CTE (Common Table Expression)
WITH construct that defines intermediate results inside a single SQL statement.
Db2 for i
The integrated relational database on the IBM i platform.
DGTT
Declared Global Temporary Table – a temporary table created with DECLARE GLOBAL TEMPORARY TABLE.
FOR SYSTEM NAME
Option to assign an IBM i system name (max. 10 characters), useful when name length limits or low-level access apply.
IBM i
Operating system and platform on IBM Power Systems, formerly known as iSeries / AS/400.
Index
Data structure that accelerates search, join, and sort operations.
Journaling
Logging of data changes for recovery, audit, and transaction integrity.
NOT LOGGED
DGTT option that reduces logging overhead; changes to the temporary table are not rolled back like normal table changes.
Optimizer
Component that chooses SQL execution plans.
Plan Cache
Cache that stores SQL execution plans.
QTEMP
Job-specific temporary library on IBM i.
RPG / SQLRPGLE
Programming language (or RPG variant with embedded SQL) widely used on IBM i.
SESSION
SQL schema used for declared temporary tables.
TRUNCATE TABLE
SQL statement that quickly empties a table without dropping its structure.
WITH REPLACE
DGTT option that replaces an existing temporary table of the same name in the current session.

Transparency Notice


🔎
Transparency Notice:
The content on tiny-tool.de is carefully researched, editorially reviewed, and updated on a regular basis. Sources and quotations are provided in a transparent and traceable manner. However, we do not guarantee the accuracy, completeness, or timeliness of the information provided. Errors cannot be ruled out.

Authorship & Editorial Support: Texts published on tiny-tool.de are original editorial works created by our team (final editorial responsibility: Guido Zeuner). Digital tools — including AI-based assistance systems — may be used solely as support tools for research, structuring, or language refinement. The selection of content, structure, reasoning, and final wording are created and approved by us as natural persons; AI systems are not authors.

Audience Measurement (VG Wort / METIS): To measure text reach, tracking pixels provided by VG Wort are used. For technical reasons, these tracking marks are loaded when the page is accessed and currently cannot be blocked through the cookie banner, as no cookies are set. The measurement is used exclusively for reach statistics; no personal profiles are created. More information is available in our Privacy Notice.

Please note: The content provided on this website is for general informational purposes only and does not constitute professional advice of any kind, including legal, tax, financial, or other specialist advice. Use of the content is at your own risk. Liability for material or immaterial damages is excluded, except in cases of intentional misconduct or gross negligence.

Advertising & Affiliate Links: Some posts may contain promotional references or so-called affiliate links. These are identified accordingly. Clicking such links does not result in any additional cost to you; we may receive a small commission.

Trademark Notice: All brand names, logos, and product names are the property of their respective rights holders and are used solely for identification and descriptive purposes. No affiliation with the companies mentioned is implied.

External Links: This website contains links to external third-party websites. Although we review links carefully, we assume no responsibility for the content of external websites. If we become aware of unlawful content, we will remove the respective links without undue delay.