Skip to content
IntoSQL home

Convert SQL Server to MySQL

Drop the script SSMS's Generate Scripts writes with schema and data, as .sql or .sql.gz, UTF-16 or UTF-8, and download a script for MySQL with the same tables, keys, indexes and data. Every value MySQL can't hold as it is, and every object it doesn't carry, is listed in the report.

Your file
sqlshop_2024_sqlserver.sqlSample file, 11 kB
Your database

18 rows converted, 3 fixes, none rejected

Show

Tables in shop_2024_sqlserver.sqlSQL Server dump · written by SQL Server Management Objects · utf-8

  • orders10 rows1
    11 columns
    idint not null
    customer_idint not null
    statusvarchar(10) not null
    totaldecimal(19,4) not null
    itemstinyint unsigned not null
    ordered_atdatetime not null
    shipped_atdatetime(6)
    notetext
    detailstext
    order_refchar(36) not null
    total_with_taxdecimal(12,2)
  • customers8 rows2
    8 columns
    idint not null
    namevarchar(100) not null
    emailvarchar(200) not null
    countrychar(2) not null
    is_activetinyint(1) not null
    signed_update
    created_atdatetime not null
    row_versionblob

Not converted

  • View paid_orders
  • Trigger orders_no_negative

Each database writes these in its own language. Rewrite them for MySQL.

-- Written by IntoSQL from a SQL Server dump. Load it with: mysql --default-character-set=utf8mb4 <database> < <file>
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
SET UNIQUE_CHECKS = 0;
SET SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO,ALLOW_INVALID_DATES';

CREATE TABLE `orders` (
  `id` int NOT NULL AUTO_INCREMENT,
  `customer_id` int NOT NULL,
  `status` varchar(10) NOT NULL DEFAULT 'pending',
  `total` decimal(19,4) NOT NULL,
  `items` tinyint unsigned NOT NULL DEFAULT 1,
  `ordered_at` datetime NOT NULL,
  `shipped_at` datetime(6),
  `note` text,
  `details` text,
  `order_ref` char(36) NOT NULL,
  `total_with_tax` decimal(12,2) GENERATED ALWAYS AS (cast(`total` * (1.16) AS DECIMAL(12,2))) STORED,
  PRIMARY KEY (`id`),
  KEY `idx_orders_customer` (`customer_id`),
  KEY `idx_orders_status_date` (`status`,`ordered_at`),
  CONSTRAINT `check2` CHECK (`status` = 'cancelled' OR `status` = 'shipped' OR `status` = 'paid' OR `status` = 'pending'),
  CONSTRAINT `check3` CHECK (`total` >= (0)),
  FOREIGN KEY (`customer_id`) REFERENCES `customers` (`id`)
) AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

CREATE TABLE `customers` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL,
  `email` varchar(200) NOT NULL,
  `country` char(2) NOT NULL DEFAULT 'US',
  `is_active` tinyint(1) NOT NULL DEFAULT 1,
  `signed_up` date,
  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `row_version` blob,
  PRIMARY KEY (`id`),
  UNIQUE KEY (`email`)
) AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='People who bought at least once';

INSERT INTO `customers` (`id`,`name`,`email`,`country`,`is_active`,`signed_up`,`created_at`) VALUES (1,'Ana Martínez','ana@example.com','MX',1,'2023-01-15','2023-01-15 10:24:00.000000');
INSERT INTO `customers` (`id`,`name`,`email`,`country`,`is_active`,`signed_up`,`created_at`) VALUES (2,'Zoë Müller','zoe@example.de','DE',1,'2023-02-03','2023-02-03 08:11:42.000000');
INSERT INTO `customers` (`id`,`name`,`email`,`country`,`is_active`,`signed_up`,`created_at`) VALUES (3,'José Peña','jose@example.es','ES',0,'2023-03-21','2023-03-21 19:05:17.000000');
INSERT INTO `customers` (`id`,`name`,`email`,`country`,`is_active`,`signed_up`,`created_at`) VALUES (4,'李雷','lilei@example.cn','CN',1,'2023-05-09','2023-05-09 02:40:00.000000');
INSERT INTO `customers` (`id`,`name`,`email`,`country`,`is_active`,`signed_up`,`created_at`) VALUES (5,'Noah O\'Brien','noah@example.ie','IE',1,NULL,'2023-06-30 14:00:00.000000');
INSERT INTO `customers` (`id`,`name`,`email`,`country`,`is_active`,`signed_up`,`created_at`) VALUES (6,'Amélie Laurent','amelie@example.fr','FR',1,'2024-01-02','2024-01-02 09:30:00.000000');
INSERT INTO `customers` (`id`,`name`,`email`,`country`,`is_active`,`signed_up`,`created_at`) VALUES (7,'Ravi Patel','ravi@example.in','IN',0,'2024-02-29','2024-02-29 23:59:59.000000');
INSERT INTO `customers` (`id`,`name`,`email`,`country`,`is_active`,`signed_up`,`created_at`) VALUES (8,'Sofia Rossi 🌻','sofia@example.it','IT',1,'2024-04-12','2024-04-12 12:12:12.000000');
INSERT INTO `orders` (`id`,`customer_id`,`status`,`total`,`items`,`ordered_at`,`shipped_at`,`note`,`details`,`order_ref`) VALUES (1,1,'shipped',129.9000,3,'2024-03-01T10:00:00.000000','2024-03-02T16:30:00.123456',NULL,'{"gift": false}','0b7a4c2e-3f1d-4e8a-9c55-1a2b3c4d5e6f');
INSERT INTO `orders` (`id`,`customer_id`,`status`,`total`,`items`,`ordered_at`,`shipped_at`,`note`,`details`,`order_ref`) VALUES (2,2,'paid',54.0000,1,'2024-03-02T11:15:00.000000',NULL,'Gift wrap 🎁','{"gift": true, "wrap": "red"}','1c8b5d3f-4a2e-4f9b-8d66-2b3c4d5e6f70');
INSERT INTO `orders` (`id`,`customer_id`,`status`,`total`,`items`,`ordered_at`,`shipped_at`,`note`,`details`,`order_ref`) VALUES (3,1,'cancelled',12.5000,1,'2024-03-03T09:45:00.000000',NULL,'Customer changed their mind',NULL,'2d9c6e4a-5b3f-4a0c-9e77-3c4d5e6f7081');
INSERT INTO `orders` (`id`,`customer_id`,`status`,`total`,`items`,`ordered_at`,`shipped_at`,`note`,`details`,`order_ref`) VALUES (4,4,'shipped',980.0000,12,'2024-03-05T02:10:00.000000','2024-03-07T08:00:00.000000','请小心轻放','{"fragile": true}','3e0d7f5b-6c4a-4b1d-8f88-4d5e6f708192');
INSERT INTO `orders` (`id`,`customer_id`,`status`,`total`,`items`,`ordered_at`,`shipped_at`,`note`,`details`,`order_ref`) VALUES (5,5,'pending',45.7500,2,'2024-03-08T17:20:00.000000',NULL,'Leave at the door; ring twice',NULL,'4f1e806c-7d5b-4c2e-9a99-5e6f708192a3');
INSERT INTO `orders` (`id`,`customer_id`,`status`,`total`,`items`,`ordered_at`,`shipped_at`,`note`,`details`,`order_ref`) VALUES (6,6,'shipped',310.4000,5,'2024-03-10T13:00:00.000000','2024-03-11T09:00:00.000000','Fragile: "glass"','{"fragile": true}','50f2917d-8e6c-4d3f-8baa-6f708192a3b4');
INSERT INTO `orders` (`id`,`customer_id`,`status`,`total`,`items`,`ordered_at`,`shipped_at`,`note`,`details`,`order_ref`) VALUES (7,8,'paid',76.2000,2,'2024-03-11T18:45:00.000000',NULL,NULL,NULL,'6103a28e-9f7d-4e40-9cbb-708192a3b4c5');
INSERT INTO `orders` (`id`,`customer_id`,`status`,`total`,`items`,`ordered_at`,`shipped_at`,`note`,`details`,`order_ref`) VALUES (8,2,'shipped',19.9900,1,'2024-03-12T08:00:00.000000','9999-12-31T23:59:59.999999','Shipped date set to the maximum by an old import',NULL,'7214b39f-a08e-4f51-8dcc-8192a3b4c5d6');
INSERT INTO `orders` (`id`,`customer_id`,`status`,`total`,`items`,`ordered_at`,`shipped_at`,`note`,`details`,`order_ref`) VALUES (9,3,'shipped',64.0000,2,'2024-03-14T15:30:00.000000','2024-03-15T10:00:00.000000','Two parcels','{"parcels": [1, 2]}','8325c4a0-b19f-4062-9edd-92a3b4c5d6e7');
INSERT INTO `orders` (`id`,`customer_id`,`status`,`total`,`items`,`ordered_at`,`shipped_at`,`note`,`details`,`order_ref`) VALUES (10,7,'paid',1499.0000,1,'2024-03-15T21:05:00.000000',NULL,'C:\\Users\\ravi\\invoice.pdf',NULL,'9436d5b1-c2a0-4173-8fee-a3b4c5d6e7f8');

SET UNIQUE_CHECKS = 1;
SET FOREIGN_KEY_CHECKS = 1;

Fixes

  1. "shipped_at" of orders is datetime2(7), with more digits of a second than MySQL keeps, so it is datetime(6) and the last digits are cut.
  2. "created_at" of customers holds moments with a time zone; MySQL has no type that keeps one, so they are stored in UTC as datetime.
  3. "row_version" of customers is a row version SQL Server sets by itself; the dump has no values for it, so it is blob and empty.

Needs a look

  • 6 text columns compare ignoring case in the source; MySQL compares exactly, so unique keys and searches may behave differently. Use a case-insensitive collation or column type on the columns that need it.
  • 2 views, triggers, routines or events aren't converted; the report lists each one. Recreate them by hand in MySQL.
  • The check check1 on orders can't be written for MySQL (function isjson), so it isn't created. Write the check by hand if it matters.
  • The index idx_orders_pending on orders uses an expression that can't be written for MySQL (neither MySQL 5.7 nor MariaDB index an expression), so it isn't created. Create the index by hand if queries need it.
  • The default of "order_ref" in orders can't be written for MySQL (a default expression needs MySQL 8.0.13), so the column has no default. Add the default by hand if new rows need it.

Your file is converted on this computer and never uploaded.

Other samples:

What changes from SQL Server to MySQL

Each row is a rule the converter applies, with the report line it writes when the rule changes something in your data.

  • bit

    tinyint(1)

    MySQL's BOOLEAN is TINYINT(1): 1 and 0, as bit holds them.

  • char(n), varchar(n), nchar(n), nvarchar(n), varchar(max), text

    char(n), varchar(n) up to 1024 characters, text types beyond

    Text keeps every character as utf8mb4. A column in a key is varchar(768) at most, the longest a key holds, and so is text with a default, which MySQL 5.7 gives only to varchar.

  • decimal(p,s), money, smallmoney

    decimal(p,s); money as decimal(19,4)

    decimal holds 65 digits, more than SQL Server's 38, so every value is exact.

  • datetime, datetime2, smalldatetime

    datetime(p)

    A datetime is a wall-clock reading in both.

  • datetime2(7), time(7) and datetimeoffset(7)

    six digits of a second, the seventh cut

    MySQL keeps microseconds; the seventh digit (100 nanoseconds) is cut, never rounded, so no value moves to the next second or day.

    Report line: fraction_cut

  • datetimeoffset

    datetime(p) in UTC

    MySQL's TIMESTAMP only reaches 2038, so moments are stored as datetime in UTC, the same instant whatever offset each value had.

    Report line: moments_as_utc

  • time

    time(p)

    The same times of day.

  • dates before the year 1000

    NULL

    MySQL stores years 1000 to 9999, and SQL Server goes back to the year 1. Those values become NULL and the report counts them.

    Report line: date_out_of_range

  • json (SQL Server 2025)

    json

    The same JSON values, which MySQL's JSON functions read.

  • uniqueidentifier

    char(36)

    The same identifiers, written in lowercase.

  • binary, varbinary, image

    binary(n), varbinary(n) and blob types

    Bytes stay bytes.

  • geometry, geography

    blob with SQL Server's own bytes

    SSMS writes spatial values in SQL Server's serialization, which is kept byte for byte.

    Report line: spatial_as_native

  • rowversion, timestamp

    blob, empty

    SQL Server sets a row version by itself and SSMS scripts no values for it.

    Report line: rowversion_empty

  • xml, sql_variant, hierarchyid, vector

    longtext

    MySQL has no equivalent type; xml and sql_variant keep each value's text and hierarchyid its path, such as /1/2/.

    Report line: type_replaced

  • collations that ignore case (_CI_)

    exact comparison (utf8mb4_bin)

    Tables use utf8mb4_bin so unique keys keep apart what SQL Server kept apart; columns that ignored case lose that. Accent rules differ between versions, so no _ci collation matches SQL Server's on every one.

    Report line: case_insensitive_collation

  • IDENTITY

    AUTO_INCREMENT, continuing after the highest id

    The column that leads the primary key counts; the counter continues after the highest id.

  • IDENTITY columns that don't lead the primary key

    a plain integer

    MySQL counts one column per table, at the start of a key; any other keeps its values without a counter.

    Report line: counter_dropped

  • CREATE SEQUENCE

    not created

    MySQL has no sequences. The values a sequence handed out are in the rows.

    Report line: sequence_not_converted

  • computed columns

    generated columns, STORED when PERSISTED, VIRTUAL otherwise

    MySQL computes them the same two ways, with CONCAT written so NULL counts as empty as in SQL Server.

  • computed columns with functions MySQL lacks

    a plain column

    A formula that can't be written leaves a plain column and a report line; SSMS scripts no values for computed columns.

    Report line: generated_not_translated

  • defaults with functions MySQL lacks, and NEXT VALUE FOR

    no default

    A default that can't be written is left out and reported.

    Report line: default_not_translated

  • CHECK constraints

    translated

    MySQL 5.7 keeps them without checking; the others test them as each row loads.

  • CHECK constraints with functions MySQL lacks

    not created

    A check that can't be written is left out and reported.

    Report line: check_skipped

  • CHECK constraints added WITH NOCHECK or disabled

    not created

    SQL Server doesn't apply these checks, so the rows may break them.

    Report line: check_not_enforced

  • filtered and columnstore indexes

    not created

    IntoSQL writes b-tree indexes on whole columns; create the others again after loading.

    Report line: index_skipped

  • foreign keys

    foreign keys, loaded with FOREIGN_KEY_CHECKS=0

    The rows load in any order, and the keys check new rows afterwards.

  • foreign keys to tables the dump doesn't create

    not created

    A key can't name a table that isn't there.

    Report line: foreign_key_skipped

  • tables on a partition scheme

    one table with every partition's rows

    The rows are the same; partition the table again in MySQL if it needs it.

    Report line: partitions_dropped

  • temporal tables (SYSTEM_VERSIONING)

    two tables: the current rows and the history

    The table and its history table are copied as plain tables; MySQL doesn't keep the history by itself.

    Report line: system_versioning_dropped

  • schemas

    one database, tables named after their schema first

    MySQL's schemas are databases, which a script loaded into one database can't create; tables in dbo keep their names.

    Report line: schemas_merged

  • 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

  • views, functions, procedures, triggers, synonyms, types and partition functions

    listed in the report

    Their code is written in T-SQL, so they are listed for you to recreate.

    Report line: not_converted_objects

  • scripts without CREATE TABLE (data only)

    types read from the values

    A data-only script has only rows, so the types come from the values.

    Report line: no_schema

  • expressions in INSERT values

    NULL

    A converter that doesn't run a database can't evaluate them.

    Report line: expression_value

  • types IntoSQL doesn't know

    longtext

    Text holds any value; change the type after loading.

    Report line: unknown_type

  • NUL characters in text

    kept

    The value is copied with the NUL character escaped.

  • text that isn't valid in its encoding

    the bad bytes as the replacement character

    The script is UTF-8 and every value has to be valid text.

    Report line: invalid_utf8_replaced

  • text decoded twice (ñ)

    copied as it is

    The script holds exactly these characters, so they are copied; the report points them out.

    Report line: mojibake_suspected

In SQL ServerIn MySQLReport line
bittinyint(1)MySQL's BOOLEAN is TINYINT(1): 1 and 0, as bit holds them.
char(n), varchar(n), nchar(n), nvarchar(n), varchar(max), textchar(n), varchar(n) up to 1024 characters, text types beyondText keeps every character as utf8mb4. A column in a key is varchar(768) at most, the longest a key holds, and so is text with a default, which MySQL 5.7 gives only to varchar.
decimal(p,s), money, smallmoneydecimal(p,s); money as decimal(19,4)decimal holds 65 digits, more than SQL Server's 38, so every value is exact.
datetime, datetime2, smalldatetimedatetime(p)A datetime is a wall-clock reading in both.
datetime2(7), time(7) and datetimeoffset(7)six digits of a second, the seventh cutMySQL keeps microseconds; the seventh digit (100 nanoseconds) is cut, never rounded, so no value moves to the next second or day.fraction_cut
datetimeoffsetdatetime(p) in UTCMySQL's TIMESTAMP only reaches 2038, so moments are stored as datetime in UTC, the same instant whatever offset each value had.moments_as_utc
timetime(p)The same times of day.
dates before the year 1000NULLMySQL stores years 1000 to 9999, and SQL Server goes back to the year 1. Those values become NULL and the report counts them.date_out_of_range
json (SQL Server 2025)jsonThe same JSON values, which MySQL's JSON functions read.
uniqueidentifierchar(36)The same identifiers, written in lowercase.
binary, varbinary, imagebinary(n), varbinary(n) and blob typesBytes stay bytes.
geometry, geographyblob with SQL Server's own bytesSSMS writes spatial values in SQL Server's serialization, which is kept byte for byte.spatial_as_native
rowversion, timestampblob, emptySQL Server sets a row version by itself and SSMS scripts no values for it.rowversion_empty
xml, sql_variant, hierarchyid, vectorlongtextMySQL has no equivalent type; xml and sql_variant keep each value's text and hierarchyid its path, such as /1/2/.type_replaced
collations that ignore case (_CI_)exact comparison (utf8mb4_bin)Tables use utf8mb4_bin so unique keys keep apart what SQL Server kept apart; columns that ignored case lose that. Accent rules differ between versions, so no _ci collation matches SQL Server's on every one.case_insensitive_collation
IDENTITYAUTO_INCREMENT, continuing after the highest idThe column that leads the primary key counts; the counter continues after the highest id.
IDENTITY columns that don't lead the primary keya plain integerMySQL counts one column per table, at the start of a key; any other keeps its values without a counter.counter_dropped
CREATE SEQUENCEnot createdMySQL has no sequences. The values a sequence handed out are in the rows.sequence_not_converted
computed columnsgenerated columns, STORED when PERSISTED, VIRTUAL otherwiseMySQL computes them the same two ways, with CONCAT written so NULL counts as empty as in SQL Server.
computed columns with functions MySQL lacksa plain columnA formula that can't be written leaves a plain column and a report line; SSMS scripts no values for computed columns.generated_not_translated
defaults with functions MySQL lacks, and NEXT VALUE FORno defaultA default that can't be written is left out and reported.default_not_translated
CHECK constraintstranslatedMySQL 5.7 keeps them without checking; the others test them as each row loads.
CHECK constraints with functions MySQL lacksnot createdA check that can't be written is left out and reported.check_skipped
CHECK constraints added WITH NOCHECK or disablednot createdSQL Server doesn't apply these checks, so the rows may break them.check_not_enforced
filtered and columnstore indexesnot createdIntoSQL writes b-tree indexes on whole columns; create the others again after loading.index_skipped
foreign keysforeign keys, loaded with FOREIGN_KEY_CHECKS=0The rows load in any order, and the keys check new rows afterwards.
foreign keys to tables the dump doesn't createnot createdA key can't name a table that isn't there.foreign_key_skipped
tables on a partition schemeone table with every partition's rowsThe rows are the same; partition the table again in MySQL if it needs it.partitions_dropped
temporal tables (SYSTEM_VERSIONING)two tables: the current rows and the historyThe table and its history table are copied as plain tables; MySQL doesn't keep the history by itself.system_versioning_dropped
schemasone database, tables named after their schema firstMySQL's schemas are databases, which a script loaded into one database can't create; tables in dbo keep their names.schemas_merged
NOT NULL columns with values that become NULLNOT NULL droppedA column whose values had to become NULL can't keep NOT NULL, or the load would stop.not_null_relaxed
views, functions, procedures, triggers, synonyms, types and partition functionslisted in the reportTheir code is written in T-SQL, so they are listed for you to recreate.not_converted_objects
scripts without CREATE TABLE (data only)types read from the valuesA data-only script has only rows, so the types come from the values.no_schema
expressions in INSERT valuesNULLA converter that doesn't run a database can't evaluate them.expression_value
types IntoSQL doesn't knowlongtextText holds any value; change the type after loading.unknown_type
NUL characters in textkeptThe value is copied with the NUL character escaped.
text that isn't valid in its encodingthe bad bytes as the replacement characterThe script is UTF-8 and every value has to be valid text.invalid_utf8_replaced
text decoded twice (ñ)copied as it isThe script holds exactly these characters, so they are copied; the report points them out.mojibake_suspected

Load the script into MySQL

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.

mysql --default-character-set=utf8mb4 mydb < sql_server.mysql.sql

Foreign keys are created as the dump declares them, with FOREIGN_KEY_CHECKS=0 while the rows load, so a row that points to a missing row doesn't stop the load.

For a large dump, run the client on the same machine as the server, and raise max_allowed_packet if a single row is very large.

IntoSQL Pro loads the file straight into MySQL from the desktop app or the command line, without writing a script.

Dump SQL Server for the best result

The converter reads what the dump holds. These options keep more of your data in it:

  • In SSMS, right-click the database, Tasks, Generate Scripts, and pick the tables. dbatools, mssql-scripter and other tools built on the same library write the same script.
  • Under Advanced, set Types of data to script to Schema and data. A script of the schema alone has tables without rows.
  • Save it as Unicode, SSMS's default, or as UTF-8: both are read the same way, and gzip works too.

What isn't converted

Views, functions, procedures, triggers, synonyms, rules and security policies are written in T-SQL. They are listed in the report by name, never dropped silently, so you can rewrite them for MySQL.

Questions about SQL Server to MySQL

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 the UTF-16 file SSMS saves?

Yes. SSMS saves scripts as UTF-16 by default, and the converter reads UTF-16 and UTF-8 alike, gzipped or not, with GO between batches.

Which SQL Server versions can the script come from?

Scripts that SSMS, dbatools or mssql-scripter write from SQL Server 2017 on. Every test script comes from SQL Server 2017, 2022 and 2025 and is compared with its source cell by cell.

Are IDENTITY values kept?

Yes, for one column per table: the IDENTITY column that leads the primary key becomes AUTO_INCREMENT and continues after the highest id. Sequences aren't created, because MySQL has none, and the report says so.

What happens to money, datetimeoffset and other SQL Server types?

bit becomes tinyint(1), money decimal(19,4), uniqueidentifier char(36), and datetimeoffset datetime in UTC. Schemas become table name prefixes in one database. Columns that ignored case compare exactly, and the report says which ones.

What about procedures, triggers and views?

They are listed in the report by name and not converted, because each database writes them in its own language.

Which MySQL versions does the script load on?

MySQL 5.7 and later. Every test script loads on 5.7 and 9, stopping at the first error if anything goes wrong.