Almost every Db2 application eventually needs a piece of a string: the first three bytes of an account number, a department code buried in a CHAR(8) column, or the last four digits of a phone number. In DB2 for z/OS you have two scalar functions for that job: the classic SUBSTR function and the SQL-standard SUBSTRING function. They look similar. They are not the same. This page teaches both, with the rules that actually matter on z/OS.
SUBSTR has been in Db2 for decades. COBOL programmers reach for it the way they reach for MOVE with a reference modification. Positions are counted in the natural units of the data type: bytes for character and binary strings, double-byte characters for graphic strings. The start argument is strict. If you ask for a start past the length attribute, Db2 rejects the call.
SUBSTRING arrived later to match the SQL standard and Unicode-aware processing. You can say “give me two characters” instead of “give me two bytes.” That difference is invisible for plain EBCDIC names such as SMITH and painful for names such as Jürgen, where ü is more than one byte in UTF-8.
Both functions live in schema SYSIBM. Qualify them only when function path resolution might pick a user-defined function with the same name.
Syntax:
1SUBSTR(string-expression, start [, length])
Arguments:
If the source is a fixed-length string and you omit length, Db2 uses LENGTH(string) − start + 1. If the source is varying-length and you omit length, Db2 uses the greater of zero and that same formula, so a start past the actual length of a short VARCHAR can yield an empty string rather than padding.
The result can be null; a null argument produces a null result. The data type follows the source (character stays character, graphic stays graphic, binary stays binary). Length attribute rules that bite beginners:
That CHAR-versus-VARCHAR distinction matters in COBOL host variables and in predicates. A CHAR(3) result is blank-padded. A VARCHAR(3) result of ABC has actual length 3 with no extra blanks.
12345678SELECT SUBSTR('DATABASE', 1, 4) AS FIRST4, -- 'DATA' SUBSTR('DATABASE', 5) AS REST -- 'BASE' FROM SYSIBM.SYSDUMMY1; SELECT EMPNO, SUBSTR(LASTNAME, 1, 3) AS NAME3 FROM DSN8C10.EMP WHERE WORKDEPT = 'A00';
For a VARCHAR column NAME with value MCKNIGHT (eight characters), SUBSTR(NAME, 10) — start beyond the actual length but within the length attribute — yields an empty string, not an error. That surprises people who expected padding or SQLCODE −171. The start is still legal because the VARCHAR length attribute (the maximum) is large enough.
Mixed EBCDIC data is a special hazard. SUBSTR counts bytes, including shift-out (X'0E') and shift-in (X'0F') control characters. Slicing through the middle of a DBCS pair produces a string that is not well-formed mixed data. Prefer SUBSTRING with CODEUNITS32, or keep mixed columns out of byte-oriented SUBSTR until you know the layout.
SUBSTRING has three type-specific forms:
12345678-- Character SUBSTRING(character-expression, start [, length] [, CODEUNITS16 | CODEUNITS32 | OCTETS]) -- Graphic SUBSTRING(graphic-expression, start [, length] [, CODEUNITS16 | CODEUNITS32]) -- Binary SUBSTRING(binary-expression, start [, length])
A numeric first argument is implicitly cast to VARCHAR. Start and length can also be values that Db2 can assign to INTEGER (including some string forms that cast through DECFLOAT(34)).
Binary SUBSTRING always works in bytes and does not accept unit keywords.
For SUBSTRING, start may be positive, negative, or zero. Length, if specified, must be greater than or equal to 0. If you specify a length larger than what remains in the string, Db2 uses the remaining length instead of failing. If the computed window does not overlap the string at all, you get a zero-length string.
12345-- C1 = 'ABCDEFG' SUBSTRING(C1, -2, 2, OCTETS) -- empty string (window is before the data) SUBSTRING(C1, -2, 4, OCTETS) -- 'A' (one byte overlaps the start) SUBSTRING(C1, -2, OCTETS) -- 'ABCDEFG' SUBSTRING(C1, 0, 1, OCTETS) -- empty string
IBM’s Unicode example with FIRSTNAME = Jürgen (UTF-8) shows why units matter:
1234SUBSTRING(FIRSTNAME, 1, 2, CODEUNITS32) -- 'Jü' SUBSTRING(FIRSTNAME, 1, 2, CODEUNITS16) -- 'Jü' SUBSTRING(FIRSTNAME, 1, 2, OCTETS) -- truncated: 'J' plus a blank SUBSTRING(FIRSTNAME, 8, CODEUNITS16) -- empty string
The result type of SUBSTRING is typically varying-length: VARCHAR from CHAR/VARCHAR, CLOB from CLOB, VARGRAPHIC from GRAPHIC/VARGRAPHIC, VARBINARY from BINARY/VARBINARY, BLOB from BLOB. The length attribute of the result matches the first argument (with extra calculation when CODEUNITS16 or CODEUNITS32 is specified). CCSID of a character or graphic result matches the source.
| Topic | SUBSTR | SUBSTRING |
|---|---|---|
| Start position | Must be 1 through the length attribute | May be positive, negative, or zero |
| String units | Characters or bytes of the string type (no unit keyword) | CODEUNITS16, CODEUNITS32, or OCTETS |
| Out of range | Invalid start/length can raise an error (SQLSTATE 22011) | Often a zero-length string; overly large length is clipped |
| Result type | CHAR when length is a small integer constant; else VARCHAR (or LOB type) | Typically VARCHAR / VARGRAPHIC / VARBINARY (or CLOB family) |
Practical guidance for z/OS shops:
A null source, start, or length makes the result null. An empty string is not null. If you filter WHERE SUBSTR(COL, 1, 1) = 'A', rows where COL is null do not qualify — three-valued logic still applies. If you meant “treat null like blank,” wrap with COALESCE first.
123SELECT EMPNO, LASTNAME FROM DSN8C10.EMP WHERE SUBSTR(COALESCE(LASTNAME, ' '), 1, 1) BETWEEN 'A' AND 'M';
Index matching: a SUBSTR on a column is an expression. Stage 1 index matching on that column often disappears unless you have an index on the expression or a matching generated column. For a prefix search, LIKE 'ABC%' can be a better optimizer friend than SUBSTR(COL,1,3) = 'ABC' on a VARCHAR, depending on statistics and index design. Measure with EXPLAIN; do not guess.
When SUBSTR returns CHAR(n), the COBOL receiving field is typically PIC X(n). When the result is VARCHAR, you need a varying structure (length halfword plus data) from DCLGEN or a VARCHAR host variable. Mixing them causes truncated data or SQLCODE −433 / −302 style problems. Always match the result type, not the source type — SUBSTR of a VARCHAR with a constant length 5 is CHAR(5), not VARCHAR.
1234567* Assume LAST3 is PIC X(3) EXEC SQL SELECT SUBSTR(LASTNAME, 1, 3) INTO :LAST3 FROM DSN8C10.EMP WHERE EMPNO = :EMPNO END-EXEC.
Imagine a row of letter tiles. SUBSTR says “start at tile number 3 and pick 4 tiles.” Tile number 1 is the first tile — nobody starts counting at zero. If you ask for a tile number that is not on the rack, SUBSTR complains. SUBSTRING is a kinder friend: if you point a little to the left of the rack, it just hands you whatever tiles actually exist, or an empty handful. SUBSTRING can also count “letters” instead of “inches of wood,” which matters when one letter is painted with two strokes and takes extra space.
1. In DB2 SUBSTR, what does a start value of 1 mean?
2. What is a key difference between SUBSTR and SUBSTRING in Db2 for z/OS?
3. If any argument to SUBSTR or SUBSTRING is NULL, the result is:
4. Why might SUBSTRING(..., 1, 2, OCTETS) differ from SUBSTRING(..., 1, 2, CODEUNITS32) for the name Jürgen?
5. When does SUBSTR typically return a fixed-length CHAR result?