Convert MySQL to SQL Server
Drop a dump from mysqldump, mariadb-dump, mysqlpump, phpMyAdmin or Adminer, as .sql or .sql.gz, and download a script for SQL Server with the same tables, keys, indexes and data. Every value SQL Server can't hold as it is, and every object it doesn't carry, is listed in the report.
18 rows converted, 10 fixes, none rejected
Tables in shop_2024.sqlMySQL 8.0.46 dump · written by mysqldump · utf8mb4
customers8 rows4
7 columns id bigint not null name nvarchar(200) not null email nvarchar(150) not null country nvarchar(2) not null is_active bit not null signed_up date created_at datetimeoffset(0) not null orders10 rows6
9 columns id bigint not null customer_id bigint not null status nvarchar(9) not null total decimal(10,2) not null items int not null ordered_at datetime2(0) not null shipped_at datetime2(0) note nvarchar(510) updated_at datetimeoffset(0) not null
-- Written by IntoSQL from a MySQL dump. Load it with: sqlcmd -b -f 65001 -d <database> -i <file>
SET NOCOUNT ON;
SET ANSI_NULLS ON;
SET QUOTED_IDENTIFIER ON;
SET ANSI_PADDING ON;
SET ANSI_WARNINGS ON;
SET ARITHABORT ON;
SET CONCAT_NULL_YIELDS_NULL ON;
SET NUMERIC_ROUNDABORT OFF;
GO
CREATE TABLE [dbo].[customers] (
[id] bigint IDENTITY(1001, 1) NOT NULL,
[name] nvarchar(200) NOT NULL,
[email] nvarchar(150) NOT NULL,
[country] nvarchar(2) DEFAULT N'US' NOT NULL,
[is_active] bit DEFAULT 1 NOT NULL,
[signed_up] date NULL,
[created_at] datetimeoffset(0) DEFAULT SYSDATETIMEOFFSET() NOT NULL,
CONSTRAINT [CK_customers_id] CHECK ([id] >= 0),
CONSTRAINT [PK_customers] PRIMARY KEY ([id])
);
GO
CREATE TABLE [dbo].[orders] (
[id] bigint IDENTITY(5001, 1) NOT NULL,
[customer_id] bigint NOT NULL,
[status] nvarchar(9) DEFAULT N'pending' NOT NULL,
[total] decimal(10,2) NOT NULL,
[items] int DEFAULT 1 NOT NULL,
[ordered_at] datetime2(0) NOT NULL,
[shipped_at] datetime2(0) NULL,
[note] nvarchar(510) NULL,
[updated_at] datetimeoffset(0) DEFAULT SYSDATETIMEOFFSET() NOT NULL,
CONSTRAINT [CK_orders_id] CHECK ([id] >= 0),
CONSTRAINT [CK_orders_customer_id] CHECK ([customer_id] >= 0),
CONSTRAINT [CK_orders_status] CHECK ([status] IN (N'pending', N'paid', N'shipped', N'cancelled', N'')),
CONSTRAINT [CK_orders_items] CHECK ([items] >= 0),
CONSTRAINT [PK_orders] PRIMARY KEY ([id])
);
GO
SET IDENTITY_INSERT [dbo].[customers] ON;
INSERT INTO [dbo].[customers] ([id], [name], [email], [country], [is_active], [signed_up], [created_at]) VALUES
(1,N'Ana Martínez',N'ana@example.com',N'MX',1,'2023-01-15','2023-01-15 10:24:00+00:00'),
(2,N'Zoë Müller',N'zoe@example.de',N'DE',1,'2023-02-03','2023-02-03 08:11:42+00:00'),
(3,N'José Peña',N'jose@example.es',N'ES',0,'2023-03-21','2023-03-21 19:05:17+00:00'),
(4,N'李雷',N'lilei@example.cn',N'CN',1,'2023-05-09','2023-05-09 02:40:00+00:00'),
(5,N'Noah O''Brien',N'noah@example.ie',N'IE',1,NULL,'2023-06-30 14:00:00+00:00'),
(6,N'Amélie Laurent',N'amelie@example.fr',N'FR',1,'2024-01-02','2024-01-02 09:30:00+00:00'),
(7,N'Ravi Patel',N'ravi@example.in',N'IN',0,'2024-02-29','2024-02-29 23:59:59+00:00'),
(8,N'Sofia Rossi 🌻',N'sofia@example.it',N'IT',1,'2024-04-12','2024-04-12 12:12:12+00:00');
SET IDENTITY_INSERT [dbo].[customers] OFF;
GO
SET IDENTITY_INSERT [dbo].[orders] ON;
INSERT INTO [dbo].[orders] ([id], [customer_id], [status], [total], [items], [ordered_at], [shipped_at], [note], [updated_at]) VALUES
(1,1,N'shipped',129.90,3,'2024-03-01 10:00:00','2024-03-02 16:30:00',NULL,'2024-03-02 16:30:00+00:00'),
(2,2,N'paid',54.00,1,'2024-03-02 11:15:00',NULL,N'Gift wrap 🎁','2024-03-02 11:20:00+00:00'),
(3,1,N'cancelled',12.50,1,'2024-03-03 09:45:00',NULL,N'Customer changed their mind','2024-03-03 10:00:00+00:00'),
(4,4,N'shipped',980.00,12,'2024-03-05 02:10:00','2024-03-07 08:00:00',N'请小心轻放','2024-03-07 08:00:00+00:00'),
(5,5,N'pending',45.75,2,'2024-03-08 17:20:00',NULL,N'Leave at the door; ring twice','2024-03-08 17:20:00+00:00'),
(6,6,N'shipped',310.40,5,'2024-03-10 13:00:00','2024-03-11 09:00:00',N'Fragile: "glass"','2024-03-11 09:00:00+00:00'),
(7,8,N'paid',76.20,2,'2024-03-11 18:45:00',NULL,NULL,'2024-03-11 18:46:00+00:00'),
(8,2,N'',19.99,1,'2024-03-12 08:00:00',NULL,N'Status lost in an old import','2024-03-12 08:00:00+00:00'),
(9,3,N'shipped',64.00,2,'2024-03-14 15:30:00',NULL,N'Shipping date never recorded','2024-03-15 10:00:00+00:00'),
(10,7,N'paid',1499.00,1,'2024-03-15 21:05:00',NULL,N'C:\Users\ravi\invoice.pdf','2024-03-15 21:05:00+00:00');
SET IDENTITY_INSERT [dbo].[orders] OFF;
GO
-- Indexes, foreign keys, counters and triggers, after the rows.
CREATE UNIQUE INDEX [uq_email] ON [dbo].[customers] ([email]);
CREATE INDEX [idx_customer] ON [dbo].[orders] ([customer_id]);
CREATE INDEX [idx_status_date] ON [dbo].[orders] ([status], [ordered_at]);
ALTER TABLE [dbo].[orders] WITH NOCHECK ADD CONSTRAINT [fk_orders_customer] FOREIGN KEY ([customer_id]) REFERENCES [dbo].[customers] ([id]);
BEGIN TRY ALTER TABLE [dbo].[orders] WITH CHECK CHECK CONSTRAINT [fk_orders_customer]; END TRY BEGIN CATCH PRINT N'Foreign key ' + N'[fk_orders_customer]' + N' has rows without a parent: it stays unchecked, and new rows are checked.'; END CATCH;
DECLARE @next decimal(38,0) = (SELECT MAX([id]) FROM [dbo].[customers]); IF @next IS NOT NULL BEGIN IF @next < 1000 SET @next = 1000; DBCC CHECKIDENT (N'[dbo].[customers]', RESEED, @next) WITH NO_INFOMSGS; END;
GO
EXEC sys.sp_addextendedproperty @name = N'MS_Description', @value = N'People who bought at least once', @level0type = N'SCHEMA', @level0name = N'dbo', @level1type = N'TABLE', @level1name = N'customers';
DECLARE @next decimal(38,0) = (SELECT MAX([id]) FROM [dbo].[orders]); IF @next IS NOT NULL BEGIN IF @next < 5000 SET @next = 5000; DBCC CHECKIDENT (N'[dbo].[orders]', RESEED, @next) WITH NO_INFOMSGS; END;
GO
GO
CREATE TRIGGER [dbo].[TR_orders_updated_at_on_update] ON [dbo].[orders] AFTER UPDATE AS
BEGIN
SET NOCOUNT ON;
IF NOT UPDATE([updated_at])
UPDATE x SET [updated_at] = SYSDATETIMEOFFSET() FROM [dbo].[orders] AS x JOIN inserted AS i ON x.[id] = i.[id];
END;
GO
GO
Fixes
- "id" of customers is unsigned, so it became bigint with a check that it isn't negative.
- "name" of customers holds characters SQL Server counts twice, such as emoji, so it is nvarchar(200).
- "signed_up" of customers allows NULL now: some of its values couldn't be stored and are NULL.
- The default of "signed_up" in customers, 0000‑00‑00, isn't a date SQL Server accepts, so the column has no default.
- "customer_id" of orders is unsigned, so it became bigint with a check that it isn't negative.
- "id" of orders is unsigned, so it became bigint with a check that it isn't negative.
- "items" of orders is unsigned, so it became int with a check that it isn't negative.
- "note" of orders holds characters SQL Server counts twice, such as emoji, so it is nvarchar(510).
- 1 row of "status" in orders holds the empty value MySQL stores for invalid entries; the check of its values allows it.
- "updated_at" of orders changes to the current time on every update; a trigger does that in SQL Server.
Needs a look
- 1 value in "signed_up" of customers is a zero or impossible date such as 0000‑00‑00, which SQL Server can't store, so it is NULL. If those dates mean something, replace them in the source with real ones before dumping.
- 1 value in "shipped_at" of orders is a zero or impossible date such as 0000‑00‑00 00:00:00, which SQL Server can't store, so it is NULL. If those dates mean something, replace them in the source with real ones before dumping.
Your file is converted on this computer and never uploaded.
Other samples:
What changes from MySQL to SQL Server
Each row is a rule the converter applies, with the report line it writes when the rule changes something in your data.
TINYINT(1), BOOL
bit when every value is 0 or 1, else smallint
MySQL's BOOL is a TINYINT(1) that can hold any number from -128 to 127. A column that only holds 0 and 1 becomes bit; one that holds 2 or -1 stays a number so no value changes meaning.
Report line: bool_kept_numeric
UNSIGNED integers
TINYINT UNSIGNED as tinyint; the others the next wider integer with a CHECK that it isn't negative, BIGINT UNSIGNED as bigint when every value fits, else decimal(38,0)
SQL Server has no unsigned integers except tinyint, which is 0 to 255. A wider type holds the whole unsigned range, and the check keeps negative values out as MySQL did.
Report line: unsigned_widened
ZEROFILL and display widths
dropped
Zero padding is a way of showing a number, not part of it. The numbers are kept; format them when you read them if the padding matters.
Report line: zerofill_dropped
FLOAT(M,D)
real or float, without the scale
SQL Server floats have no fixed scale. The stored values are copied as they are.
Report line: float_scale_dropped
DECIMAL(p,s)
decimal(p,s) up to 38 digits; nvarchar beyond
SQL Server's decimal holds 38 digits. Wider decimals are kept as text so every digit stays exact.
Report line: decimal_as_text
BIT(1), BIT(n)
bit; bigint up to 63 bits; decimal(20,0) for BIT(64)
A single bit is a yes or no; wider bit fields are stored as the number their bits make.
zero, partial or impossible dates (0000-00-00, 2024-02-30)
NULL
SQL Server only stores real calendar dates. MySQL keeps zero and impossible dates when its SQL mode allows them; they become NULL and the report counts them.
Report line: invalid_date_null
NOT NULL columns with values that become NULL
NOT NULL dropped
A column whose values had to become NULL can't keep NOT NULL, or the load would stop.
Report line: not_null_relaxed
DEFAULT '0000-00-00'
no default
A default SQL Server can't store is left out rather than replaced with a made-up date.
Report line: zero_date_default
TIME
time when every value is within a day, else nvarchar
MySQL's TIME is a duration from -838:59:59 to 838:59:59; SQL Server's time is a time of day. A column with longer or negative values keeps them as text.
Report line: time_as_interval
YEAR
smallint with a CHECK of its range
SQL Server has no year type; the check keeps the same values MySQL allows.
CHAR(n), VARCHAR(n)
nvarchar(n)
nvarchar holds every character on every supported version, and MySQL returns CHAR values without trailing spaces, as nvarchar keeps them.
text with characters past U+FFFF (emoji) in VARCHAR(n)
nvarchar(2n), or nvarchar(max) past 4000
nvarchar counts UTF-16 units and an emoji takes two, so the column gets room for every value.
Report line: text_length_widened
DATETIME
datetime2
A DATETIME is a wall-clock reading without a time zone, like datetime2.
TIMESTAMP
datetimeoffset in the time zone the dump states, UTC when it states none
A TIMESTAMP is a moment. Dumps state the zone they wrote it in (mysqldump writes UTC); without that, the values are read as UTC and the report says so.
Report line: timestamp_zone_assumed
ENUM
nvarchar with a CHECK of its values
Text with a check keeps the same values.
ENUM holding '' (MySQL's error value)
the empty value allowed by the check
MySQL stores an empty string when an invalid value goes into an ENUM in a lenient SQL mode. Those rows are kept and the check allows them.
Report line: enum_invalid_value
ENUM holding values outside its list
no CHECK
Values the list doesn't name would fail the check, so the check isn't created.
Report line: enum_check_skipped
SET
nvarchar as MySQL writes it ('a,b')
SQL Server has no arrays; the text is the one MySQL returns.
JSON
nvarchar(max)
SQL Server's JSON functions read JSON from text. An ISJSON check would refuse scalar values such as 1 or "x" before SQL Server 2022, which MySQL keeps.
NUL bytes in text
kept
nvarchar can hold a NUL character, so the value is copied as it is.
text that isn't valid in its character set
the bad bytes as the replacement character
Text has to be read as characters to be written as nvarchar.
Report line: invalid_utf8_replaced
text decoded twice (ñ)
copied as it is
The dump holds exactly these characters, so they are copied; the report points them out.
Report line: mojibake_suspected
AUTO_INCREMENT
IDENTITY, with the counter set past the copied ids
The copied ids load with IDENTITY_INSERT, and the counter continues after the highest one or the dump's AUTO_INCREMENT, whichever is higher.
MyISAM AUTO_INCREMENT on the second column of a key
one counter for the table
MyISAM counts per group of the key's first column; SQL Server has one counter per table.
Report line: myisam_group_autoincrement
ON UPDATE CURRENT_TIMESTAMP
an AFTER UPDATE trigger
The trigger sets the column to the current time when an update doesn't set it. It joins on the primary key, so the table needs one.
Report line: on_update_trigger
ON UPDATE CURRENT_TIMESTAMP on a table without a primary key
not carried
The trigger finds the updated rows by their primary key; without one it can't.
Report line: on_update_without_key
generated columns
computed columns, PERSISTED when the source stores them
SQL Server computes them the same two ways.
generated columns with functions SQL Server lacks
a plain column
A formula that can't be written for SQL Server leaves a plain column and a report line.
Report line: generated_not_translated
INVISIBLE columns
regular columns
SQL Server has no invisible columns.
Report line: invisible_column
CHECK constraints
translated, inline in CREATE TABLE
Inline checks are tested as each row loads.
CHECK constraints with functions SQL Server lacks
not created
A check that can't be written for SQL Server is left out and reported.
Report line: check_skipped
CHECK ... NOT ENFORCED
not created
MySQL keeps these checks without applying them, so the rows may break them.
Report line: check_not_enforced
defaults with functions SQL Server lacks
no default
A default that can't be written for SQL Server is left out and reported.
Report line: default_not_translated
keys and indexes on a prefix, c(20)
an index on the whole column
SQL Server indexes whole columns; a key unique on a prefix is unique on the whole column too, so every row loads.
Report line: prefix_whole_column
FULLTEXT indexes
not created
SQL Server's full-text search needs a catalog and its own queries.
Report line: fulltext_not_created
SPATIAL indexes
not created
A spatial index on a geometry column needs a bounding box the dump doesn't state.
Report line: spatial_index_not_created
functional indexes, and indexes on columns of unlimited length
not created
SQL Server indexes neither expressions nor nvarchar(max) and varbinary(max) columns.
Report line: index_skipped
foreign keys
after the data WITH NOCHECK, then checked with a warning on orphans
Dumps load with FOREIGN_KEY_CHECKS=0 and MyISAM never checks, so rows without a parent are common. The key is created first and checked last; orphans leave it unchecked with a warning instead of stopping the load.
ON DELETE or ON UPDATE that cascades in a cycle or along a second path
NO ACTION
SQL Server refuses cascades that could reach a table twice; MySQL allows them.
Report line: foreign_key_action_dropped
foreign keys to columns without a unique key
not created
SQL Server needs a primary key or a unique index without NULLs on the referenced columns; InnoDB doesn't.
Report line: foreign_key_skipped
table and column names
as written; renamed when they differ only in case
SQL Server names ignore case under the usual collation.
Report line: identifier_renamed
names over 116 characters
shortened
SQL Server names hold 128 characters, some of them for the numbers that keep names apart.
Report line: name_shortened
GEOMETRY, POINT, POLYGON...
geometry, with each value's SRID
SQL Server's own geometry type keeps every shape and its spatial reference system.
INET4, INET6, UUID (MariaDB)
nvarchar(39), uniqueidentifier
uniqueidentifier is SQL Server's UUID; addresses are kept as text.
BINARY, VARBINARY, BLOB
binary, varbinary, varbinary(max)
Bytes stay bytes.
PARTITION BY
one table
The rows are the same; partition the table again in SQL Server if it needs it.
Report line: partitions_dropped
WITH SYSTEM VERSIONING (MariaDB)
a table with the current rows
A dump holds the current rows only.
Report line: system_versioning_dropped
MyISAM, MEMORY and other engines
a regular table
SQL Server has one table engine.
Report line: engine_dropped
views, triggers, procedures, functions and events
listed in the report
Their code is written in MySQL's language, so they are listed for you to recreate.
Report line: not_converted_objects
dumps without CREATE TABLE
types read from the values
A dump made with --no-create-info only has rows, so the types come from the values.
Report line: no_schema
geometries in a dump without CREATE TABLE
geometry
MySQL's geometry format is recognised from the values.
_ci collations
the database's collation, usually case-insensitive
SQL Server compares text without case under its usual collation, as MySQL's _ci does.
SET NAMES utf8 from utf8mb4 tables
converted, with a warning
MySQL 5.x mysqldump writes 3-byte utf8 by default and replaces every emoji with a question mark before any converter sees the file.
Report line: dump_lost_four_byte_chars
dumps without SET NAMES whose text isn't UTF-8
read as Latin-1 (Windows-1252)
MySQL's latin1 is Windows-1252, the usual encoding of such dumps.
Report line: charset_guessed
sjis, cp932, gbk, big5 dumps
read as UTF-8, with a warning
These encodings are not decoded yet.
Report line: unsupported_charset
UNHEX('..'), CONVERT(b'..', UNSIGNED), CONVERT(".." USING x), ST_GeomFromText('..') in rows
evaluated
Some dump tools write values as these calls; they are read as the values they produce, with geographic WKT turned to longitude first.
other expressions in rows
NULL
A converter that doesn't run a database can't evaluate them.
Report line: expression_value
DEFAULT in VALUES
the column's default value
A literal default is copied into the row.
Report line: default_keyword_filled
DEFAULT in VALUES without a literal default
NULL
There is no value to copy.
Report line: default_keyword_null
REPLACE and INSERT IGNORE
plain rows
A dump has one row per key, so the rows are the same.
Report line: replace_as_insert
types IntoSQL doesn't know
nvarchar(max)
Text holds any value; change the type after loading.
Report line: unknown_type
values that don't fit their column
NULL
A damaged or hand-written dump can hold values its column can't; they become NULL so the rest loads.
Report line: invalid_value_null
unreadable geometries
NULL
Bytes that aren't a geometry can't be stored as one.
Report line: bad_geometry
CREATE SEQUENCE (MariaDB)
CREATE SEQUENCE ... AS bigint, at its next value
SQL Server has sequences with the same options; each one continues where the dump left it.
VECTOR(n) (MariaDB 11.7+)
nvarchar(max) with the floats as a JSON array
Only SQL Server 2025 has a vector type; a JSON array keeps every float on every version.
Report line: type_replaced
| In MySQL | In SQL Server | Report line |
|---|---|---|
| TINYINT(1), BOOL | bit when every value is 0 or 1, else smallintMySQL's BOOL is a TINYINT(1) that can hold any number from -128 to 127. A column that only holds 0 and 1 becomes bit; one that holds 2 or -1 stays a number so no value changes meaning. | bool_kept_numeric |
| UNSIGNED integers | TINYINT UNSIGNED as tinyint; the others the next wider integer with a CHECK that it isn't negative, BIGINT UNSIGNED as bigint when every value fits, else decimal(38,0)SQL Server has no unsigned integers except tinyint, which is 0 to 255. A wider type holds the whole unsigned range, and the check keeps negative values out as MySQL did. | unsigned_widened |
| ZEROFILL and display widths | droppedZero padding is a way of showing a number, not part of it. The numbers are kept; format them when you read them if the padding matters. | zerofill_dropped |
| FLOAT(M,D) | real or float, without the scaleSQL Server floats have no fixed scale. The stored values are copied as they are. | float_scale_dropped |
| DECIMAL(p,s) | decimal(p,s) up to 38 digits; nvarchar beyondSQL Server's decimal holds 38 digits. Wider decimals are kept as text so every digit stays exact. | decimal_as_text |
| BIT(1), BIT(n) | bit; bigint up to 63 bits; decimal(20,0) for BIT(64)A single bit is a yes or no; wider bit fields are stored as the number their bits make. | |
| zero, partial or impossible dates (0000-00-00, 2024-02-30) | NULLSQL Server only stores real calendar dates. MySQL keeps zero and impossible dates when its SQL mode allows them; they become NULL and the report counts them. | invalid_date_null |
| NOT NULL columns with values that become NULL | NOT NULL droppedA column whose values had to become NULL can't keep NOT NULL, or the load would stop. | not_null_relaxed |
| DEFAULT '0000-00-00' | no defaultA default SQL Server can't store is left out rather than replaced with a made-up date. | zero_date_default |
| TIME | time when every value is within a day, else nvarcharMySQL's TIME is a duration from -838:59:59 to 838:59:59; SQL Server's time is a time of day. A column with longer or negative values keeps them as text. | time_as_interval |
| YEAR | smallint with a CHECK of its rangeSQL Server has no year type; the check keeps the same values MySQL allows. | |
| CHAR(n), VARCHAR(n) | nvarchar(n)nvarchar holds every character on every supported version, and MySQL returns CHAR values without trailing spaces, as nvarchar keeps them. | |
| text with characters past U+FFFF (emoji) in VARCHAR(n) | nvarchar(2n), or nvarchar(max) past 4000nvarchar counts UTF-16 units and an emoji takes two, so the column gets room for every value. | text_length_widened |
| DATETIME | datetime2A DATETIME is a wall-clock reading without a time zone, like datetime2. | |
| TIMESTAMP | datetimeoffset in the time zone the dump states, UTC when it states noneA TIMESTAMP is a moment. Dumps state the zone they wrote it in (mysqldump writes UTC); without that, the values are read as UTC and the report says so. | timestamp_zone_assumed |
| ENUM | nvarchar with a CHECK of its valuesText with a check keeps the same values. | |
| ENUM holding '' (MySQL's error value) | the empty value allowed by the checkMySQL stores an empty string when an invalid value goes into an ENUM in a lenient SQL mode. Those rows are kept and the check allows them. | enum_invalid_value |
| ENUM holding values outside its list | no CHECKValues the list doesn't name would fail the check, so the check isn't created. | enum_check_skipped |
| SET | nvarchar as MySQL writes it ('a,b')SQL Server has no arrays; the text is the one MySQL returns. | |
| JSON | nvarchar(max)SQL Server's JSON functions read JSON from text. An ISJSON check would refuse scalar values such as 1 or "x" before SQL Server 2022, which MySQL keeps. | |
| NUL bytes in text | keptnvarchar can hold a NUL character, so the value is copied as it is. | |
| text that isn't valid in its character set | the bad bytes as the replacement characterText has to be read as characters to be written as nvarchar. | invalid_utf8_replaced |
| text decoded twice (ñ) | copied as it isThe dump holds exactly these characters, so they are copied; the report points them out. | mojibake_suspected |
| AUTO_INCREMENT | IDENTITY, with the counter set past the copied idsThe copied ids load with IDENTITY_INSERT, and the counter continues after the highest one or the dump's AUTO_INCREMENT, whichever is higher. | |
| MyISAM AUTO_INCREMENT on the second column of a key | one counter for the tableMyISAM counts per group of the key's first column; SQL Server has one counter per table. | myisam_group_autoincrement |
| ON UPDATE CURRENT_TIMESTAMP | an AFTER UPDATE triggerThe trigger sets the column to the current time when an update doesn't set it. It joins on the primary key, so the table needs one. | on_update_trigger |
| ON UPDATE CURRENT_TIMESTAMP on a table without a primary key | not carriedThe trigger finds the updated rows by their primary key; without one it can't. | on_update_without_key |
| generated columns | computed columns, PERSISTED when the source stores themSQL Server computes them the same two ways. | |
| generated columns with functions SQL Server lacks | a plain columnA formula that can't be written for SQL Server leaves a plain column and a report line. | generated_not_translated |
| INVISIBLE columns | regular columnsSQL Server has no invisible columns. | invisible_column |
| CHECK constraints | translated, inline in CREATE TABLEInline checks are tested as each row loads. | |
| CHECK constraints with functions SQL Server lacks | not createdA check that can't be written for SQL Server is left out and reported. | check_skipped |
| CHECK ... NOT ENFORCED | not createdMySQL keeps these checks without applying them, so the rows may break them. | check_not_enforced |
| defaults with functions SQL Server lacks | no defaultA default that can't be written for SQL Server is left out and reported. | default_not_translated |
| keys and indexes on a prefix, c(20) | an index on the whole columnSQL Server indexes whole columns; a key unique on a prefix is unique on the whole column too, so every row loads. | prefix_whole_column |
| FULLTEXT indexes | not createdSQL Server's full-text search needs a catalog and its own queries. | fulltext_not_created |
| SPATIAL indexes | not createdA spatial index on a geometry column needs a bounding box the dump doesn't state. | spatial_index_not_created |
| functional indexes, and indexes on columns of unlimited length | not createdSQL Server indexes neither expressions nor nvarchar(max) and varbinary(max) columns. | index_skipped |
| foreign keys | after the data WITH NOCHECK, then checked with a warning on orphansDumps load with FOREIGN_KEY_CHECKS=0 and MyISAM never checks, so rows without a parent are common. The key is created first and checked last; orphans leave it unchecked with a warning instead of stopping the load. | |
| ON DELETE or ON UPDATE that cascades in a cycle or along a second path | NO ACTIONSQL Server refuses cascades that could reach a table twice; MySQL allows them. | foreign_key_action_dropped |
| foreign keys to columns without a unique key | not createdSQL Server needs a primary key or a unique index without NULLs on the referenced columns; InnoDB doesn't. | foreign_key_skipped |
| table and column names | as written; renamed when they differ only in caseSQL Server names ignore case under the usual collation. | identifier_renamed |
| names over 116 characters | shortenedSQL Server names hold 128 characters, some of them for the numbers that keep names apart. | name_shortened |
| GEOMETRY, POINT, POLYGON... | geometry, with each value's SRIDSQL Server's own geometry type keeps every shape and its spatial reference system. | |
| INET4, INET6, UUID (MariaDB) | nvarchar(39), uniqueidentifieruniqueidentifier is SQL Server's UUID; addresses are kept as text. | |
| BINARY, VARBINARY, BLOB | binary, varbinary, varbinary(max)Bytes stay bytes. | |
| PARTITION BY | one tableThe rows are the same; partition the table again in SQL Server if it needs it. | partitions_dropped |
| WITH SYSTEM VERSIONING (MariaDB) | a table with the current rowsA dump holds the current rows only. | system_versioning_dropped |
| MyISAM, MEMORY and other engines | a regular tableSQL Server has one table engine. | engine_dropped |
| views, triggers, procedures, functions and events | listed in the reportTheir code is written in MySQL's language, so they are listed for you to recreate. | not_converted_objects |
| dumps without CREATE TABLE | types read from the valuesA dump made with --no-create-info only has rows, so the types come from the values. | no_schema |
| geometries in a dump without CREATE TABLE | geometryMySQL's geometry format is recognised from the values. | |
| _ci collations | the database's collation, usually case-insensitiveSQL Server compares text without case under its usual collation, as MySQL's _ci does. | |
| SET NAMES utf8 from utf8mb4 tables | converted, with a warningMySQL 5.x mysqldump writes 3-byte utf8 by default and replaces every emoji with a question mark before any converter sees the file. | dump_lost_four_byte_chars |
| dumps without SET NAMES whose text isn't UTF-8 | read as Latin-1 (Windows-1252)MySQL's latin1 is Windows-1252, the usual encoding of such dumps. | charset_guessed |
| sjis, cp932, gbk, big5 dumps | read as UTF-8, with a warningThese encodings are not decoded yet. | unsupported_charset |
| UNHEX('..'), CONVERT(b'..', UNSIGNED), CONVERT(".." USING x), ST_GeomFromText('..') in rows | evaluatedSome dump tools write values as these calls; they are read as the values they produce, with geographic WKT turned to longitude first. | |
| other expressions in rows | NULLA converter that doesn't run a database can't evaluate them. | expression_value |
| DEFAULT in VALUES | the column's default valueA literal default is copied into the row. | default_keyword_filled |
| DEFAULT in VALUES without a literal default | NULLThere is no value to copy. | default_keyword_null |
| REPLACE and INSERT IGNORE | plain rowsA dump has one row per key, so the rows are the same. | replace_as_insert |
| types IntoSQL doesn't know | nvarchar(max)Text holds any value; change the type after loading. | unknown_type |
| values that don't fit their column | NULLA damaged or hand-written dump can hold values its column can't; they become NULL so the rest loads. | invalid_value_null |
| unreadable geometries | NULLBytes that aren't a geometry can't be stored as one. | bad_geometry |
| CREATE SEQUENCE (MariaDB) | CREATE SEQUENCE ... AS bigint, at its next valueSQL Server has sequences with the same options; each one continues where the dump left it. | |
| VECTOR(n) (MariaDB 11.7+) | nvarchar(max) with the floats as a JSON arrayOnly SQL Server 2025 has a vector type; a JSON array keeps every float on every version. | type_replaced |
Load the script into SQL Server
The script creates the tables, copies the rows, then adds keys, indexes and foreign keys, so it loads fast and stops at the first error.
sqlcmd -b -f 65001 -S localhost -d mydb -i mysql.sql_server.sqlForeign keys are added WITH NOCHECK and then checked. If a row points to a row that doesn't exist, the script says which constraint and leaves it unchecked, instead of failing half way.
For a large dump, run sqlcmd on the same machine as the server. Each batch of up to 1000 rows commits on its own, so the transaction log stays small.
Dump MySQL for the best result
The converter reads what the dump holds. These options keep more of your data in it:
- --default-character-set=utf8mb4 keeps emoji and other 4-byte characters, which a utf8 dump loses before the converter sees it.
- --hex-blob writes binary columns as hexadecimal, which survives copying between systems.
- --routines --triggers --events add those objects to the dump, so the report can list them.
What isn't converted
Views, triggers, stored procedures, functions and events are written in each database's own language. They are listed in the report by name, never dropped silently, so you can rewrite them for SQL Server.
Questions about MySQL to SQL Server
Not answered here? Write to us from the contact page and a person replies. Contact page
Is my dump uploaded?
No. The converter runs in your browser and reads the dump from your disk in pieces, so it never leaves your computer. The desktop app and the command line convert on your computer too.
How large can the dump be?
The converter never holds the whole dump in memory: it reads the file twice, by ranges, and writes the script as it goes. Multi-gigabyte dumps convert in the browser, in the desktop app and with the command line.
Does it read MariaDB dumps?
Yes. Dumps from mariadb-dump and from MariaDB 10.3 to 12 convert the same way, including MariaDB's INET6, INET4 and UUID columns.
What happens to 0000-00-00 dates?
SQL Server has no zero date, so they become NULL and the column accepts NULL. The report says how many there were in each column.
Are AUTO_INCREMENT values kept?
Yes. Each AUTO_INCREMENT column becomes an IDENTITY column, every row keeps its id, and the next id continues after the highest one, or after the table's AUTO_INCREMENT if that is higher.
Does TINYINT(1) become boolean?
It becomes bit only when every value in the column is 0 or 1. If it holds other numbers, such as 2 or -1, it stays a number and the report says why, so no value turns into 1 by accident.
What about stored procedures and triggers?
They are listed in the report by name and not converted, because each database writes them in its own language. ON UPDATE CURRENT_TIMESTAMP is the exception: it becomes a SQL Server trigger.
Which SQL Server versions does the script load on?
SQL Server 2017 to 2025. The same script loads on each of them, stopping at the first error if anything goes wrong.