Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

SQL (PostgreSQL, MySQL, SQLite)  Module 21 – SQL Server and Oracle side by side

NULLs and empty strings: Oracle versus SQL Server

Oracle stores an empty string as NULL, SQL Server keeps it as a value. See how that changes filters, counts, UNIQUE keys and data moved between them.

  • Intermediate
  • 25 minutes
  • Examples run with sql.js 1.14.2
  • By MySmartCoPilot
Free core, Pro depth: 9 of 25 minutes free
Free: One INSERT, two different rows · The same table under both rules · Sorting, CHAR columns and the rules both engines share · 3 more sections. With a pass: 2 in-depth parts and 3 interview answers, about 16 minutes.

What you will learn

  • Predict how Oracle's rule that '' is NULL changes inserts, filters, counts and concatenation
  • Compare UNIQUE constraints and indexes that contain NULLs in SQL Server, Oracle and PostgreSQL
  • Use NVL, NVL2, DECODE, ISNULL, COALESCE and NULLIF without type or evaluation surprises
  • Plan how empty strings and NULLs move between engines during a migration

Before you start

What you’ll learn · Before you start

What you will learn

  • Predict how Oracle's rule that '' is NULL changes inserts, filters, counts and concatenation
  • Compare UNIQUE constraints and indexes that contain NULLs in SQL Server, Oracle and PostgreSQL
  • Use NVL, NVL2, DECODE, ISNULL, COALESCE and NULLIF without type or evaluation surprises
  • Plan how empty strings and NULLs move between engines during a migration

Before you start

On this page

Oracle treats a character value of length zero as NULL. Insert '' into a VARCHAR2 column and the row holds NULL: WHERE nick = '' never matches it, WHERE nick IS NULL finds it, and COUNT(nick) skips it. SQL Server keeps an empty string as an ordinary value of length 0 that is not NULL, so '' = '' is true there and COUNT(nick) counts the row. Inside one engine both rules are consistent. The trouble starts when data or code crosses between them: a NOT NULL column full of empty strings in SQL Server cannot be loaded into Oracle, counts and filters change their answers, and text built by concatenation comes out differently. Oracle’s own reference calls the rule current behaviour that may change in a future release, and advises against code that treats empty strings and NULLs as the same thing.

One INSERT, two different rows

One INSERT of an empty string: SQL Server stores a value of length 0, Oracle stores NULL, and three tests answer differently.INSERT INTO customers (id, nick)VALUES (2, '')SQL Server stores ''a value of length 0, not NULLOracle stores NULLa zero-length string is NULLnick = '' → truenick IS NULL → falseCOUNT(nick) counts the rownick = '' → unknown, no matchnick IS NULL → trueCOUNT(nick) skips the row

The same INSERT of '' in SQL Server and in Oracle

Text description of the diagram

The diagram follows one statement, INSERT INTO customers (id, nick) VALUES (2, ''), into two engines.

  • In SQL Server the empty string is stored as it is: a value of length 0 that is not NULL. Afterwards nick = '' is true for the row, nick IS NULL is false, and COUNT(nick) counts it.
  • In Oracle a zero-length string is NULL, so the row holds NULL. Afterwards nick = '' is unknown and matches nothing, nick IS NULL is true, and COUNT(nick) skips the row.

Everything else in this lesson follows from that one difference. SQL Server stores what it was given, and its NULLs and empty strings stay apart. Oracle has only NULLs for a missing text value: an empty string coming in from a form, a CSV file or another database becomes NULL on the way in. A single space is not empty, so ' ' stays a real value in both engines.

The same table under both rules

SQLite, the engine that runs this track’s examples in your browser, keeps empty strings the way SQL Server does. NULLIF(nick, '') returns NULL where nick is '', which is exactly what Oracle would have stored, so one table can be read both ways side by side:

Four questions about five nicknames, under each engine's rule SQL · empty_vs_null.sql
-- Five customers of a shop. Ravi and Zoya left the nickname box empty; Meera's was never asked for.
-- SQLite keeps '' as a value, as SQL Server does. NULLIF(nick, '') turns each '' into NULL, which is
-- what Oracle stores for a zero-length string: a model of Oracle's documented rule, run in SQLite.
CREATE TABLE customers (
  id   INTEGER PRIMARY KEY,
  name TEXT NOT NULL,
  nick TEXT
);
INSERT INTO customers (id, name, nick) VALUES
  (1, 'Asha',  'Ash'),
  (2, 'Ravi',  ''),
  (3, 'Meera', NULL),
  (4, 'Kabir', 'Kabs'),
  (5, 'Zoya',  '');

-- The same four questions, asked of the data as SQL Server keeps it and as Oracle would keep it.
WITH kept AS (
  SELECT id, nick AS sql_server, NULLIF(nick, '') AS oracle FROM customers
)
SELECT 'WHERE nick = ''''' AS question,
       (SELECT count(*) FROM kept WHERE sql_server = '') AS sql_server,
       (SELECT count(*) FROM kept WHERE oracle = '')     AS oracle
UNION ALL
SELECT 'WHERE nick IS NULL',
       (SELECT count(*) FROM kept WHERE sql_server IS NULL),
       (SELECT count(*) FROM kept WHERE oracle IS NULL)
UNION ALL
SELECT 'COUNT(nick)', count(sql_server), count(oracle) FROM kept
UNION ALL
SELECT 'COUNT(DISTINCT nick)', count(DISTINCT sql_server), count(DISTINCT oracle) FROM kept;

-- Row by row: what is stored, its length, and a label joined from two columns.
-- In SQLite, || with a NULL gives NULL, like T-SQL's + with its default settings.
-- Oracle's || skips a NULL operand, modelled here with coalesce(..., '').
SELECT id,
       quote(nick)                                      AS stored,
       length(nick)                                     AS len,
       quote(NULLIF(nick, ''))                          AS oracle_stores,
       length(NULLIF(nick, ''))                         AS oracle_len,
       name || ' / ' || nick                            AS label_plus,
       name || ' / ' || coalesce(NULLIF(nick, ''), '')  AS label_oracle
FROM customers
ORDER BY id;

Output

┌──────────────────────┬────────────┬────────┐
│       question       │ sql_server │ oracle │
├──────────────────────┼────────────┼────────┤
│ WHERE nick = ''      │ 2          │ 0      │
│ WHERE nick IS NULL   │ 1          │ 3      │
│ COUNT(nick)          │ 4          │ 2      │
│ COUNT(DISTINCT nick) │ 3          │ 2      │
└──────────────────────┴────────────┴────────┘
┌────┬────────┬─────┬───────────────┬────────────┬──────────────┬──────────────┐
│ id │ stored │ len │ oracle_stores │ oracle_len │  label_plus  │ label_oracle │
├────┼────────┼─────┼───────────────┼────────────┼──────────────┼──────────────┤
│ 1  │ 'Ash'  │ 3   │ 'Ash'         │ 3          │ Asha / Ash   │ Asha / Ash   │
│ 2  │ ''     │ 0   │ NULL          │            │ Ravi /       │ Ravi /       │
│ 3  │ NULL   │     │ NULL          │            │              │ Meera /      │
│ 4  │ 'Kabs' │ 4   │ 'Kabs'        │ 4          │ Kabir / Kabs │ Kabir / Kabs │
│ 5  │ ''     │ 0   │ NULL          │            │ Zoya /       │ Zoya /       │
└────┴────────┴─────┴───────────────┴────────────┴──────────────┴──────────────┘

Recorded with sql.js 1.14.2 (SQLite 3.49.1) on macOS 26 arm64. To run it yourself: sqlite3 -box :memory: < empty_vs_null.sql

Three of the four answers change. Two rows match = '' under SQL Server’s rule and none under Oracle’s, because nothing in Oracle equals an empty string: comparing anything with NULL is unknown. IS NULL finds one row in SQL Server and three in Oracle. COUNT(nick) falls from 4 to 2, and COUNT(DISTINCT nick) from 3 to 2. Oracle’s LENGTH('') is NULL for the same reason, while SQL Server’s LEN('') is 0.

The labels in the second result show the other half of the story. In SQLite, || with a NULL operand gives NULL, and so does T-SQL’s + with its standard setting CONCAT_NULL_YIELDS_NULL ON, which SQL Server 2017 and later always apply. SQL Server 2025’s new || operator returns NULL as well. Oracle’s || skips a NULL operand instead, so Meera’s label survives there and vanishes in SQL Server. T-SQL’s CONCAT function treats NULL arguments as empty strings, the closest match to Oracle’s behaviour.

Sorting, CHAR columns and the rules both engines share

Sort order differs too. SQL Server treats NULL as the lowest value, so NULLs come first in ascending order. Oracle puts NULLs last in ascending order and first in descending order, and its former empty strings sort with them. Write NULLS FIRST or NULLS LAST in Oracle when the position matters; SQL Server has no such clause, so sort on CASE WHEN nick IS NULL THEN 1 ELSE 0 END first.

Fixed-length columns follow the same rule. Oracle blank-pads a CHAR value to its declared length, so ' ' in a CHAR(3) column becomes three spaces, but '' becomes NULL before any padding happens.

Two rules are the same in both engines. NOT IN against a list that contains a NULL returns no rows, and = NULL matches nothing (SQL Server’s old ANSI_NULLS OFF mode, which changed that, is deprecated).

The functions that handle NULLs

Each engine has its own functions for replacing a NULL or comparing two values that may be NULL:

NULL functions in SQL Server and Oracle
Criterion SQL ServerOracle
Replace NULL by a value ISNULL(x, y), or COALESCENVL(x, y), or COALESCE
One value if not NULL, another if NULL IIF or CASENVL2(x, if_not_null, if_null)
NULL when two values are equal NULLIF(x, y)NULLIF(x, y)
Compare two values, NULLs equal x IS NOT DISTINCT FROM y (2022 and later)DECODE(x, y, 1, 0) = 1
Join text, NULL as empty CONCAT(a, b, ...)a || b (NULL is skipped)

ISNULL returns its second argument when the first is NULL. NVL does the same in Oracle. NVL2 returns its second argument when the first is not NULL and its third when it is. COALESCE returns the first argument that is not NULL, in both engines. NULLIF returns NULL when its two arguments are equal. IS NOT DISTINCT FROM compares two values and treats two NULLs as equal. DECODE compares a value with a list and also treats two NULLs as equal.

What moving data between them changes

  • UNIQUE keys follow a different NULL rule in each engine, so a key that is valid in SQL Server can collide in Oracle.
  • Oracle’s B-tree indexes leave out rows whose key columns are all NULL.
  • ISNULL and COALESCE return different types, and COALESCE may evaluate an argument twice in SQL Server.
  • Before-and-after checks need a comparison that treats two NULLs as equal.
  • A dry run on a copy of the data puts numbers on every change before the real move.

Your turn

Exercise · Easy · SQL

Find the rows Oracle would refuse to load

A list of suppliers is about to be copied from SQL Server to Oracle. In SQL Server the table vendors has these columns:

  • id: the vendor's number;
  • name: the vendor's name, declared NOT NULL;
  • code: the vendor's short code, declared NOT NULL;
  • city: the vendor's city, which may be NULL.

SQL Server accepts an empty string in a NOT NULL column, because an empty string is a value there. Oracle stores an empty string as NULL, so a row with an empty name or an empty code would break the NOT NULL rule in Oracle, and the load would refuse it.

Write the query that returns the id and name of every vendor that Oracle would refuse for this reason. An empty city is no problem: that column accepts NULL. The query runs here on SQLite, which keeps empty strings the way SQL Server does.

Starter code · find_rejected.sql

-- Return the id and name of every vendor that Oracle would refuse to load,
-- because a NOT NULL column (name or code) holds an empty string.
SELECT id, name
FROM vendors
WHERE code IS NULL;
The sample tests · tests.yaml
# Sample tests: each one builds a small vendors table as it sits in SQL Server, runs your query on it and compares the
# rows it returns (in any order).
tests:
  - name: finds a vendor whose code is an empty string
    setup: |
      CREATE TABLE vendors (id INTEGER PRIMARY KEY, name TEXT NOT NULL, code TEXT NOT NULL, city TEXT);
      INSERT INTO vendors VALUES
        (1, 'Ganesh Traders',  'GT01', 'Pune'),
        (2, 'Noor Textiles',   '',     'Surat'),
        (3, 'Kaveri Spices',   'KS07', NULL);
    expected:
      columns: [id, name]
      rows:
        - [2, Noor Textiles]
  - name: finds an empty name too, but not an empty city
    setup: |
      CREATE TABLE vendors (id INTEGER PRIMARY KEY, name TEXT NOT NULL, code TEXT NOT NULL, city TEXT);
      INSERT INTO vendors VALUES
        (4, '',              'AN12', 'Kochi'),
        (5, 'Lotus Paper',   'LP03', ''),
        (6, 'Indus Steel',   'IS55', 'Nagpur');
    expected:
      columns: [id, name]
      rows:
        - [4, '']
  - name: returns a row once even when both columns are empty
    setup: |
      CREATE TABLE vendors (id INTEGER PRIMARY KEY, name TEXT NOT NULL, code TEXT NOT NULL, city TEXT);
      INSERT INTO vendors VALUES
        (7, '',              '',     'Delhi'),
        (8, 'Sahyadri Agro', '',     NULL),
        (9, 'Mehta & Sons',  'MS09', 'Rajkot');
    expected:
      columns: [id, name]
      rows:
        - [7, '']
        - [8, Sahyadri Agro]
  - name: returns nothing when every NOT NULL column has a value
    setup: |
      CREATE TABLE vendors (id INTEGER PRIMARY KEY, name TEXT NOT NULL, code TEXT NOT NULL, city TEXT);
      INSERT INTO vendors VALUES
        (10, 'Pragati Tools', 'PT10', NULL),
        (11, 'Vasant Foods',  'VF11', '');
    expected:
      columns: [id, name]
      rows: []
A hint

In this table an empty string is stored as '', so name = '' finds an empty name. A row is refused when either of the two NOT NULL columns is empty, so the condition has two parts joined by OR. Leave city out of it.

The sample tests run on this device, in your browser (sql.js): nothing is sent to mysmartcopilot.com. The first run downloads SQLite (about 0.7 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Check yourself

4 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 4 In Oracle, a row was inserted with VALUES (2, '') into a VARCHAR2 column nick. Which condition finds that row?

    Choose one answer.

    Show the answer to question 1

    Answer: nick IS NULL

    Oracle stored NULL, because it treats a zero-length string as NULL. Every comparison with NULL is unknown, so nick = '', nick <> 'x' and LENGTH(nick) = 0 (LENGTH of NULL is NULL) match nothing. Only IS NULL finds it.

  2. Question 2 of 4 The example's five customers hold the nicknames 'Ash', '', NULL, 'Kabs' and ''. What would COUNT(nick) return once the table is in Oracle?

    Type a number.

    Show the answer to question 2

    Answer: 2

    Both empty strings become NULL in Oracle, so three of the five values are NULL and COUNT(nick) counts the other two, 'Ash' and 'Kabs'. In SQL Server the same column gives 4, because '' is a value there.

  3. Question 3 of 4 What does 'Dr. ' || NULL || 'Rao' return in Oracle?

    Choose one answer.

    Show the answer to question 3

    Answer: 'Dr. Rao'

    Oracle's concatenation operator skips a NULL operand: the result is the other operands joined. NULL comes out only when every operand is NULL. T-SQL's + returns NULL here with its default settings, and so does || in SQL Server 2025.

  4. Question 4 of 4 In SQL Server, which way of joining a first name, a middle name that may be NULL and a last name never returns NULL because of the middle name?

    Choose one answer.

    Show the answer to question 4

    Answer: CONCAT(first_name, ' ', middle_name, ' ', last_name)

    CONCAT converts NULL arguments to empty strings. The + operator and SQL Server 2025's || return NULL when any operand is NULL, and the ISNULL version loses the first name whenever the middle name is NULL.

UNIQUE keys and indexes when empty strings become NULL, with the failures reproduced Pro: opens with a Pro, Premium or Ultimate pass

A UNIQUE key and an index both have their own rules for NULLs, and the three engines disagree on them. This part reproduces a load that fails only because empty strings turned into NULLs, and works out what an Oracle index on a nullable column does and does not contain.

In this part:

  • What each engine lets a UNIQUE key hold
  • A composite key that loads in SQL Server and fails in Oracle, reproduced
  • An Oracle index skips NULL keys, worked through with numbers

1 runnable example (SQL) · about 4 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

NULL functions in depth and a migration plan, tested on a dry run Pro: opens with a Pro, Premium or Ultimate pass

The NULL functions look interchangeable, but they differ in the type they return, in when they evaluate their arguments and in how they compare two NULLs. This part shows where each one surprises people and turns everything in the lesson into a migration plan with numbers from a dry run.

In this part:

  • Return types and evaluation, where ISNULL, NVL and COALESCE disagree
  • Comparing two values that may be NULL, with the failure reproduced
  • A migration plan, sized on a 1,000-row dry run

2 runnable examples (SQL) · about 4 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

  1. Fresher to mid · Warm-up

    What does Oracle store when you insert an empty string into a VARCHAR2 column, and why does WHERE nick = '' then find nothing?

    Oracle stores NULL. Its SQL reference says that a character value with a length of zero is treated as null, so the INSERT succeeds, but the row holds NULL instead of an empty string. The condition nick = '' compares the column with an empty string, which Oracle also reads as NULL, and a comparison with NULL is unknown, never true, so no row matches. Use nick IS NULL to find the row. SQL Server behaves differently: it keeps '' as a value of length 0 that is not NULL, so nick = '' finds the row there and nick IS NULL does not. Oracle's documentation adds that this treatment may change in a future release and advises against treating empty strings and NULLs as the same, so write IS NULL whenever you mean a missing value.

  2. Mid to senior

    A SQL Server table with UNIQUE (branch_code, counter_code) holds 2 million rows and no duplicates. Copied into Oracle with the same constraint, the load stops with ORA-00001. Why, and how do you find and fix the rows before the next attempt?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    After a reporting database moved from SQL Server to Oracle, the number of customers with a phone number fell from 84,000 to 71,000, yet the row count of every table matches. What happened, and how do you make the two reports agree?

    Model answer, Pro. First step and follow-ups

  4. Mid to staff

    The same application stores customers in SQL Server for one client and in Oracle for another. Some customers have no middle name. How should the schema and the code represent that, so every query gives the same answer on both engines?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Oracle stores an empty string as NULL; SQL Server keeps it as a value of length 0.
  • In Oracle, test for a missing text value with IS NULL: = '' never matches.
  • COUNT(column) and COUNT(DISTINCT column) drop when empty strings become NULLs.
  • Oracle’s || skips NULLs; T-SQL’s + and || return NULL, and CONCAT treats NULL as empty.
  • NULLIF(column, '') previews in SQL Server what Oracle would store.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.