Syntax & Data Types
SQL Server and MySQL 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 square brackets [like_this] for backticks `like_this` when quoting
identifiers, swap NVARCHAR for VARCHAR with an explicit UTF-8 charset,
swap DATETIME2 for DATETIME or TIMESTAMP depending on whether you
need timezone-aware behavior, swap BIT for TINYINT(1), and swap the
+ string operator for CONCAT(). Get those five swaps automatic and most
T-SQL scripts port over with only minor friction.
Quoting identifiers
SELECT [Order Date], [User]
FROM [dbo].[Orders]
WHERE [User] = 'sdeiv';
SELECT `Order Date`, `User`
FROM `orders`
WHERE `User` = 'sdeiv';
📋 Key differences
- SQL Server quotes identifiers with square brackets
[Order Date]; MySQL uses backticks`Order Date`(double quotes only work in MySQL ifANSI_QUOTESmode is enabled, so don't rely on them by default). - SQL Server's schema-qualified names look like
[dbo].[Orders]; MySQL treats each database as its own namespace and doesn't use adbo-style default schema — you'd writemydb.ordersonly when crossing databases. - 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 T-SQL script into a MySQL client with the brackets left in place doesn't
throw a helpful "wrong quote character" error — MySQL just doesn't recognize
[User] as an identifier at all and throws a syntax error pointing at the bracket
itself. If you see a syntax error right at a [, that's almost always this.
String types: NVARCHAR vs VARCHAR + charset
CREATE TABLE Users (
Name NVARCHAR(100) NOT NULL
);
CREATE TABLE users (
name VARCHAR(100)
CHARACTER SET utf8mb4
NOT NULL
);
SQL Server splits string types into VARCHAR (single-byte, non-Unicode) and
NVARCHAR (Unicode) because its default collations are not Unicode-aware. MySQL doesn't
make that split at the type level — VARCHAR stores whatever character set the column
(or table, or database) is configured with. The practical translation: an NVARCHAR
column becomes a VARCHAR column on a table (or database) whose default charset is
utf8mb4 — MySQL's true, 4-byte-per-character UTF-8, not the older 3-byte
utf8 alias, which can't store the full Unicode range (including emoji).
Date and time: DATETIME2 vs DATETIME/TIMESTAMP
CREATE TABLE Events (
StartsAt DATETIME2(3) NOT NULL
);
CREATE TABLE events (
starts_at DATETIME(3) NOT NULL
);
📋 Key differences
DATETIME2is SQL Server's modern, high-precision date/time type (up to 100ns precision); MySQL's closest match isDATETIMEwith an explicit fractional-second precision, e.g.DATETIME(3)for milliseconds — MySQL supports up to 6 digits.- MySQL also has
TIMESTAMP, which looks similar but stores values in UTC internally and converts on read/write based on the session time zone, and has a much smaller range (1970–2038). UseDATETIMEwhen you want SQL Server-like "store exactly what I gave you" behavior; reach forTIMESTAMPonly when you specifically want automatic UTC conversion or a self-updating "last modified" column. - Neither
DATETIMEnorDATETIME2stores a time zone offset the way some other databases do — both are "wall clock" values unless you convert explicitly.
Booleans: BIT vs TINYINT(1)
CREATE TABLE Users (
IsActive BIT NOT NULL DEFAULT 1
);
CREATE TABLE users (
is_active TINYINT(1) NOT NULL DEFAULT 1
);
SQL Server has a dedicated BIT type that stores 0, 1, or NULL and is treated as a
boolean by tools and drivers. MySQL has no true boolean type — BOOLEAN/BOOL are
just aliases for TINYINT(1), a one-byte integer. It behaves like a boolean in practice
(most drivers map it to a language-level bool), but nothing stops someone from inserting
5 into it — MySQL won't reject it the way a strictly boolean type would.
Concatenating strings: + vs CONCAT()
SELECT FirstName + ' ' + LastName AS FullName
FROM Users;
SELECT CONCAT(first_name, ' ', last_name) AS full_name
FROM users;
⚠️ Gotcha
MySQL's + operator exists — but it means numeric addition, not string
concatenation. SELECT 'Full' + 'Name' in MySQL tries to convert both strings to
numbers (usually landing on 0) instead of throwing an error or concatenating them.
This is one of the quietest bugs a T-SQL developer can ship: it runs, it just returns nonsense.
Always use CONCAT() (or CONCAT_WS() for a separator-aware version) in MySQL.
🔧 Try It Yourself
Take a T-SQL CREATE TABLE statement you've written before — ideally one with a
name column, a flag column, and a timestamp column — and rewrite it for MySQL: backticks
instead of brackets, VARCHAR(...) CHARACTER SET utf8mb4 instead of NVARCHAR,
TINYINT(1) instead of BIT, and DATETIME instead of
DATETIME2. Then rewrite one SELECT that concatenates two columns, using
CONCAT() instead of +.
🙋 There Are No Dumb Questions
Q: If I forget the charset and just write VARCHAR(100) in MySQL, what
happens?
A: It still works — MySQL falls back to the table's (or database's) default character set. The
risk isn't a broken column, it's an inconsistent one: if your database's default charset is the
older 3-byte utf8 (a common legacy default) instead of utf8mb4, you can hit
"Incorrect string value" errors the moment someone inserts an emoji or certain Asian-language
characters. Being explicit about utf8mb4 on columns that store user input avoids that
surprise later.
✅ Quick Check
A T-SQL developer writes SELECT 'Hello' + ' World' against a MySQL database. What
happens?
Next up: once the tables exist, the next friction point is how you query them — TOP, OFFSET-FETCH, and a few string/NULL-handling functions all change names or shape.