ANSI SQL Extensions  «Prev  Next»

Lesson 5Using the DECODE function with Oracle SQL
ObjectiveUse the DECODE built-in function in Oracle SQL and PL/SQL SQL statements.

Using the Oracle DECODE Function

Oracle's DECODE function performs compact, equality-based value translation. It compares one expression with a sequence of search values and returns the result associated with the first match. This makes it useful for converting stored codes into readable labels, handling a small set of known values, and labeling subtotal or grand-total rows generated by GROUPING and ROLLUP.

DECODE remains supported in Oracle Database 23ai. It is an Oracle-specific SQL function rather than an ANSI SQL construct. In PL/SQL, it is normally used inside a SQL statement, such as a SELECT ... INTO, cursor query, view definition, or DML statement. For new SQL, CASE is often preferred because it is more portable and supports a wider range of conditions, but DECODE remains important when maintaining established Oracle applications.

DECODE syntax and evaluation

The general syntax is:

DECODE(
    expression,
    search_value_1, result_1,
    search_value_2, result_2,
    ...,
    default_result
)

Oracle compares expression with each search value from left to right. When it finds a match, it returns the corresponding result. If no search value matches, Oracle returns the optional default result. If the default is omitted, the function returns NULL.

The following query translates numeric region codes into names:

SELECT
    emp_name,
    DECODE(
        region_code,
        10, 'EAST',
        20, 'WEST',
        30, 'SOUTH',
        40, 'NORTH',
        'NONE'
    ) AS region_name
FROM employee;

Example output:

EMP_NAME   REGION_NAME
---------- -----------
BARRETT    WEST
BURLEEZE   EAST
DAHLSTROM  NONE

In this example, region_code is the expression being evaluated. Each number is a search value, each region name is its associated result, and 'NONE' is the default result. The default is returned when the region code does not match 10, 20, 30, or 40.

DECODE works best when the mapping is small and stable. When a code list contains many values, changes frequently, or must be shared by several applications, a lookup table is usually easier to maintain and enforce with referential integrity.

Equivalent CASE expression

A simple CASE expression can perform the same translation:

SELECT
    emp_name,
    CASE region_code
        WHEN 10 THEN 'EAST'
        WHEN 20 THEN 'WEST'
        WHEN 30 THEN 'SOUTH'
        WHEN 40 THEN 'NORTH'
        ELSE 'NONE'
    END AS region_name
FROM employee;

The two forms are similar for equality comparisons. CASE, however, also supports searched conditions such as ranges, inequalities, compound predicates, and tests involving multiple columns:

CASE
    WHEN salary IS NULL THEN 'NOT ASSIGNED'
    WHEN salary < 50000 THEN 'BELOW RANGE'
    WHEN salary > 150000 THEN 'ABOVE RANGE'
    ELSE 'WITHIN RANGE'
END

Use DECODE when a compact Oracle-specific equality mapping is easy to read. Use CASE when the logic is more complex, portability matters, or future maintainers are likely to understand the standard syntax more quickly.

Datatype behavior and implicit conversion

The expression and search values must be comparable. Oracle can perform implicit conversions, but mixing character and numeric datatypes can make a query dependent on session settings or cause conversion errors. Consistent datatypes are safer:

DECODE(region_code, 10, 'EAST', 20, 'WEST', 'NONE')

The return values must also be compatible. In the preceding expression, every result is character data. When a numeric or date result must be displayed as text, convert it explicitly with TO_CHAR.

How DECODE handles NULL

DECODE has an Oracle-specific null comparison rule: it treats two null values as equal. This differs from an ordinary SQL comparison, where NULL = NULL evaluates to UNKNOWN.

SELECT
    emp_name,
    DECODE(
        commission_pct,
        NULL, 'NO COMMISSION',
        'COMMISSION ASSIGNED'
    ) AS commission_status
FROM employee;

The equivalent CASE expression is often more explicit:

CASE
    WHEN commission_pct IS NULL THEN 'NO COMMISSION'
    ELSE 'COMMISSION ASSIGNED'
END

This special null behavior is useful when reading legacy code, but it should not be confused with the normal three-valued logic used by SQL predicates.

DECODE with GROUPING and ROLLUP

DECODE becomes especially useful in summary reports when it labels rows generated by ROLLUP. A ROLLUP extends a GROUP BY operation by adding progressively higher summary levels.

GROUP BY ROLLUP(region_name, job_title)

This grouping list can produce:

  1. Detail groups for each region and job title.
  2. A subtotal for each region.
  3. A grand total for all regions and job titles.

Oracle represents a rolled-up grouping column with a null placeholder. The GROUPING function distinguishes that generated null from a null stored in the source data:

GROUPING(region_name)

The function returns 0 when the column participates in the current grouping level and 1 when Oracle generated the null placeholder. DECODE can translate that result into a readable heading:

DECODE(
    GROUPING(region_name),
    1, 'All Regions',
    region_name
)

Modernized ROLLUP query

The following query expresses the intended report consistently and uses an ANSI join. It assumes that employee.region_id references region.region_id.

SELECT
    DECODE(
        GROUPING(r.region_name),
        1, 'All Regions',
        r.region_name
    ) AS region_name,
    DECODE(
        GROUPING(e.job_title),
        1, 'All Jobs',
        e.job_title
    ) AS job_title,
    COUNT(*) AS employee_count,
    ROUND(AVG(e.sal) * 12, 2) AS avg_annual_salary
FROM employee e
JOIN region r
    ON r.region_id = e.region_id
GROUP BY ROLLUP(
    r.region_name,
    e.job_title
)
ORDER BY
    GROUPING(r.region_name),
    r.region_name,
    GROUPING(e.job_title),
    e.job_title;

Rows labeled All Jobs are regional subtotals. The row labeled All Regions and All Jobs is the grand total. AVG is recalculated from the rows that contribute to each grouping level; the grand-total average is not merely an average of the displayed regional averages.

Legacy image walkthrough

The original image remains useful because it visually connects DECODE, GROUPING, ROLLUP, COUNT, and AVG. Its SQL and displayed output are transcribed below.

Oracle DECODE labels GROUPING and ROLLUP summary rows in an employee salary query

SQL displayed in the legacy image:

SELECT
    DECODE(GROUPING(region_name), 1, 'All Regions', rname)
        AS rname,
    DECODE(GROUPING(job_title), 1, 'All Jobs', job_title)
        AS job_title,
    COUNT(*) "#",
    AVG(sal) * 12 "Avgg Sal"
FROM
    Employee e,
    Region r
WHERE
    r.region_name = e.region_name
GROUP BY ROLLUP (rname, job_title);

Data displayed in the legacy image:

| RNAME       | JOB      |  # | Avgg Sal |
|-------------|----------|---:|---------:|
| EAST        | Editor   | 43 |   93,100 |
| WEST        | Editor   | 12 |   63,100 |
| NORTH       | Manager  |  3 |  123,000 |
| WEST        | Manager  |  2 |  231,000 |
| All Regions | All Jobs | 14 |   73,100 |

The image conveys the intended concept, but the legacy SQL is internally inconsistent because it alternates between region_name and rname, uses a comma join, and displays only selected rows from what would normally be a larger ROLLUP result. The modern query above should be used as the implementation reference.

What each function contributes

ROLLUP

ROLLUP generates hierarchical grouping levels from right to left. For:

ROLLUP(region_name, job_title)

Oracle creates these grouping sets:

(region_name, job_title)
(region_name)
()

The empty grouping set represents the grand total.

GROUPING

GROUPING(column) identifies whether a null was generated for a subtotal or grand-total row. It does not itself perform the summarization; ROLLUP creates the grouping levels.

DECODE

DECODE translates the numeric result of GROUPING into labels such as All Jobs and All Regions.

COUNT and AVG

COUNT(*) reports the number of rows contributing to each group. If sal stores a monthly salary, AVG(sal) * 12 annualizes the average. A descriptive alias improves the report:

ROUND(AVG(sal) * 12, 2) AS avg_annual_salary

Frequently used Oracle SQL built-in functions

  • TO_CHAR: Converts datetime or numeric values into formatted character data.
  • UPPER: Converts character data to uppercase.
  • LOWER: Converts character data to lowercase.
  • SUBSTR: Extracts part of a character value.
  • DECODE: Translates one value into another through ordered equality comparisons.
  • COUNT, SUM, AVG, MIN, and MAX: Calculate values across groups of rows.

Functions in predicates and index usage

Applying a function to an indexed column can prevent Oracle from using a conventional index on the unmodified column for that predicate. It does not, however, guarantee a full table scan. The optimizer chooses an access path according to statistics, selectivity, available indexes, transformations, partitioning, and estimated cost.

Consider a query that counts STATSPACK snapshots created in March:

SELECT COUNT(*)
FROM perfstat.stats$snapshot
WHERE TO_CHAR(snap_time, 'MM') = '03';

A conventional index on snap_time may not directly support the transformed expression. A matching function-based index can make that expression indexable:

CREATE INDEX stats_snapshot_month_fbi
    ON perfstat.stats$snapshot (
        TO_CHAR(snap_time, 'MM')
    );

The indexed expression and query expression must match closely enough for Oracle to recognize the access path.

For one month in a known year, a half-open date range is usually clearer and can use a conventional index:

SELECT COUNT(*)
FROM perfstat.stats$snapshot
WHERE snap_time >= DATE '2026-03-01'
  AND snap_time <  DATE '2026-04-01';

This form avoids converting the indexed date column and handles time components correctly.

Date format models

TO_CHAR(date_column, 'MM')
TO_CHAR(date_column, 'MON')
TO_CHAR(date_column, 'FMMonth')
  • 'MM' returns a two-digit month number from 01 through 12.
  • 'MON' returns a language-dependent abbreviated month name.
  • 'FMMonth' returns a full month name without blank padding.

Month names depend on NLS_DATE_LANGUAGE. For filtering, a date range is generally safer than comparing localized character output.

Choosing DECODE, CASE, or a lookup table

TechniqueBest use
DECODEA small set of equality comparisons in Oracle-specific SQL.
CASEPortable SQL, ranges, compound conditions, or logic involving multiple columns.
Lookup tableLarge or changing code lists that require constraints, descriptions, governance, or reuse.

Using DECODE from PL/SQL

Because DECODE is a SQL function, PL/SQL code normally invokes it inside a SQL statement:

DECLARE
    v_region_name VARCHAR2(20);
BEGIN
    SELECT DECODE(
               region_code,
               10, 'EAST',
               20, 'WEST',
               30, 'SOUTH',
               40, 'NORTH',
               'NONE'
           )
    INTO v_region_name
    FROM employee
    WHERE employee_id = 100;

    DBMS_OUTPUT.PUT_LINE(v_region_name);
END;
/

For procedural branching outside a SQL statement, use PL/SQL IF or CASE statements instead.

Key points

The next lesson examines additional Oracle-supplied SQL extensions and how they simplify reporting and data transformation.

Oracle DECODE Exercise

Practice writing an Oracle SQL statement that uses a built-in function:

Oracle DECODE Exercise


SEMrush Software 5 SEMrush Banner 5