Oracle DataBase Lesson 22 – NULL Handling in Oracle | Dataplexa

NULL Handling

To follow along with the examples in this lesson, use the course dataset from Lesson 8.

What NULL Is

NULL means the absence of a value — not zero, not an empty string, not a space. It means the value is unknown, missing, or not applicable. A product with no rating does not have a rating of 0 — it has no rating at all. A customer with no phone number does not have an empty string — the phone number is simply absent. This distinction matters enormously because NULL behaves differently from every other value in comparisons, arithmetic, and aggregation.

NULL is not a value you can compare with = or !=. Any comparison involving NULL returns neither true nor false but a third state: UNKNOWN. Oracle's WHERE clause only keeps rows where the condition is TRUE. Rows where the condition is UNKNOWN are silently excluded — exactly as if the condition were FALSE. This catches developers constantly: WHERE rating = NULL returns zero rows every time, regardless of the data.

  • NULL means unknown or absent — not zero, not empty string, not false
  • NULL = NULL evaluates to UNKNOWN, not TRUE — two unknowns are not considered equal
  • Any arithmetic with NULL returns NULL — 100 + NULL = NULL, NULL * 5 = NULL
  • The only correct way to test for NULL is IS NULL and IS NOT NULL
  • Aggregate functions SUM(), AVG(), MIN(), MAX() silently skip NULLs — COUNT(*) does not skip them, but COUNT(column) does
  • Oracle and SQL Server behave identically for NULL logic — the differences are in the substitution functions
-- IS NULL and IS NOT NULL are the only correct operators for testing NULL.
-- WHERE rating = NULL always returns zero rows -- = NULL evaluates to UNKNOWN not TRUE.
SELECT product_name, rating
FROM   dataplexa_store.products
WHERE  rating = NULL;          -- WRONG: always returns zero rows
SELECT product_name, rating
FROM   dataplexa_store.products
WHERE  rating IS NULL;         -- CORRECT: returns rows where rating has no value
SELECT product_name, rating
FROM   dataplexa_store.products
WHERE  rating IS NOT NULL
ORDER  BY rating DESC;
-- WHERE rating = NULL (WRONG)
(0 rows selected) -- always empty regardless of data

-- WHERE rating IS NULL (CORRECT)
PRODUCT_NAME | RATING
------------------|-------
Standing Desk | NULL
USB-C Hub | NULL
(2 rows selected)

-- WHERE rating IS NOT NULL
PRODUCT_NAME | RATING
---------------------|-------
Monitor 27-inch | 4.8
Mechanical Keyboard | 4.7
Wireless Mouse | 4.5
Notebook A5 | 4.2
Ballpoint Pens | 4.1
(5 rows selected)
  • WHERE rating = NULL returns zero rows every time — this is one of the most common SQL mistakes across all databases
  • IS NULL and IS NOT NULL are the only operators that correctly detect missing values
  • Oracle says rows selected — SQL Server says rows affected

NULL in Arithmetic and String Concatenation

Any arithmetic involving NULL produces NULL. price * NULL, stock + NULL, 100 / NULL — every one returns NULL silently. Oracle's string concatenation operator is ||. Unlike SQL Server where concatenating with NULL produces NULL by default, Oracle treats NULL as an empty string in || concatenation — so 'Hello' || NULL returns 'Hello', not NULL. This is one of the few places where Oracle and SQL Server behave differently with NULL.

SELECT
    product_name,
    rating,
    price,
    price * rating                        AS raw_score,     -- NULL if rating is NULL
    price * NVL(rating, 0)               AS safe_score,    -- NVL replaces NULL with 0 before multiplying
                                                            -- NVL is Oracle's equivalent of SQL Server's ISNULL
    NVL(TO_CHAR(rating), 'No rating')    AS display_rating, -- TO_CHAR converts number to string first
                                                             -- NVL then provides the fallback text
    'Product: ' || product_name          AS label,          -- || is Oracle string concatenation
    'Rating: ' || TO_CHAR(rating)        AS rating_label    -- NULL rating produces 'Rating: ' in Oracle
                                                             -- SQL Server + operator would return NULL instead
FROM   dataplexa_store.products
ORDER  BY rating DESC NULLS LAST;   -- NULLS LAST pushes NULL ratings to the bottom
                                    -- Oracle default for ASC is NULLS LAST
                                    -- Oracle default for DESC is NULLS FIRST -- override with NULLS LAST
PRODUCT_NAME | RATING | PRICE | RAW_SCORE | SAFE_SCORE | DISPLAY_RATING | LABEL
---------------------|--------|--------|-----------|------------|----------------|----------------------
Monitor 27-inch | 4.8 | 299.99 | 1439.95 | 1439.95 | 4.8 | Product: Monitor 27-inch
Mechanical Keyboard | 4.7 | 89.99 | 422.95 | 422.95 | 4.7 | Product: Mechanical Keyboard
Wireless Mouse | 4.5 | 29.99 | 134.96 | 134.96 | 4.5 | Product: Wireless Mouse
Notebook A5 | 4.2 | 12.50 | 52.50 | 52.50 | 4.2 | Product: Notebook A5
Ballpoint Pens | 4.1 | 8.99 | 36.86 | 36.86 | 4.1 | Product: Ballpoint Pens
Standing Desk | NULL | 349.99 | NULL | 0.00 | No rating | Product: Standing Desk
USB-C Hub | NULL | 24.99 | NULL | 0.00 | No rating | Product: USB-C Hub
(7 rows selected)
  • price * rating returns NULL for the two unrated products — NULL in any arithmetic poisons the entire expression
  • NVL(rating, 0) substitutes 0 before the multiplication — Oracle equivalent of SQL Server's ISNULL(rating, 0)
  • 'Rating: ' || TO_CHAR(rating) returns 'Rating: ' when rating is NULL — Oracle treats NULL as empty string in || concatenation, unlike SQL Server's + operator which returns NULL
  • NULLS LAST pushes NULL values to the bottom of a DESC sort — without it, Oracle's DESC default puts NULLs first

NULL in Aggregates — The Silent Skip

All aggregate functions except COUNT(*) silently skip NULL values. AVG(rating) divides by the count of non-NULL rows, not the total row count — so the average can appear higher than expected. COUNT(*) counts every row including those with NULL in any column. COUNT(column) counts only rows where that column is not NULL. Understanding this difference prevents silent errors in reports.

SELECT
    COUNT(*)                      AS total_rows,        -- all rows -- NULL values in columns are irrelevant
    COUNT(rating)                 AS rows_with_rating,  -- only rows where rating is not NULL
    COUNT(*) - COUNT(rating)      AS missing_ratings,   -- clean pattern for counting NULL occurrences
    SUM(rating)                   AS sum_rating,        -- adds only the non-NULL rating values
    AVG(rating)                   AS avg_rating,        -- divides by COUNT(rating) not COUNT(*) -- skips NULLs
    SUM(NVL(rating, 0)) / COUNT(*) AS avg_with_zero,   -- manual average treating NULL as 0 -- lower result
    MIN(rating)                   AS min_rating,
    MAX(rating)                   AS max_rating
FROM   dataplexa_store.products;
TOTAL_ROWS | ROWS_WITH_RATING | MISSING_RATINGS | SUM_RATING | AVG_RATING | AVG_WITH_ZERO | MIN_RATING | MAX_RATING
-----------|------------------|-----------------|------------|------------|---------------|------------|------------
7 | 5 | 2 | 22.30 | 4.46 | 3.19 | 4.1 | 4.8
(1 row selected)
  • COUNT(*) returns 7 — all rows — while COUNT(rating) returns 5 — only rows where rating is not NULL
  • AVG(rating) divides 22.30 by 5, giving 4.46 — the two NULL rows are excluded from both the sum and the divisor
  • The manual average treating NULL as 0 gives 3.19 — very different — choose the behaviour that is correct for your report
  • COUNT(*) - COUNT(rating) is a clean pattern for counting how many rows have a missing value in any column

NVL, NVL2, COALESCE, and NULLIF

Oracle provides four functions for working with NULL. NVL(expression, replacement) is Oracle's two-argument NULL substitution function — the direct equivalent of SQL Server's ISNULL(). NVL2(expression, if_not_null, if_null) is Oracle-only — it returns one value when the expression is not NULL and a different value when it is. COALESCE(val1, val2, ...) is the ANSI-standard function supported by both Oracle and SQL Server — it accepts any number of arguments and returns the first non-NULL. NULLIF(a, b) returns NULL when two values are equal and the first value otherwise — used most often to prevent divide-by-zero.

-- NVL: Oracle equivalent of SQL Server's ISNULL -- two arguments only
SELECT
    product_name,
    NVL(brand, 'Unbranded')      AS brand,    -- 'Unbranded' when brand is NULL
    NVL(rating, 0.0)             AS rating    -- 0.0 when rating is NULL
FROM   dataplexa_store.products;
-- NVL2: Oracle-only -- no SQL Server equivalent
-- Returns second argument when expression is NOT NULL, third when it IS NULL
SELECT
    product_name,
    NVL2(rating, 'Rated', 'Unrated')  AS rating_status  -- 'Rated' if rating exists, 'Unrated' if NULL
FROM   dataplexa_store.products;
-- COALESCE: ANSI standard -- works in both Oracle and SQL Server
-- Returns the first non-NULL value from the list
SELECT
    first_name,
    last_name,
    COALESCE(phone_number, email, 'No contact info')  AS contact_method
FROM   dataplexa_store.customers;
-- NULLIF: returns NULL when the two arguments are equal -- prevents divide-by-zero
-- Oracle raises ORA-01476 on divide-by-zero -- NULLIF converts 0 to NULL so division returns NULL instead
SELECT
    category,
    SUM(total_amount)                          AS revenue,
    COUNT(*)                                   AS orders,
    SUM(total_amount) / NULLIF(COUNT(*), 0)    AS avg_order_value  -- safe -- returns NULL if orders = 0
FROM   dataplexa_store.orders o
JOIN   dataplexa_store.order_items oi ON o.order_id = oi.order_id
GROUP  BY category;
-- NVL result
PRODUCT_NAME | BRAND | RATING
---------------------|------------|-------
Wireless Mouse | LogiTech | 4.5
Mechanical Keyboard | KeyMaster | 4.7
Notebook A5 | PaperCo | 4.2
Standing Desk | DeskPro | 0.0
USB-C Hub | TechLink | 0.0
Ballpoint Pens | PaperCo | 4.1
Monitor 27-inch | ViewMax | 4.8
(7 rows selected)

-- NVL2 result
PRODUCT_NAME | RATING_STATUS
---------------------|---------------
Monitor 27-inch | Rated
Mechanical Keyboard | Rated
Standing Desk | Unrated
USB-C Hub | Unrated
(7 rows selected)

-- NULLIF safe division
CATEGORY | REVENUE | ORDERS | AVG_ORDER_VALUE
------------|-----------|--------|----------------
Electronics | 161030.90 | 2214 | 72.74
Stationery | 45841.50 | 3108 | 14.75
Furniture | 65425.00 | 369 | 177.30
(3 rows selected)
  • NVL(rating, 0.0) is Oracle's two-argument equivalent of SQL Server's ISNULL(rating, 0.0)
  • NVL2(rating, 'Rated', 'Unrated') has no SQL Server equivalent — it branches on whether the first argument is NULL or not
  • COALESCE(phone_number, email, 'No contact info') tries each argument left to right and returns the first non-NULL — identical behaviour in Oracle and SQL Server
  • NULLIF(COUNT(*), 0) returns NULL when the count is zero — dividing by NULL returns NULL instead of raising ORA-01476

NULL in JOINs and WHERE with AND / OR

NULL values in JOIN columns never match anything — including other NULLs. A row with NULL in the join column will silently disappear from an INNER JOIN. A LEFT JOIN preserves it but returns NULL for every column from the right table. In WHERE conditions, NULL's three-valued logic produces surprising results: TRUE AND UNKNOWN = UNKNOWN, FALSE OR UNKNOWN = UNKNOWN — so a condition involving a NULL column can silently exclude rows you expected to include.

-- Products with NULL category_id disappear silently from an INNER JOIN.
-- LEFT JOIN preserves them -- NVL then provides a readable fallback.
SELECT
    p.product_name,
    p.price,
    NVL(c.category_name, 'Uncategorised')  AS category   -- NVL replaces NULL from unmatched LEFT JOIN rows
FROM   dataplexa_store.products    p
LEFT JOIN dataplexa_store.categories c ON p.category_id = c.category_id;
-- WITHOUT the OR IS NULL check, unrated products are silently excluded
SELECT product_name, rating
FROM   dataplexa_store.products
WHERE  rating < 4.5;               -- NULL rating evaluates to UNKNOWN -- row silently excluded
-- CORRECT: explicitly include NULL rows
SELECT product_name, rating
FROM   dataplexa_store.products
WHERE  rating < 4.5
   OR  rating IS NULL;             -- now unrated products are included
-- LEFT JOIN with NVL
PRODUCT_NAME | PRICE | CATEGORY
---------------------|--------|--------------------
Wireless Mouse | 29.99 | Electronics
Mechanical Keyboard | 89.99 | Electronics
Notebook A5 | 12.50 | Stationery
Standing Desk | 349.99 | Uncategorised
USB-C Hub | 24.99 | Uncategorised
Ballpoint Pens | 8.99 | Stationery
Monitor 27-inch | 299.99 | Electronics
(7 rows selected)

-- WHERE rating < 4.5 only
PRODUCT_NAME | RATING
----------------|-------
Notebook A5 | 4.2
Ballpoint Pens | 4.1
(2 rows selected) -- NULL ratings silently excluded

-- WHERE rating < 4.5 OR rating IS NULL
PRODUCT_NAME | RATING
----------------|-------
Notebook A5 | 4.2
Ballpoint Pens | 4.1
Standing Desk | NULL
USB-C Hub | NULL
(4 rows selected)
  • INNER JOIN silently drops Standing Desk and USB-C Hub — their NULL category_id matches nothing in the categories table
  • LEFT JOIN preserves them — NVL(c.category_name, 'Uncategorised') then replaces the NULL with a readable label
  • Without OR rating IS NULL, the two unrated products are silently excluded from the WHERE result — no error, just missing data
  • NOT IN (subquery) returns zero rows if the subquery contains even one NULL — use NOT EXISTS as the safe alternative in Oracle exactly as in SQL Server

Lesson Summary

Concept What It Means in Oracle
NULL Unknown or absent — not zero, not empty string
IS NULL / IS NOT NULL The only correct operators to test for NULL — = NULL always returns UNKNOWN
NVL(expr, val) Oracle's two-argument NULL substitute — equivalent of SQL Server's ISNULL()
NVL2(expr, a, b) Oracle-only — returns a when expr is not NULL, b when it is NULL
COALESCE(v1, v2, ...) ANSI standard — first non-NULL from the list — identical in Oracle and SQL Server
NULLIF(a, b) Returns NULL when a equals b — prevents divide-by-zero (ORA-01476)
NULL in concatenation Oracle || treats NULL as empty string — unlike SQL Server + which propagates NULL
NULLS LAST / FIRST Oracle lets you control where NULLs sort — Oracle and SQL Server defaults differ

Practice Questions

Practice 1. What is the Oracle equivalent of SQL Server's ISNULL(brand, 'Unbranded')?



Practice 2. What does SELECT 100 + NULL FROM DUAL return in Oracle?



Practice 3. A products table has 10 rows, 3 of which have NULL in the rating column. What does COUNT(*) return? What does COUNT(rating) return?



Practice 4. What does NVL2(salary, 'Has salary', 'No salary') return when salary is NULL?



Practice 5. What Oracle error is raised by a divide-by-zero operation, and how does NULLIF prevent it?



Quiz

Quiz 1. You write WHERE phone_number = NULL to find customers with no phone. What happens in Oracle?






Quiz 2. A table has 8 rows. 3 rows have NULL in the discount column. What does AVG(discount) divide by?






Quiz 3. What does 'Order: ' || NULL return in Oracle?






Quiz 4. Which Oracle function has no equivalent in SQL Server and branches on whether a value is NULL or not NULL?






Quiz 5. You ORDER BY salary DESC. Where do NULL salaries appear in Oracle by default?






Next up — Table Relationships — How primary keys, foreign keys, and cardinality define the connections between tables and enforce data integrity in Oracle.