ANSI SQL Extensions  «Prev  Next»

Lesson 4Using NVL with Oracle SQL
ObjectiveUse the NVL built-in function with Oracle SQL.

Using NVL with Oracle SQL

Oracle SQL uses NULL to represent a value that is missing, unknown, or not applicable. A null value is not the same as zero, a blank character, or a datatype default. Because nulls affect comparisons, calculations, joins, and report output, SQL developers must handle them deliberately. Oracle's NVL built-in function provides a direct way to replace a null expression with a meaningful alternative.

This lesson explains how SQL's three-valued logic produces an UNKNOWN result, how NVL substitutes display and numeric values, and why null handling is especially important when an outer join preserves rows that have no match in another table.

SQL NULL and three-valued logic

Most programming conditions are described as either true or false. SQL adds a third logical result: UNKNOWN. This three-valued logic is necessary because a condition cannot always be evaluated when one of its operands is NULL.

Consider a row in which salary is null:

salary = 50000

Oracle cannot determine whether the missing salary equals 50,000, so the condition evaluates to UNKNOWN. It does not evaluate to FALSE. This distinction matters because a WHERE clause returns only rows whose condition evaluates to TRUE. Rows that produce either FALSE or UNKNOWN are filtered out.

Use the null predicates IS NULL and IS NOT NULL when testing for missing values:

SELECT employee_name,
       salary
FROM employee
WHERE salary IS NULL;

Do not use = NULL or <> NULL. Those comparisons evaluate to UNKNOWN, so they do not identify null rows.

How UNKNOWN affects logical operators

The result of a compound condition depends on the other operands. For example:

  • TRUE AND UNKNOWN evaluates to UNKNOWN.
  • FALSE AND UNKNOWN evaluates to FALSE, because the complete condition cannot be true.
  • TRUE OR UNKNOWN evaluates to TRUE.
  • FALSE OR UNKNOWN evaluates to UNKNOWN.
  • NOT UNKNOWN remains UNKNOWN.

These rules influence filters, joins, constraints, conditional expressions, and data-quality checks. A null-handling function does not change SQL's logical model; it changes the expression supplied to that model.

Replacing a null expression with NVL

The syntax of NVL is:

NVL(expression, replacement_value)

If expression is null, Oracle returns replacement_value. Otherwise, Oracle returns the original expression. The two arguments must be the same datatype or convertible to compatible datatypes.

A null salary does not mean that an employee earns zero. It may mean that a salary has not yet been assigned. A report can preserve that meaning by returning descriptive text:

SELECT
    ename,
    NVL(
        TO_CHAR(salary, 'FM999G999G990'),
        'NOT YET ASSIGNED'
    ) AS salary
FROM employee;

Example output:

ENAME       SALARY
----------- ----------------
BARRETT     85,000
BURLEEZE    101,000
DAHLSTROM   NOT YET ASSIGNED

TO_CHAR converts the numeric salary to character data before NVL is evaluated. This gives both possible return values compatible character datatypes. The phrase NOT YET ASSIGNED is a presentation value; the underlying salary remains null.

Numeric substitution

For arithmetic and aggregate calculations, replacing null with zero may be appropriate:

SELECT quantity,
       unit_price,
       NVL(discount_amount, 0) AS discount_amount,
       quantity * unit_price - NVL(discount_amount, 0) AS net_amount
FROM order_item;

Without NVL, a null discount_amount would cause the entire subtraction expression to return null. With NVL, the calculation treats a missing discount as zero.

Use zero only when it accurately represents the business meaning. A missing test score, account balance, or measurement may be unknown rather than zero. Substituting zero indiscriminately can produce misleading totals and averages.

NVL and implicit datatype conversion

Oracle may implicitly convert one NVL argument to the datatype of the other. Explicit conversion is safer because it makes the intended datatype visible and avoids conversion errors that depend on session settings.

NVL(TO_CHAR(hire_date, 'YYYY-MM-DD'), 'DATE UNKNOWN')

This expression is clearer than relying on Oracle to convert a date automatically. The same principle applies to numeric formatting and localized date or number representations.

How outer joins introduce null values

An outer join preserves rows from one side of a relationship even when no matching row exists on the other side. Oracle fills the missing side's columns with nulls. This behavior is essential for complete reports, but calculations based on those null-extended columns must be handled carefully.

The data dictionary views used in this lesson illustrate the problem:

  • DBA_DATA_FILES describes permanent database data files.
  • DBA_FREE_SPACE describes free extents in tablespaces.

A data file that contains no free extents can have no matching row in DBA_FREE_SPACE. An inner join would omit that file, making a completely full file disappear from the report. A left outer join preserves the row from DBA_DATA_FILES and returns null values for the unmatched DBA_FREE_SPACE columns.

The historical images use Oracle's legacy outer-join operator (+):

FROM
    sys.dba_free_space a,
    sys.dba_data_files b
WHERE
    a.file_id (+) = b.file_id
AND
    b.status = 'AVAILABLE'

The (+) marker identifies the optional side of the join. Oracle still recognizes this syntax, but ANSI join syntax is clearer and should be used for new SQL:

FROM sys.dba_data_files b
LEFT OUTER JOIN sys.dba_free_space a
    ON a.file_id = b.file_id
WHERE b.status = 'AVAILABLE'

In both forms, DBA_DATA_FILES is the preserved side. Every qualifying data file can therefore appear even when no free extent is found.

Using NVL in a data-file space report

When no DBA_FREE_SPACE row matches a data file, expressions such as SUM(a.bytes) and MAX(a.bytes) return null. The report can convert those results to zero:

NVL(SUM(a.bytes), 0)
NVL(MAX(a.bytes), 0)

This is a valid substitution because the absence of any free extent means that the report should show zero free bytes and a zero-byte largest free extent.

Aggregate row counting requires an additional distinction. With an outer join, COUNT(*) counts the preserved result row even when every column from the optional table is null. To count actual matching free extents, count a non-null column from DBA_FREE_SPACE:

COUNT(a.bytes) AS free_extents

This returns zero for a data file with no matching free-space row.

Image walkthrough: outer joins and NVL

The following legacy images remain useful because they show the progression from an outer join to null substitution. Their SQL has been transcribed into the captions so that the lesson remains searchable and accessible.

Legacy Oracle outer join using the plus operator to retain data files without matching free-space rows
FROM
    SYS.DBA_FREE_SPACE A,
    SYS.DBA_DATA_FILES B
WHERE
    A.FILE_ID (+) = B.FILE_ID
AND
    B.STATUS = 'AVAILABLE'
GROUP BY

1. The legacy (+) operator preserves rows from DBA_DATA_FILES even when a data file has no matching entry in DBA_FREE_SPACE.

The query reads DBA_FREE_SPACE values that are null when a full data file has no matching free-space row
FROM
    SYS.DBA_FREE_SPACE A,
    SYS.DBA_DATA_FILES B
WHERE
    A.FILE_ID (+) = B.FILE_ID
AND
    B.STATUS = 'AVAILABLE'
GROUP BY

2. When no free extent exists, columns from DBA_FREE_SPACE are null. For this report, those missing free-space values should be represented as zero.


NVL expressions replace null values with numeric zero in data-file space calculations
B.TABLESPACE_NAME,
SUBSTR(B.FILE_NAME, 1, 100),
B.FILE_ID,
NVL(COUNT(*), 0),
NVL(ROUND
    ((SUM(B.BYTES) / COUNT(*)) / 1048576), 0),
NVL(ROUND
    ((SUM(A.BYTES)) / 1048576), 0),
NVL(ROUND
    (100 -
        (NVL(100 -
            (SUM(NVL(A.BYTES, 0)) / (SUM(NVL(B.BYTES, 0))
            / COUNT(*))) * 100, 0))), 0),
NVL(ROUND
    (MAX(A.BYTES / 1048576)), 0),
NVL(ROUND
    (100 -

3. The selected expressions use NVL to replace null numeric results with zero. The image shows only a partial excerpt of the original SELECT list.


Sample DBA_DATA_FILES and DBA_FREE_SPACE rows before the outer join
DBA_DATA_FILES DBA_FREE_SPACE
| C1 | C2 | C3 |
|---:|---:|---:|
| 12 | 14 | 10 |
| 13 | 23 | 18 |
| 46 | 27 | 46 |
| 24 | 46 | 27 |
| 13 | 72 | 33 |
| 12 | 18 | 91 |
| C3 | C5 | C6 |
|---:|---:|---:|
| 12 | 14 | 16 |
|  8 |  3 |  7 |
|  — |  — |  — |
|  — |  — |  — |
| 14 | 11 |  3 |
|  — |  — |  — |

4. Some rows represented by DBA_DATA_FILES have no corresponding row in DBA_FREE_SPACE.


The two sample data sets flow into an outer join that can produce null values

5. The outer join preserves the data-file rows. Where no free-space row matches, the result contains null values for the DBA_FREE_SPACE columns.

NVL replaces a null column value with numeric zero
SELECT NVL(column_name, 0)

6. The expression returns zero when column_name is null; otherwise, it returns the column value.

Modern Oracle data-file space query

The following version preserves the purpose of the legacy query while using ANSI join syntax and more direct calculations. It lists every available permanent data file, including a file that has no rows in DBA_FREE_SPACE.


SELECT
    b.tablespace_name,
    SUBSTR(b.file_name, 1, 100) AS file_name,
    b.file_id,
    COUNT(a.bytes) AS free_extents,
    ROUND(b.bytes / 1048576) AS allocated_mbytes,
    ROUND(NVL(SUM(a.bytes), 0) / 1048576) AS free_mbytes,
    ROUND(
        NVL(SUM(a.bytes), 0) /
        NULLIF(b.bytes, 0) * 100,
        2
    ) AS pct_free,
    ROUND(
        NVL(MAX(a.bytes), 0) / 1048576
    ) AS largest_free_extent_mbytes,
    ROUND(
        NVL(MAX(a.bytes), 0) /
        NULLIF(b.bytes, 0) * 100,
        2
    ) AS pct_largest_free_extent,
    SYSDATE AS stats_date,
    ROUND(
        (b.bytes - NVL(SUM(a.bytes), 0)) /
        1048576
    ) AS used_mbytes,
    ROUND(
        (b.bytes - NVL(SUM(a.bytes), 0)) /
        NULLIF(b.bytes, 0) * 100,
        2
    ) AS pct_used,
    NULL AS db_name,
    NULL AS server_name
FROM sys.dba_data_files b
LEFT OUTER JOIN sys.dba_free_space a
    ON a.file_id = b.file_id
WHERE b.status = 'AVAILABLE'
GROUP BY
    b.tablespace_name,
    b.file_name,
    b.file_id,
    b.bytes
ORDER BY
    b.tablespace_name,
    b.file_id;

The query incorporates several defensive SQL techniques:

Oracle Tuning Reference

NVL, COALESCE, and CASE

NVL is concise and familiar in Oracle SQL, but it is not the only null-handling construct. COALESCE returns the first non-null expression from a list:

COALESCE(work_phone, mobile_phone, home_phone, 'NO PHONE')

Use COALESCE when several fallback expressions are required. A searched CASE expression is appropriate when the replacement depends on more than a simple null check:

CASE
    WHEN salary IS NULL THEN 'NOT ASSIGNED'
    WHEN salary < 50000 THEN 'BELOW RANGE'
    ELSE 'ASSIGNED'
END

For a direct two-value substitution in Oracle-specific SQL, NVL remains clear and effective.

Performance and correctness considerations

Null-handling expressions are often inexpensive, but their placement can affect access paths. Applying a function to an indexed column in a predicate can prevent Oracle from using a conventional index entry unless an appropriate function-based index exists.

WHERE NVL(status_code, 'N') = 'A'

Before using this form in a high-volume query, review the execution plan and consider whether an equivalent predicate expresses the requirement more selectively. If the expression is used frequently and is important to query performance, a function-based index may be appropriate after workload testing.

Do not add NVL merely to make every output non-null. First determine what a null means, then choose a replacement that preserves the semantics of the data. This approach prevents a visually convenient report from becoming analytically incorrect.

Key points

After learning how NVL replaces null expressions, the next lesson examines another Oracle conditional translation function: DECODE.


SEMrush Software 3 SEMrush Banner 3