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

Collations and case in SQL Server and Oracle

SQL Server compares text without regard to case by default; Oracle compares bytes. Control case, accents and Hindi or Marathi text in both engines.

  • Intermediate
  • 30 minutes
  • Examples run with sql.js 1.14.2
  • By MySmartCoPilot
Free core, Pro depth: 11 of 30 minutes free
Free: Two defaults on one table · Reading a collation's name · Where the rule is set · 2 more sections. With a pass: 2 in-depth parts and 3 interview answers, about 19 minutes.

What you will learn

  • Predict whether a comparison, DISTINCT or ORDER BY ignores case and accents in each engine
  • Configure one query or one column to compare text with or without regard to case
  • Fix collation conflicts, including the temporary-table trap in SQL Server
  • Store Hindi and other Indian-language text without data loss

Before you start

What you’ll learn · Before you start

What you will learn

  • Predict whether a comparison, DISTINCT or ORDER BY ignores case and accents in each engine
  • Configure one query or one column to compare text with or without regard to case
  • Fix collation conflicts, including the temporary-table trap in SQL Server
  • Store Hindi and other Indian-language text without data loss

Before you start

On this page

A collation is the rule a database uses to compare and sort text, and SQL Server and Oracle start from opposite rules. SQL Server’s setup takes its default from the operating system’s locale: SQL_Latin1_General_CP1_CI_AS for English (United States) and Latin1_General_CI_AS for English (India), both case-insensitive and accent-sensitive. So 'Pune' = 'PUNE' is true, DISTINCT folds Pune, PUNE and pune into one value, and 'café' = 'cafe' stays false. Oracle compares text as binary by default, because the session settings NLS_COMP and NLS_SORT are BINARY: the same comparison is false, DISTINCT keeps three cities, and every capital letter sorts before every small one. Collations therefore decide whether a login, a duplicate check or a report gives the same answer after a move between the engines.

Two defaults on one table

SQLite has two collations that behave like the two defaults for English letters: BINARY compares bytes as Oracle does, and NOCASE ignores the case of the letters A to Z as SQL Server’s _CI collations do. That makes it a fair model of both engines for the words below, which use English letters only:

One comparison, one DISTINCT and one sort under each default SQL · case_rules.sql
-- The two defaults, modelled with SQLite's own collations for English letters only:
--   BINARY compares bytes, like Oracle's default (NLS_COMP = BINARY, NLS_SORT = BINARY) or a SQL Server _BIN2 collation;
--   NOCASE folds the 26 letters A to Z, like SQL Server's case-insensitive default (_CI). SQL Server's _CI collations
--   also fold accented capitals, which NOCASE does not, so these rows use English letters only.
CREATE TABLE cities (id INTEGER PRIMARY KEY, name TEXT NOT NULL);
INSERT INTO cities (name) VALUES ('Pune'), ('PUNE'), ('pune'), ('Mumbai'), ('mumbai');

CREATE TABLE produce (name TEXT NOT NULL);
INSERT INTO produce (name) VALUES ('banana'), ('Zucchini'), ('apple'), ('Mango');

-- 1. Equality and a filter.
SELECT 'Pune' = 'PUNE'                AS equal_binary,
       'Pune' = 'PUNE' COLLATE NOCASE AS equal_case_insensitive,
       (SELECT count(*) FROM cities WHERE name = 'pune')                AS rows_binary,
       (SELECT count(*) FROM cities WHERE name = 'pune' COLLATE NOCASE) AS rows_case_insensitive;

-- 2. DISTINCT and GROUP BY use the same rule as equality.
SELECT count(DISTINCT name)                AS distinct_binary,
       count(DISTINCT name COLLATE NOCASE) AS distinct_case_insensitive
FROM cities;

-- 3. Sorting: bytes put every capital letter before every small one.
SELECT group_concat(name, ', ' ORDER BY name)                AS order_binary,
       group_concat(name, ', ' ORDER BY name COLLATE NOCASE) AS order_case_insensitive
FROM produce;

Output

┌──────────────┬────────────────────────┬─────────────┬───────────────────────┐
│ equal_binary │ equal_case_insensitive │ rows_binary │ rows_case_insensitive │
├──────────────┼────────────────────────┼─────────────┼───────────────────────┤
│ 0            │ 1                      │ 1           │ 3                     │
└──────────────┴────────────────────────┴─────────────┴───────────────────────┘
┌─────────────────┬───────────────────────────┐
│ distinct_binary │ distinct_case_insensitive │
├─────────────────┼───────────────────────────┤
│ 5               │ 2                         │
└─────────────────┴───────────────────────────┘
┌────────────────────────────────┬────────────────────────────────┐
│          order_binary          │     order_case_insensitive     │
├────────────────────────────────┼────────────────────────────────┤
│ Mango, Zucchini, apple, banana │ apple, banana, Mango, Zucchini │
└────────────────────────────────┴────────────────────────────────┘

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

Every answer differs. The filter finds one row under the binary rule and three without regard to case. COUNT(DISTINCT name) gives 5 or 2, so a report of “cities” changes size after a move. The sort puts Mango and Zucchini first under the binary rule, because capitals have smaller codes than small letters. Nothing fails loudly: a login, a search box or a duplicate check written for SQL Server keeps running in Oracle and quietly returns fewer rows.

Accents are where the model stops. Letter case in SQL Server is folded for accented letters too, so É equals é under _CI, while SQLite’s NOCASE folds only A to Z. And an accent-sensitive collation (_AS) keeps café and cafe apart in SQL Server, while _AI makes them equal.

Reading a collation’s name

The name of a collation states its rules. Ignoring case in SQL Server is a matter of choosing a name with _CI:

Collation names in SQL Server and sort names in Oracle
Criterion SQL ServerOracle
Case-sensitive or not _CS or _CI in the collation nameBINARY compares case; a name ending in _CI ignores it
Accent-sensitive or not _AS or _AIA name ending in _AI ignores accents and case
Compare code points _BIN2 (and the older _BIN)BINARY, the default
UTF-8 in char and varchar _UTF8, SQL Server 2019 and laterThe database character set, AL32UTF8
Rule for literals and variables The current database collationThe session: NLS_COMP and NLS_SORT

_BIN2 collations compare Unicode code points, which is what Oracle’s BINARY does with the bytes of its database character set. BINARY_CI ignores case but keeps accents; BINARY_AI ignores both.

Where the rule is set

Where the comparison rule comes from: SQL Server instance, database, column, expression; Oracle session, column, expression.SQL ServerOracleInstance collationchosen at setup; tempdb uses it,so #temp tables do tooDatabase collationCREATE DATABASE … COLLATE …Column collationCREATE TABLE … name varchar(50) COLLATE …Expressionname COLLATE Latin1_General_100_CS_ASSession settingsNLS_COMP and NLS_SORT, BINARY by defaultTable or column collationonly with MAX_STRING_SIZE = EXTENDEDExpressionname COLLATE BINARY_CIdefault fordefault foroverridden byused unless a column declares oneoverridden by

Where each engine takes its rule for comparing text

Text description of the diagram

The diagram has two columns of boxes, one for each engine. In each, a level is the default for the level below it, and the last level overrides everything above it.

SQL Server, from top to bottom: 1. The instance collation, chosen at setup. The tempdb system database uses it, so temporary tables do too. 2. The database collation, set with CREATE DATABASE … COLLATE. It is the default for the columns of that database. 3. The column collation, set in CREATE TABLE with COLLATE after the column's type. 4. An expression with COLLATE, such as name COLLATE Latin1_General_100_CS_AS, which overrides the column's collation for one comparison or sort.

Oracle, from top to bottom: 1. The session settings NLS_COMP and NLS_SORT, both BINARY by default. A column without a declared collation follows them. 2. A table or column collation declared in the DDL, which Oracle accepts only when the initialization parameter MAX_STRING_SIZE is EXTENDED. 3. An expression with the COLLATE operator, such as name COLLATE BINARY_CI, which works whatever MAX_STRING_SIZE is.

In SQL Server each level is the default for the next one down: the instance collation for databases, the database collation for columns, and a column’s collation for every comparison that uses it. COLLATE after an expression overrides all of them for one comparison or sort. The instance collation also belongs to tempdb, where temporary tables live, which matters as soon as a database uses another collation.

Oracle puts the rule in the session: NLS_SORT names a sort order and NLS_COMP decides which operations follow it. A column can declare its own collation only when the database runs with MAX_STRING_SIZE = EXTENDED; otherwise the DDL fails with ORA-43929. The COLLATE operator on an expression works either way.

Hindi, Marathi and other Indian scripts

A collation in SQL Server also fixes the code page of char and varchar columns, and a Latin code page has no Devanagari letters at all. Microsoft’s documentation adds that data lost in a code-page conversion is not reported, so a Hindi name saved into such a column is damaged without any error. The run below counts characters and bytes:

Characters and bytes of four names, and the code points of one word SQL · hindi_bytes.sql
-- Characters and bytes of four names: what a column sized in bytes can hold. SQLite stores text as UTF-8, so the
-- length of the text cast to a BLOB is its UTF-8 size. UTF-16, used by SQL Server's nvarchar and Oracle's
-- NVARCHAR2 (AL16UTF16), takes 2 bytes for every character up to U+FFFF and 4 bytes above it.
WITH names(label, name) AS (
  VALUES ('English name', 'Asha'), ('French name', 'Zoë'), ('Hindi greeting', 'नमस्ते'), ('Marathi, the word', 'मराठी')
),
chars(label, name, pos, cp) AS (
  SELECT label, name, 1, unicode(substr(name, 1, 1)) FROM names
  UNION ALL
  SELECT label, name, pos + 1, unicode(substr(name, pos + 1, 1)) FROM chars WHERE pos < length(name)
)
SELECT label,
       count(*)                                    AS characters,
       length(CAST(name AS BLOB))                  AS utf8_bytes,
       sum(CASE WHEN cp > 65535 THEN 4 ELSE 2 END) AS utf16_bytes,
       sum(cp > 255)                               AS beyond_latin1,
       name
FROM chars
GROUP BY label, name
ORDER BY utf8_bytes, label;

-- The six characters of नमस्ते: all in the Devanagari block U+0900 to U+097F, so 3 bytes each in UTF-8.
WITH chars(pos, ch) AS (
  SELECT 1, substr('नमस्ते', 1, 1)
  UNION ALL
  SELECT pos + 1, substr('नमस्ते', pos + 1, 1) FROM chars WHERE pos < length('नमस्ते')
)
SELECT pos, printf('U+%04X', unicode(ch)) AS code_point, length(CAST(ch AS BLOB)) AS utf8_bytes, ch
FROM chars;

Output

┌───────────────────┬────────────┬────────────┬─────────────┬───────────────┬────────┐
│       label       │ characters │ utf8_bytes │ utf16_bytes │ beyond_latin1 │  name  │
├───────────────────┼────────────┼────────────┼─────────────┼───────────────┼────────┤
│ English name      │ 4          │ 4          │ 8           │ 0             │ Asha   │
│ French name       │ 3          │ 4          │ 6           │ 0             │ Zoë    │
│ Marathi, the word │ 5          │ 15         │ 10          │ 5             │ मराठी  │
│ Hindi greeting    │ 6          │ 18         │ 12          │ 6             │ नमस्ते │
└───────────────────┴────────────┴────────────┴─────────────┴───────────────┴────────┘
┌─────┬────────────┬────────────┬────┐
│ pos │ code_point │ utf8_bytes │ ch │
├─────┼────────────┼────────────┼────┤
│ 1   │ U+0928     │ 3          │ न  │
│ 2   │ U+092E     │ 3          │ म  │
│ 3   │ U+0938     │ 3          │ स  │
│ 4   │ U+094D     │ 3          │ ्  │
│ 5   │ U+0924     │ 3          │ त  │
│ 6   │ U+0947     │ 3          │ े  │
└─────┴────────────┴────────────┴────┘

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

Each Devanagari letter and sign is one code point between U+0900 and U+097F, and UTF-8 needs 3 bytes for each of them, so नमस्ते takes 18 bytes in UTF-8 and 12 in UTF-16. Store such text in nvarchar, or in varchar under a _UTF8 collation in SQL Server 2019 and later. In Oracle use an AL32UTF8 database with VARCHAR2 sized in characters, or NVARCHAR2.

Your turn

Exercise · Easy · SQL

List the logins that would collide without regard to case

A shop's accounts move from SQL Server, where the email column used a case-insensitive collation, to Oracle, which compares text byte by byte. In Oracle, the team will keep emails unique without regard to case with a unique index on UPPER(email). The copy of the data you have here came from an older system that never checked this, so some addresses appear more than once in different capitals, and the index would refuse to build.

The table users has two columns, id and email. Write the query that lists every address the index would reject, one row per address: the address in capitals as email_key, and the number of accounts that share it as accounts. Addresses that occur only once must not appear.

Starter code · case_collisions.sql

-- List each address that appears more than once without regard to case:
-- the address in capitals as email_key, and how many accounts share it as accounts.
SELECT email AS email_key, count(*) AS accounts
FROM users
GROUP BY email
HAVING count(*) > 1;
The sample tests · tests.yaml
# Sample tests: each one builds a small users table, runs your query on it and compares the rows it returns (in any
# order).
tests:
  - name: finds two spellings of one address
    setup: |
      CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT NOT NULL);
      INSERT INTO users VALUES
        (1, 'asha@mail.in'),
        (2, 'Asha@Mail.in'),
        (3, 'ravi@mail.in');
    expected:
      columns: [email_key, accounts]
      rows:
        - [ASHA@MAIL.IN, 2]
  - name: counts three spellings once, and leaves single addresses out
    setup: |
      CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT NOT NULL);
      INSERT INTO users VALUES
        (1, 'meera.s@mail.in'),
        (2, 'MEERA.S@MAIL.IN'),
        (3, 'Meera.S@mail.IN'),
        (4, 'kabir@mail.in'),
        (5, 'zoya@mail.in');
    expected:
      columns: [email_key, accounts]
      rows:
        - [MEERA.S@MAIL.IN, 3]
  - name: reports every colliding address
    setup: |
      CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT NOT NULL);
      INSERT INTO users VALUES
        (1, 'imran@mail.in'),
        (2, 'Imran@mail.in'),
        (3, 'neha@mail.in'),
        (4, 'NEHA@mail.in'),
        (5, 'neha@MAIL.in'),
        (6, 'tenzin@mail.in');
    expected:
      columns: [email_key, accounts]
      rows:
        - [IMRAN@MAIL.IN, 2]
        - [NEHA@MAIL.IN, 3]
  - name: returns nothing when every address is unique
    setup: |
      CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT NOT NULL);
      INSERT INTO users VALUES
        (1, 'arjun@mail.in'),
        (2, 'arjun.k@mail.in'),
        (3, 'fatima@mail.in');
    expected:
      columns: [email_key, accounts]
      rows: []
A hint

Group the rows by the same expression the index will use, upper(email), and count the rows in each group. A group is a problem only when it has more than one row, which is a condition on the groups, so it belongs in HAVING.

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

5 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 5 A SQL Server database uses the collation SQL_Latin1_General_CP1_CI_AS. Which pair of comparisons is true?

    Choose one answer.

    Show the answer to question 1

    Answer: 'Pune' = 'PUNE' is true, and 'café' = 'cafe' is false

    CI means case-insensitive, so capitals do not count, and AS means accent-sensitive, so é and e stay different. A collation ending in _CI_AI would make the second comparison true as well.

  2. Question 2 of 5 An Oracle table with the default NLS settings holds the cities 'Pune', 'PUNE' and 'pune'. What does SELECT COUNT(DISTINCT city) return?

    Type a number.

    Show the answer to question 2

    Answer: 3

    With NLS_COMP and NLS_SORT both BINARY, DISTINCT compares the bytes, so the three spellings are three values. SQL Server's case-insensitive default would return 1.

  3. Question 3 of 5 Put these words in the order that Oracle's default ORDER BY returns them.

    Give each item its position, from 1 (first).

    Show the answer to question 3

    Answer:

    1. Mango
    2. Zucchini
    3. apple
    4. banana

    A binary sort compares character codes, and every capital letter comes before every small letter, so Mango and Zucchini come first. A case-insensitive collation, such as SQL Server's default, gives apple, banana, Mango, Zucchini instead.

  4. Question 4 of 5 In an Oracle session you run ALTER SESSION SET NLS_SORT = BINARY_CI and nothing else. What changes?

    Choose one answer.

    Show the answer to question 4

    Answer: ORDER BY ignores case, but = and DISTINCT still compare bytes

    ORDER BY follows NLS_SORT, but with NLS_COMP left at BINARY the comparison operators, LIKE, DISTINCT and GROUP BY stay binary. Setting NLS_COMP = LINGUISTIC as well makes them follow NLS_SORT too.

  5. Question 5 of 5 How many bytes does the Hindi word नमस्ते take in UTF-8, according to the example's output?

    Type a number.

    Show the answer to question 5

    Answer: 18

    The word has six characters, all in the Devanagari block U+0900 to U+097F, and UTF-8 needs 3 bytes for each of them: 18 bytes. In UTF-16, as in nvarchar or NVARCHAR2, it takes 12.

Case-insensitive on purpose, per query, per session and in a unique index Pro: opens with a Pro, Premium or Ultimate pass

Most systems need both rules: case-insensitive matching for logins and searches, exact matching for codes and passwords. This part shows how to pick the rule for one comparison, one session or one column in each engine, and reproduces a case-insensitive unique email with the index that keeps its login fast.

In this part:

  • One comparison at a time in SQL Server, and what LIKE does with it
  • Oracle’s two session switches, and the one that is not enough
  • A unique email without regard to case, reproduced

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).

Collation conflicts, the tempdb trap and sizing Unicode columns, worked through Pro: opens with a Pro, Premium or Ultimate pass

Two collations in one comparison is an error in SQL Server, and the most common way to get one is a temporary table. This part follows that failure step by step, then sizes text columns for Indian scripts with the numbers for each engine.

In this part:

  • Why two collations in one comparison fail
  • The tempdb trap, step by step
  • Sizing text columns for Indian scripts, with the numbers

1 runnable example (SQL) · about 3 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 do CI and AS mean in the SQL Server collation SQL_Latin1_General_CP1_CI_AS, and how does Oracle compare text by default?

    CI means case-insensitive and AS means accent-sensitive, so under this collation 'Pune' = 'PUNE' is true but 'café' = 'cafe' is false; CP1 names code page 1252, used for char and varchar columns. SQL Server's setup picks this collation from the operating system's locale for English (United States). Oracle starts from the opposite rule: its session settings NLS_COMP and NLS_SORT are BINARY by default, so it compares the bytes of the text. 'Pune' = 'PUNE' is then false, DISTINCT keeps both spellings, and every capital letter sorts before every small one. A query that relies on SQL Server's case-insensitive default therefore returns fewer rows in Oracle unless the comparison ignores case on purpose.

  2. Mid to senior

    A stored procedure runs in 40 customer databases. In one of them, created with a case-sensitive collation, it fails with a collation conflict at a join between a temporary table and a permanent table. Why only there, and how do you fix it for all 40?

    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

    In Oracle, a login query uses WHERE UPPER(email) = UPPER(:email) on a users table of 5 million rows and takes 900 ms. Make it fast, and make sure no two accounts share an address in different capitals.

    Model answer, Pro. First step and follow-ups

  4. Mid to senior

    Customer names typed in Hindi look right in the app before saving, but a SQL Server report shows them as question marks. The column is varchar(50). Find the cause and fix it without losing more data.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • SQL Server’s usual default collation ignores case and respects accents; Oracle compares binary by default.
  • In SQL Server, COLLATE sets the rule per database, column or expression.
  • In Oracle, NLS_SORT alone changes only sorting; NLS_COMP = LINGUISTIC makes comparisons follow it.
  • Store Indian scripts as Unicode, and size byte-counted columns at 3 bytes per Devanagari character in UTF-8.

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.