Syntax & Data Types
The good news first: both Oracle and SQL Server speak SQL, and 80% of a query looks
identical in either. The friction shows up at the edges — statement terminators, the exact
names of data types, and how each engine treats NULL and empty strings. Get those
out of the way first and the rest of the transition is mostly vocabulary.
💡 The Bottom Line
Oracle is strict about semicolons ending PL/SQL statements; SQL Server's T-SQL is far more
relaxed and often doesn't need them at all (though modern best practice adds them anyway).
Oracle's VARCHAR2/NUMBER/DATE map to SQL Server's
VARCHAR/DECIMAL/DATETIME2 — similar jobs, different names,
slightly different edge-case behavior. And the two engines disagree, sharply, on what an empty
string is.
Statement terminators
SELECT last_name
FROM employees
WHERE department_id = 10;
BEGIN
DBMS_OUTPUT.PUT_LINE('Done');
END;
/
SELECT last_name
FROM employees
WHERE department_id = 10;
PRINT 'Done';
GO
📋 Key differences
- Oracle requires a semicolon after every PL/SQL statement; a trailing
/on its own line tells SQL*Plus/SQL Developer to execute the preceding block. - SQL Server uses
GOas a batch separator for tools like SSMS — it's not part of T-SQL itself, and semicolons are statement terminators that are often optional (though required before some statements likeWITHcommon table expressions). DBMS_OUTPUT.PUT_LINEbecomesPRINTfor simple debug output.
Data types: same job, different names
CREATE TABLE employees (
employee_id NUMBER(10),
first_name VARCHAR2(50),
hire_date DATE,
salary NUMBER(10,2)
);
CREATE TABLE employees (
employee_id INT,
first_name VARCHAR(50),
hire_date DATETIME2,
salary DECIMAL(10,2)
);
📋 Key differences
VARCHAR2(n)→VARCHAR(n). SQL Server's plainVARCHARis fine here; there's noVARCHAR2at all in T-SQL.NUMBER(p,s)→DECIMAL(p,s)(orNUMERIC, an alias). For whole numbers, Oracle developers usually just usedNUMBERwith no scale — SQL Server has real integer types (INT,BIGINT,SMALLINT) that are cheaper to store and index than a decimal.- Oracle's
DATEactually stores a time component down to the second — it behaves more like SQL Server'sDATETIME2than like a date-only type. SQL Server also has a true date-only type,DATE, with no time component at all. - For Unicode text, Oracle's
NVARCHAR2maps to SQL Server'sNVARCHAR.
String concatenation
SELECT first_name || ' ' || last_name AS full_name
FROM employees;
SELECT first_name + ' ' + last_name AS full_name
FROM employees;
-- or: CONCAT(first_name, ' ', last_name)
Oracle's concatenation operator is ||. SQL Server overloads the plain
+ operator for strings instead — which is convenient until you concatenate a
NULL (see the gotcha below), so many T-SQL developers prefer the explicit
CONCAT() function, which sidesteps that trap entirely.
⚠️ Gotcha
In Oracle, 'Hello' || NULL evaluates to 'Hello' — Oracle's
|| treats NULL as an empty string for concatenation purposes. In SQL
Server, 'Hello' + NULL evaluates to NULL — the entire expression
collapses to NULL the moment one operand is NULL. If you're porting
an Oracle report that concatenates optional (nullable) columns, use CONCAT() or
wrap the nullable column in ISNULL(col, ''), or every row with a missing middle
name will show a blank full name instead of "first last".
NULL handling functions
SELECT NVL(commission_pct, 0) AS commission
FROM employees;
SELECT NVL2(commission_pct, 'Yes', 'No')
FROM employees;
SELECT ISNULL(commission_pct, 0) AS commission
FROM employees;
SELECT IIF(commission_pct IS NOT NULL, 'Yes', 'No')
FROM employees;
📋 Key differences
NVL(expr, replacement)maps most directly toISNULL(expr, replacement)— both return the replacement only when the first argument isNULL.- The ANSI-standard
COALESCE(a, b, c, ...)works identically in both Oracle and SQL Server (returns the first non-null argument) — prefer it when you want code that's portable across engines, sinceISNULLis T-SQL-only. - Oracle's
NVL2(three-way: value, if-not-null, if-null) has no single T-SQL equivalent — useIIF(condition, true_val, false_val)or aCASEexpression instead.
🔧 Try It Yourself
Take a CREATE TABLE statement from an Oracle schema you know and rewrite it for
SQL Server: swap VARCHAR2 for VARCHAR, pick between DATE
and DATETIME2 based on whether you actually need a time component, and replace any
NUMBER primary key with INT. Then rewrite one query that uses
|| and NVL, converting both to their T-SQL equivalents.
🙋 There Are No Dumb Questions
Q: Can I just use COALESCE everywhere instead of learning ISNULL?
A: Yes, and it's arguably the better habit — COALESCE is ANSI SQL and works in both
Oracle and SQL Server (and Postgres, MySQL, etc.), while ISNULL only exists in
T-SQL and NVL only exists in Oracle. The one wrinkle: COALESCE's
result type is determined by data type precedence across all its arguments, which can
occasionally surprise you, whereas ISNULL's result type always matches its first
argument. For simple two-argument cases the difference rarely matters.
✅ Quick Check
You're porting an Oracle report that builds a display name with
first_name || ' ' || middle_name || ' ' || last_name, where middle_name
is often NULL. You rewrite it in T-SQL using plain +. What happens for
rows where middle_name is NULL?
Next up: once your tables and types are in place, the next friction point is how you query
them — pagination, the mysterious DUAL table, and a handful of renamed string
functions.