Two of the most expensive hidden steps in a DB2 access path are a sort and a materialization. A sort puts rows (or row identifiers) into an order Db2 needs. Materialization copies an intermediate result into a work file so the next step can read it like a temporary table. Beginners often blame “the join” or “the index” when EXPLAIN is really showing METHOD = 3, SORTC_ORDERBY = Y, or TABLE_TYPE = W.
A sort is not the same thing as the SQL ORDER BY clause. ORDER BY is a request for a sequence in the result. A sort is the runtime work Db2 may do to produce that sequence—or to support grouping, uniqueness, a merge scan join, or a RID list for list prefetch. The only common SQL form that always needs uniqueness processing equivalent to a sort is UNION (not UNION ALL), because duplicates must be removed.
For ORDER BY, GROUP BY, DISTINCT, and many joins, the optimizer may choose a sort. It may also use an index that already returns keys in the needed order, or (for grouping) other techniques. The only reliable check is EXPLAIN.
1234SELECT WORKDEPT, LASTNAME, SALARY FROM HR.EMPLOYEE WHERE WORKDEPT IN ('A00', 'D11') ORDER BY LASTNAME;
If an index on LASTNAME (perhaps with WORKDEPT as a matching leading column, depending on the predicates and index design) can deliver rows in LASTNAME order, SORTC_ORDERBY can be N. If Db2 must gather qualifying rows first and then order them, you will see a sort indicator of Y and often a METHOD = 3 row.
On z/OS, PLAN_TABLE carries eight yes/no sort flags. Names that start with SORTN_ apply to the new table in that step (the table being added). Names that start with SORTC_ apply to the composite table (the result accumulated so far).
| Column | Applies to | Meaning when Y |
|---|---|---|
| SORTN_UNIQ | New table | Sort to remove duplicates (DISTINCT / uniqueness) |
| SORTN_JOIN | New table | Sort for merge scan or hybrid join (hybrid: often the RID list) |
| SORTN_ORDERBY | New table | Sort the new table for ORDER BY |
| SORTN_GROUPBY | New table | Sort the new table for GROUP BY |
| SORTC_UNIQ | Composite | Sort the accumulated result to remove duplicates |
| SORTC_JOIN | Composite | Sort the composite for nested loop, merge scan, or hybrid join |
| SORTC_ORDERBY | Composite | Sort the composite for ORDER BY or a quantified predicate |
| SORTC_GROUPBY | Composite | Sort the composite for GROUP BY |
METHOD = 3 is a dedicated sort step. That row does not access a new table; CREATOR is blank. METHOD = 2 (merge scan) or METHOD = 4 (hybrid join) can show SORTN_JOIN / SORTC_JOIN on the same row as the inner table access. A RID sort for ordinary list prefetch is often not spelled out as METHOD = 3; hybrid join is the case where SORTN_JOIN commonly means “sort the RID list,” and a highly clustered inner index can make SORTN_JOIN = N.
When you create the extra EXPLAIN tables, DSN_SORT_TABLE and DSN_SORTKEY_TABLE describe sort keys and sort type in more detail than the eight flags.
| SQL | Why a sort helps | How you might avoid it |
|---|---|---|
| ORDER BY | Return rows in a requested sequence | Clustering / matching index already in that order |
| GROUP BY | Bring equal grouping keys together | Index order matching the group keys (hash grouping may also apply) |
| SELECT DISTINCT | Eliminate duplicate rows | Unique index covering the selected columns |
| UNION (not ALL) | Combine sets and remove duplicates | Cannot skip; UNION ALL if duplicates are acceptable |
| Merge scan join | Both inputs ordered on join columns | Indexes that already provide join-column order |
| Hybrid join / list prefetch | RIDs in page order for sequential I/O | Highly clustered inner index (hybrid may skip the RID sort) |
ORDER BY asks for a sequence. If the chosen access path already produces that sequence (clustering index, matching index scan in the right direction), Db2 can skip the sort. DESC vs ASC, extra columns in the ORDER BY, and joins that scramble order are common reasons a “perfect-looking” index still does not avoid SORTC_ORDERBY.
GROUP BY needs equal keys together (or another grouping method). DISTINCT is uniqueness on the selected columns—SORTN_UNIQ or SORTC_UNIQ. A unique index that already guarantees uniqueness can make a DISTINCT cheap. SELECT DISTINCT * from a table with no unique constraint is a classic accidental sort.
UNION concatenates two result sets and removes duplicates, so a sort (or equivalent uniqueness step) is required. UNION ALL keeps duplicates and does not need that uniqueness sort. If the application does not care about duplicates, UNION ALL is the first rewrite to try.
A merge scan join (METHOD = 2) needs both inputs ordered on the join columns. If an index does not provide that order, Db2 sorts the new table, the composite, or both. A hybrid join (METHOD = 4) typically builds a RID list on the inner table and sorts those RIDs into page order so list prefetch can read data sequentially. Nested loop join (METHOD = 1) does not need join-column order, but SORTC_JOIN can still appear in some nested-loop cases documented on PLAN_TABLE.
123456-- Merge scan join often sorts unless indexes provide join order SELECT E.EMPNO, D.DEPTNAME FROM HR.EMPLOYEE E INNER JOIN HR.DEPARTMENT D ON E.WORKDEPT = D.DEPTNO WHERE E.SALARY > 50000;
Db2 tries to sort in memory using the sort pool. When the ordered runs do not fit, it writes logical work files into table spaces in the work file database (DSNDB07 in a non-data-sharing subsystem). Each data sharing member has its own work file database.
IBM documents the sort in two phases. During the input phase, ordered sets of rows are written to work files. After all rows are inserted, Db2 merges those work files if needed into a single sorted result. Buffer pools back the logical work files; the number of sort work files is limited by buffer pool size, not by a tiny hard-coded file count from ancient versions.
Allocation prefers certain work file table spaces (for example Db2-managed segmented non-UTS spaces with SECQTY 0). If preferred spaces are unavailable, subsystem parameter WFDBSEP decides whether the sort fails (YES) or Db2 picks another space (NO). Record length also matters: IBM prefers 32 KB pages when data + key + prefix is greater than 100 bytes, and 4 KB pages when the record is 100 bytes or smaller.
Undersized, fragmented, or badly pooled work files show up as sort failures, SQLCODE -904 style resource problems, or queries that burn elapsed time in work file getpages. Temporary tables compete for the same database. Capacity planning for DSNDB07 is part of sort tuning, not an afterthought.
Materialization means: evaluate this piece of the query into a temporary result, park it in a work file, then read that work file in a later step. Merge (the opposite) means: fold the view, nested table expression, or CTE into the outer query so Db2 never builds that extra result.
IBM states that merge is generally more efficient than materialization because predicates from the outer query can be applied earlier (predicate pushdown) and extra copies are avoided. Materialization is required when the inner query block must be fully evaluated first—classic triggers are GROUP BY, DISTINCT, aggregate functions, some UNION operations, and some outer join combinations with views or nested table expressions.
1234567891011CREATE VIEW HR.DEPT_AVG (WORKDEPT, AVGSAL) AS SELECT WORKDEPT, AVG(SALARY) FROM HR.EMPLOYEE GROUP BY WORKDEPT; -- GROUP BY inside the view typically forces materialization SELECT E.EMPNO, E.SALARY, V.AVGSAL FROM HR.EMPLOYEE E INNER JOIN HR.DEPT_AVG V ON E.WORKDEPT = V.WORKDEPT WHERE E.SALARY > V.AVGSAL;
EXPLAIN clues for materialization:
Outer joins combined with other joins, views, or nested table expressions sometimes force materialization of a composite even when you did not write GROUP BY. IBM documents that case specifically: TABLE_TYPE W and TNAME DSNWFQB(xx).
If the view or nested table expression is a simple SELECT without aggregation, DISTINCT, or grouping, Db2 typically merges it: column expressions are substituted into the outer statement. That is good when the outer WHERE can then become a matching index predicate on the base table. It can be less good when a costly expression is referenced many times after merge (the expression may be evaluated once per reference). In rare nested cases people force materialization on purpose; do not do that as a first habit.
A common table expression can be merged, materialized, or (if recursive) iterated. Recursion always implies a working result. A CTE referenced twice may be materialized so Db2 does not run the inner SELECT twice—or it may be merged twice. EXPLAIN, not guesswork, tells you which happened.
After materialization, Db2 may build a sparse index (in-memory work file index) so later nested-loop probes do not scan the whole work file. PLAN_TABLE ACCESSTYPE T is the usual flag. Sparse indexes help when a small materialized inner is probed many times. They still cost memory and a build step; they are not a substitute for a real index on a base table.
Materialization is not always a failure. IBM notes that with deep nesting and repeated references to generated columns, materialize-once can beat merge-and-recompute. Your job is to see the work file in EXPLAIN and decide whether the copy is justified.
Imagine you have a big box of toy cars. A sort is lining them up by color before you hand them out. If they were already stored in color order on the shelf (an index), you skip the lining-up. Materialization is dumping a mixed pile onto a side table, finishing that pile, then using the side table for the next game. The side table is a work file. If the pile is huge, it spills off the desk onto the floor (DSNDB07). UNION is “put two piles together and throw away extra copies of the same car.” UNION ALL is “just dump both piles together.”
1. Which PLAN_TABLE METHOD value means a sort that does not access a new table?
2. Where do sort work files live in a non-data-sharing Db2 subsystem?
3. Which SQL construct always requires a sort?
4. If a view contains GROUP BY, Db2 typically:
5. SORTC_ORDERBY = Y on a PLAN_TABLE row means: