Syntax & Data Types
MySQL and SQL Server both speak "SQL," but the accent is different everywhere you look — starting with how each one lets you quote an identifier that collides with a reserved word, and continuing straight into the type system. Nothing here is exotic once you know it's coming, but every one of these differences will break a copy-pasted script on day one if you don't.
💡 The Bottom Line
Swap backticks `like_this` for square brackets [like_this] when quoting
identifiers, swap VARCHAR for NVARCHAR and stop worrying about charsets
(but start prefixing Unicode string literals with N), swap DATETIME/
TIMESTAMP for DATETIME2, swap TINYINT(1) for a proper
BIT type, and swap the CONCAT() function for the + string
operator. Get those five swaps automatic and most MySQL scripts port over with only minor friction.
Quoting identifiers
SELECT `Order Date`, `User`
FROM `orders`
WHERE `User` = 'sdeiv';
SELECT [Order Date], [User]
FROM [dbo].[Orders]
WHERE [User] = 'sdeiv';
📋 Key differences
- MySQL quotes identifiers with backticks
`Order Date`; SQL Server uses square brackets[Order Date](double quotes also work in SQL Server as long asQUOTED_IDENTIFIERis on, which it is by default — but brackets are the idiomatic T-SQL choice and work regardless of that setting). - SQL Server table names are almost always schema-qualified, e.g.
[dbo].[Orders]— every table lives inside a schema (dboby default) inside a database, a third namespace level MySQL doesn't have. MySQL treats each database as its own flat namespace with no schema layer in between. - Both engines let you skip the quoting entirely if the identifier isn't a reserved word and has no spaces or special characters — quoting is a safety net, not a requirement.
⚠️ Gotcha
Copy-pasting a MySQL script into SQL Server Management Studio with the backticks left in
place doesn't throw a helpful "wrong quote character" error — T-SQL just doesn't recognize
`User` as an identifier at all and throws a syntax error pointing at the backtick
itself. If you see a syntax error right at a stray `, that's almost always this.
String types: VARCHAR vs NVARCHAR (and the N'' prefix)
CREATE TABLE users (
name VARCHAR(100)
CHARACTER SET utf8mb4
NOT NULL
);
CREATE TABLE Users (
Name NVARCHAR(100) NOT NULL
);
MySQL doesn't split string types at the type level — VARCHAR stores whatever
character set the column (or table, or database) is configured with, and you get full Unicode
support by setting the charset to utf8mb4. SQL Server takes the opposite approach: it
splits string types into VARCHAR (single-byte, non-Unicode, tied to a collation's code
page) and NVARCHAR (UCS-2/UTF-16 Unicode, always safe for any language or emoji). The
practical translation: a MySQL VARCHAR(100) CHARACTER SET utf8mb4 column becomes
NVARCHAR(100), full stop — no charset clause needed, because Unicode-ness is baked
into the type itself rather than configured separately.
⚠️ Gotcha
MySQL string literals are just 'plain text', no matter what's inside them. In
SQL Server, a string literal assigned to (or compared against) an NVARCHAR column
should be prefixed with a capital N: N'plain text'. Without it, SQL
Server treats the literal as the non-Unicode VARCHAR type and implicitly converts it
— which usually works for plain ASCII text but silently mangles any character outside the
current collation's code page (accented letters, non-Latin scripts, emoji) into question marks
or best-fit substitutes. It won't error; it'll just quietly corrupt the data you inserted.
Date and time: DATETIME/TIMESTAMP vs DATETIME2
CREATE TABLE events (
starts_at DATETIME(3) NOT NULL
);
CREATE TABLE Events (
StartsAt DATETIME2(3) NOT NULL
);
📋 Key differences
- MySQL's
DATETIMEwith an explicit fractional-second precision (e.g.DATETIME(3)for milliseconds, up to 6 digits) is the type most MySQL developers reach for; SQL Server's closest match isDATETIME2, its modern, high-precision date/time type (up to 100ns precision, i.e. 7 fractional digits). - Avoid SQL Server's older, legacy
DATETIMEtype even though the name looks familiar — it rounds to the nearest 1/300th of a second (an odd, non-decimal precision left over from an old SQL Server internal format) and has a smaller date range thanDATETIME2. Always reach forDATETIME2on new tables. - MySQL's
TIMESTAMPtype (UTC-converting, 1970–2038 range) has no direct SQL Server equivalent — the nearest concept isDATETIMEOFFSET, which stores an explicit UTC offset alongside the value instead of silently converting based on session time zone.
Booleans: TINYINT(1) vs BIT
CREATE TABLE users (
is_active TINYINT(1) NOT NULL DEFAULT 1
);
CREATE TABLE Users (
IsActive BIT NOT NULL DEFAULT 1
);
MySQL has no true boolean type — BOOLEAN/BOOL are just aliases for
TINYINT(1), a one-byte integer that behaves like a boolean in practice but doesn't stop
anyone from inserting 5 into it. SQL Server has a dedicated BIT type that
stores exactly 0, 1, or NULL and is treated as a true boolean by tools and drivers — no accidental
"5 is truthy" values are possible, because BIT rejects anything else at the type
level.
Concatenating strings: CONCAT() vs +
SELECT CONCAT(first_name, ' ', last_name) AS full_name
FROM users;
SELECT FirstName + ' ' + LastName AS FullName
FROM Users;
⚠️ Gotcha
SQL Server's + operator does double duty as both numeric addition and string
concatenation — T-SQL figures out which one you mean from the operand types. That's convenient
until NULL gets involved: 'Hello' + NULL returns NULL, not
'Hello', silently erasing the whole expression. MySQL developers used to
CONCAT() quietly ignoring NULL arguments (or, in CONCAT_WS()'s case,
skipping them) will find T-SQL's all-or-nothing NULL propagation the more surprising behavior
here — wrap nullable columns in ISNULL(col, '') before concatenating with
+, or use SQL Server's own CONCAT() function, which does ignore NULLs
the way MySQL's does.
🔧 Try It Yourself
Take a MySQL CREATE TABLE statement you've written before — ideally one with a
name column, a flag column, and a timestamp column — and rewrite it for SQL Server: square
brackets instead of backticks, NVARCHAR(...) instead of VARCHAR(...) CHARACTER
SET utf8mb4, BIT instead of TINYINT(1), and DATETIME2
instead of DATETIME. Then rewrite one SELECT that concatenates two
columns, using + instead of CONCAT() — and add an ISNULL()
guard around any column that might be NULL.
🙋 There Are No Dumb Questions
Q: If I forget the N prefix and just write 'some text' against an
NVARCHAR column in SQL Server, what happens?
A: It still runs — SQL Server implicitly converts the plain string literal to Unicode. The risk
isn't a broken query, it's silent data loss: if the literal contains a character outside the
database's default (non-Unicode) collation's code page — an emoji, an accented letter outside
that code page, non-Latin script — that character gets replaced with ? or a
best-fit substitute during the implicit conversion, before it ever reaches the Unicode column.
Being explicit about N'' on any literal that might contain non-ASCII text avoids
that surprise later.
✅ Quick Check
A MySQL developer writes SELECT FirstName + LastName FROM Users against SQL
Server, where LastName is NULL for one row. What happens for that row?
Next up: once the tables exist, the next friction point is how you query them — LIMIT becomes TOP or OFFSET-FETCH, and a few string/NULL-handling functions all change names or shape.