

# Migrating T-SQL features
<a name="chap-sql-server-aurora-mysql.tsql"></a>

This chapter provides reference information for T-SQL extensions required to migrate from Microsoft SQL Server 2019 to Amazon Aurora MySQL. You can gain a comprehensive understanding of the differences and similarities between these two database systems across multiple areas, including collation and character sets, cursors, date and time functions, string functions, database and schema concepts, transaction handling, SQL syntax, stored procedures, error handling, flow control, full-text search, graph databases, XML and JSON support, and more.

**Topics**
+ [

# Collations for T-SQL
](chap-sql-server-aurora-mysql.tsql.collations.md)
+ [

# Cursors for T-SQL
](chap-sql-server-aurora-mysql.tsql.cursors.md)
+ [

# Date and time functions for T-SQL
](chap-sql-server-aurora-mysql.tsql.datetime.md)
+ [

# String functions for T-SQL
](chap-sql-server-aurora-mysql.tsql.stringfunctions.md)
+ [

# Databases and schemas for T-SQL
](chap-sql-server-aurora-mysql.tsql.databasesschemas.md)
+ [

# Transactions for T-SQL
](chap-sql-server-aurora-mysql.tsql.transactions.md)
+ [

# DELETE and UPDATE FROM for T-SQL
](chap-sql-server-aurora-mysql.tsql.deleteupdate.md)
+ [

# Stored procedures for T-SQL
](chap-sql-server-aurora-mysql.tsql.storedprocedures.md)
+ [

# Error handling for T-SQL
](chap-sql-server-aurora-mysql.tsql.errorhandling.md)
+ [

# Flow control for T-SQL
](chap-sql-server-aurora-mysql.tsql.flowcontrol.md)
+ [

# Full-text search for T-SQL
](chap-sql-server-aurora-mysql.tsql.fulltextsearch.md)
+ [

# SQL server graph features for T-SQL
](chap-sql-server-aurora-mysql.tsql.graph.md)
+ [

# JSON and XML for T-SQL
](chap-sql-server-aurora-mysql.tsql.xml.md)
+ [

# MERGE for T-SQL
](chap-sql-server-aurora-mysql.tsql.merge.md)
+ [

# PIVOT and UNPIVOT for T-SQL
](chap-sql-server-aurora-mysql.tsql.pivot.md)
+ [

# Synonyms for T-SQL
](chap-sql-server-aurora-mysql.tsql.synonyms.md)
+ [

# SQL Server TOP and FETCH and MySQL LIMIT for T-SQL
](chap-sql-server-aurora-mysql.tsql.topfetch.md)
+ [

# Triggers for T-SQL
](chap-sql-server-aurora-mysql.tsql.triggers.md)
+ [

# User-defined functions for T-SQL
](chap-sql-server-aurora-mysql.tsql.udf.md)
+ [

# User-defined types for T-SQL
](chap-sql-server-aurora-mysql.tsql.udt.md)
+ [

# Identity and sequences for T-SQL
](chap-sql-server-aurora-mysql.tsql.identitysequences.md)
+ [

# Managing statistics for T-SQL
](chap-sql-server-aurora-mysql.tsql.managingstatistics.md)

# Collations for T-SQL
<a name="chap-sql-server-aurora-mysql.tsql.collations"></a>

This topic provides reference content comparing collation and character set support between Microsoft SQL Server 2019 and Amazon Aurora MySQL. You can gain insight into how these database systems handle string management, storage, and comparison rules.


| Feature compatibility |  AWS SCT / AWS DMS automation level |  AWS SCT action code index | Key differences | 
| --- | --- | --- | --- | 
|   ![\[Three star feature compatibility\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-compatibility-3.png)   |   ![\[Four star automation level\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-automation-4.png)   |   [Collations](chap-sql-server-aurora-mysql.tools.actioncode.md#chap-sql-server-aurora-mysql.tools.actioncode.collations)   |  UNICODE uses `CHARACTER SET` property instead of `NCHAR` or `NVARCHAR` data types.  | 

## SQL Server Usage
<a name="chap-sql-server-aurora-mysql.tsql.collations.sqlserver"></a>

SQL Server collations define the rules for string management and storage in terms of sorting, case sensitivity, accent sensitivity, and code page mapping. SQL Server supports both ASCII and UCS-2 UNICODE data.

UCS-2 UNICODE data uses a dedicated set of UNICODE data types denoted by the prefix `N`: `Nchar` and `Nvarchar`. Their ASCII counterparts are CHAR and VARCHAR.

Choosing a collation and a character set has significant implications on data storage, logical predicate evaluations, query results, and query performance.

**Note**  
To view all collations supported by SQL Server, use the `fn_helpcollations` function as shown following: `SELECT * FROM sys.fn_helpcollations()`.

Collations define the actual bitwise binary representation of all string characters and the associated sorting rules. SQL Server supports multiple collations down to the column level. A table may have multiple string columns that use different collations. Collations for non-UNICODE character sets determine the code page number representing the string characters.

**Note**  
UNICODE and non-UNICODE data types in SQL Server aren’t compatible. A predicate or data modification that introduces a type conflict is resolved using predefined collation precedence rules. For more information, see [Collation Precedence](https://docs.microsoft.com/en-us/sql/t-sql/statements/collation-precedence-transact-sql?view=sql-server-ver15) in the *SQL Server documentation*.

Collations define sorting and matching sensitivity for the following string characteristics:
+ Case
+ Accent
+ Kana
+ Width
+ Variation selector

SQL Server uses a suffix naming convention that appends the option name to the collation name. For example, the collation `Azeri_Cyrillic_100_CS_AS_KS_WS_SC`, is an Azeri-Cyrillic-100 collation that is case-sensitive, accent-sensitive, kana type-sensitive, width-sensitive, and has supplementary characters.

SQL Server supports three types of collation sets: \$1 Windows Collations use the rules defined for collations by the operating system locale where UNICODE and non-UNICODE data use the same comparison algorithms. \$1 Binary Collations use the binary bit-wise code for comparison. Therefore, the locale doesn’t affect sorting. \$1 SQL Server Collations provide backward compatibility with previous SQL Server versions. They aren’t compatible with the windows collation rules for non-UNICODE data.

You can define collations at various levels:
+  **Server-level collations** determine the collations used for all system databases and is the default for future user databases. While the system databases collation can’t be changed, an alternative collation can be specified as part of the `CREATE DATABASE` statement
+  **Database-level collations** inherit the server default unless the `CREATE DATABASE` statement explicitly sets a different collation. This collation is used as a default for all `CREATE TABLE` and `ALTER TABLE` statements.
+  **Column-level collations** can be specified as part of the `CREATE TABLE` or `ALTER TABLE` statements to override the database’s default collation setting.
+  **Expression-level collations** can be set for individual string expressions using the `COLLATE` function. For example, `SELECT * FROM MyTable ORDER BY StringColumn COLLATE Latin1_General_CS_AS`.

**Note**  
SQL Server supports UCS-2 UNICODE only.

SQL Server 2019 adds support for UTF-8 for import and export encoding, and as database-level or column-level collation for string data. Support includes PolyBase external tables, and Always Encrypted (when not used with Enclaves). For more information, see [Collation and Unicode Support](https://docs.microsoft.com/en-us/sql/relational-databases/collations/collation-and-unicode-support?view=sql-server-ver15) in the *SQL Server documentation*.

### Syntax
<a name="chap-sql-server-aurora-mysql.tsql.collations.sqlserver.syntax"></a>

```
CREATE DATABASE <Database Name>
[ ON <File Specifications> ]
COLLATE <Collation>
[ WITH <Database Option List> ];
```

```
CREATE TABLE <Table Name>
(
    <Column Name> <String Data Type>
    COLLATE <Collation> [ <Column Constraints> ]...
);
```

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.collations.sqlserver.examples"></a>

The following example creates a database with a default Bengali\$1100\$1CS\$1AI collation.

```
CREATE DATABASE MyBengaliDatabase
ON
( NAME = MyBengaliDatabase_Datafile,
    FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA\MyBengaliDatabase.mdf',
    SIZE = 100)
LOG ON
    ( NAME = MyBengaliDatabase_Logfile,
FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA\MyBengaliDblog.ldf',
    SIZE = 25)
COLLATE Bengali_100_CS_AI;
```

The following example creates a table with two different collations.

```
CREATE TABLE MyTable
(
    Col1 CHAR(10) COLLATE Hungarian_100_CI_AI_SC NOT NULL PRIMARY KEY,
    COL2 VARCHAR(100) COLLATE Sami_Sweden_Finland_100_CS_AS_KS NOT NULL
);
```

For more information, see [Collation and Unicode support](https://docs.microsoft.com/en-us/sql/relational-databases/collations/collation-and-unicode-support?view=sql-server-ver15) in the *SQL Server documentation*.

## MySQL Usage
<a name="chap-sql-server-aurora-mysql.tsql.collations.mysql"></a>

 Amazon Aurora MySQL-Compatible Edition (Aurora MySQL) supports multiple character sets and a variety of collations that can be used for comparison. Similar to SQL Server, you can define collations at the server, database, and column level. Additionally, you can define collations at the table level in Aurora MySQL.

The paradigm of collations in Aurora MySQL is different than in SQL Server and consists of separate character set and collation objects. Aurora MySQL supports 41 different character sets and 222 collations. Seven different UNICODE character sets are supported including UCS-2, UTF-8 and UTF-32.

**Note**  
Use UCS-2 which is compatible with SQL Server UNICODE types.

Each character set can have one or more associated collations with a single default collation.

Collation names have prefixes consisting of the name of their associated character set followed by suffixes that indicate additional characteristics.

To see all character sets supported by Aurora MySQL, use the `INFORMATION_SCHEMA.CHARACTER_SETS` table or the `SHOW CHARACTER SET` statement.

To see all collations for a character set, use the `INFORMATION_SCHEMA.COLLATIONS` table or the `SHOW COLLATION` statement.

**Note**  
Character set and collation settings also affect client-to -server communications. You can set explicit collations for sessions using the `SET` command. For example, `SET NAMES 'utf8';` causes Aurora MySQL to treat incoming object names as UTF-8 encoded.

You can set the default character set and collations at the server level using custom cluster parameter groups. For more information, see [Server Options](chap-sql-server-aurora-mysql.configuration.serveroptions.md).

At the database level, you can set a default character set and collation with the `CREATE DATABASE` and `ALTER DATABASE` statements. Consider the following example:

```
CREATE DATABASE MyDatabase
CHARACTER SET latin1 COLLATE latin1_swedish_ci;
```

To view the default character set and collation for an Aurora MySQL databases, use the following statement:

```
SELECT DEFAULT_CHARACTER_SET_NAME,
    DEFAULT_COLLATION_NAME
FROM INFORMATION_SCHEMA.SCHEMATA
WHERE SCHEMA_NAME = '<Database Name>';
```

**Note**  
In Aurora MySQL, a *database* is equivalent to an SQL Server *schema*. For more information, see [Databases and Schemas](chap-sql-server-aurora-mysql.tsql.databasesschemas.md).

Every string column in Aurora MySQL has a character set and an associated collation. If not explicitly specified, it will inherit the table default. To specify a non-default character set and collation, use the `CHARACTER SET` and `COLLATE` clauses of the `CREATE TABLE` statement.

```
CREATE TABLE MyTable
(
    StringColumn VARCHAR(5) NOT NULL
    CHARACTER SET latin1
    COLLATE latin1_german1_ci
);
```

At the expression level, similar to SQL Server, you can use the `COLLATE` function to explicitly declare a string’s collation. In addition, a prefix to the string can be used to denote a specific character set. Consider the following example:

```
SELECT _latin1'Latin non-UNICODE String',
_utf8'UNICODE String' COLLATE utf8_danish_ci;
```

**Note**  
The Aurora MySQL term for this prefix or string header is introducer. It doesn’t change the value of the string; only the character set.

At the session level, the server’s setting determines the default character set and collation used to evaluate nonqualified strings.

Although the server’s character set and collation default settings can be modified using the cluster parameter groups, it is recommended that client applications don’t assume a specific setting and explicitly set the required character set and collation using the `SET NAMES` and `SET CHARACTER SET` statements.

For more information, see [Connection Character Sets and Collations](https://dev.mysql.com/doc/refman/5.7/en/charset-connection.html) in the *MySQL documentation*.

### Syntax
<a name="chap-sql-server-aurora-mysql.tsql.collations.mysql.syntax"></a>

The following example creates a database-level collation.

```
CREATE DATABASE <Database Name>
[DEFAULT] CHARACTER SET <Character Set>
[[DEFAULT] COLLATE <Collation>];
```

The following example creates a table-level collation.

```
CREATE TABLE <Table Name>
(Column Specifications)
[DEFAULT] CHARACTER SET <Character Set>
[COLLATE <Collation>];
```

The following example creates a column collation.

```
CREATE TABLE <Table Name>
(
<Column Name> {CHAR | VARCHAR | TEXT} (<Length>)
CHARACTER SET CHARACTER SET <Character Set>
[COLLATE <Collation>];
```

The following example creates an expression collation.

```
_<Character Set>'<String>' COLLATE <Collation>
```

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.collations.mysql.examples"></a>

The following walkthrough describes how to change the cluster character set and collation.

1. Log in to your [Management Console](https://eu-central-1.console.aws.amazon.com/rds/home?#databases:), choose ** Amazon RDS **, and then choose **Parameter groups**.

1. Choose **Create parameter group**.

1. For **Parameter group family**, choose **aurora-mysql5.7**.

1. For **Type**, choose **DB Cluster Parameter Group**.

1. For **Group name**, enter the identified for the DB parameter group.

1. Choose **Create**.

1. Choose the newly created group on the **Parameter groups** list.

1. For **Parameters**, enter **character\$1set\$1server** in the search box and choose **Edit parameters**.

1. Choose the server default character set.

1. Delete the search term and enter collation. Select the desired default server collation and choose **Preview changes**.

1. Check the values and choose **Close**, and then choose **Save changes**.

1. Return to the Management Console dashboard and choose **Create database**.

1. For **Choose a database creation method**, choose **Easy create**.

1. For **Engine type**, choose ** Amazon Aurora **.

1. Enter the instance size, cluster identifier and username. Choose **Create database**.

1. Modify the created instance to change the **DB Parameter group**.

## Summary
<a name="chap-sql-server-aurora-mysql.tsql.collations.summary"></a>

The following table identifies similarities, differences, and key migration considerations.


| Feature | SQL Server |  Aurora MySQL  | 
| --- | --- | --- | 
|  Unicode support  |  UTF 16 using `NCHAR` and `NVARCHAR` data types  |  8 UNICODE character sets, using the `CHARACTER SET` option  | 
|  Collations levels  |  Server, Database, Column, Expression  |  Server, Database, Table, Column, Expression  | 
|  View collation metadata  |   `fn_helpcollation` system view  |   `INFORMATION_SCHEMA.SCHEMATA`, `SHOW COLLATION`, `SHOW CHARACTER SET`   | 

For more information, see [Character Sets, Collations, Unicode](https://dev.mysql.com/doc/refman/5.7/en/charset.html) in the *MySQL documentation*.

# Cursors for T-SQL
<a name="chap-sql-server-aurora-mysql.tsql.cursors"></a>

This topic provides reference information about cursor compatibility between Microsoft SQL Server 2019 and Amazon Aurora MySQL. You can understand the differences in cursor support and functionality when migrating from SQL Server to Aurora MySQL.


| Feature compatibility |  AWS SCT / AWS DMS automation level |  AWS SCT action code index | Key differences | 
| --- | --- | --- | --- | 
|   ![\[Three star feature compatibility\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-compatibility-3.png)   |   ![\[Three star automation level\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-automation-3.png)   |   [Cursors](chap-sql-server-aurora-mysql.tools.actioncode.md#chap-sql-server-aurora-mysql.tools.actioncode.cursors)   |   Aurora MySQL supports only static, forward only, read-only cursors.  | 

## SQL Server Usage
<a name="chap-sql-server-aurora-mysql.tsql.cursors.sqlserver"></a>

A *set* is a fundamental concept of the relation data model, from which SQL is derived. SQL is a declarative language that operates on whole sets, unlike most procedural languages that operate on individual data elements. A single invocation of a SQL statement can return a whole set or modify millions of rows.

Many developers are accustomed to using procedural or imperative approaches to develop solutions that are difficult to implement using set-based querying techniques. Also, operating on row data sequentially may be a more appropriate approach is certain situations.

Cursors provide an alternative mechanism for operating on result sets. Instead of receiving a table object containing rows of data, applications can use cursors to access the data sequentially, row-by-row. Cursors provide the following capabilities:
+ Positioning the cursor at specific rows of the result set using absolute or relative offsets.
+ Retrieving a row, or a block of rows, from the current cursor position.
+ Modifying data at the current cursor position.
+ Isolating data modifications by concurrent transactions that affect the cursor’s result.
+ T-SQL statements can use cursors in scripts, stored procedures, and triggers.

### Syntax
<a name="chap-sql-server-aurora-mysql.tsql.cursors.sqlserver.syntax"></a>

```
DECLARE <Cursor Name>
CURSOR [LOCAL | GLOBAL]
    [FORWARD_ONLY | SCROLL]
    [STATIC | KEYSET | DYNAMIC | FAST_FORWARD]
    [ READ_ONLY | SCROLL_LOCKS | OPTIMISTIC]
    [TYPE_WARNING]
    FOR <SELECT statement>
    [ FOR UPDATE [ OF <Column List>]][;]
```

```
FETCH [NEXT | PRIOR | FIRST | LAST | ABSOLUTE <Value> | RELATIVE <Value>]
FROM <Cursor Name> INTO <Variable List>;
```

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.cursors.sqlserver.examples"></a>

The following example processes data in a cursor.

```
DECLARE MyCursor CURSOR FOR
    SELECT *
    FROM Table1 AS T1
        INNER JOIN
        Table2 AS T2
        ON T1.Col1 = T2.Col1;
    OPEN MyCursor;
    DECLARE @VarCursor1 VARCHAR(20);
    FETCH NEXT
        FROM MyCursor INTO @VarCursor1;

    WHILE @@FETCH_STATUS = 0
    BEGIN
        EXEC MyPRocessingProcedure
            @InputParameter = @VarCursor1;
        FETCH NEXT
            FROM product_cursor INTO @VarCursor1;
    END

    CLOSE MyCursor;
    DEALLOCATE MyCursor ;
```

For more information, see [SQL Server Cursors](https://docs.microsoft.com/en-us/sql/relational-databases/cursors?view=sql-server-ver15) and [Cursors (Transact-SQL)](https://docs.microsoft.com/en-us/sql/t-sql/language-elements/cursors-transact-sql?view=sql-server-ver15) in the *SQL Server documentation*.

## MySQL Usage
<a name="chap-sql-server-aurora-mysql.tsql.cursors.mysql"></a>

 Amazon Aurora MySQL-Compatible Edition (Aurora MySQL) supports cursors only within stored routines, functions and stored procedures.

Unlike SQL Server, which offers an array of cursor types, Aurora MySQL cursors have the following characteristics:
+  **Asensitive** — The server can choose to either make a copy of its result table or to access the source data as the cursor progresses.
+  **Read-only** — Cursors aren’t updatable.
+  **Nonscrollable** — Cursors can only be traversed in one direction and can’t skip rows. The only supported cursor advance operation is `FETCH NEXT`.

In Aurora MySQL, cursor declarations appear before handler declarations and after variable and condition declarations.

Similar to SQL Server, you can declare cursors with the `DECLARE CURSOR` statement. To open a cursor, use the `OPEN` statement. To fetch a cursor, use the `FETCH` statement. You can close the cursor with the `CLOSE` statement.

**Note**  
 Aurora MySQL doesn’t have a `DEALLOCATE` statement because you don’t need it.

### DECLARE Cursor
<a name="chap-sql-server-aurora-mysql.tsql.cursors.mysql.declare"></a>

```
DECLARE <Cursor Name> CURSOR
FOR <Cursor SELECT Statement>
```

The `DECLARE CURSOR` statement instantiates a cursor object and associates it with a `SELECT` statement. This `SELECT` is then used to retrieve the cursor rows.

To fetch the rows, use the `FETCH` statement. As mentioned before, Aurora MySQL supports only `FETCH NEXT`. Make sure that the number of output variables specified in the `FETCH` statement matches the number of columns retrieved by the cursor.

 Aurora MySQL cursors have additional characteristics:
+  `SELECT INTO` isn’t allowed in a cursor.
+ Stored routing can have multiple cursor declarations, but every cursor declared in a given code block must have a unique name.
+ Cursors can be nested.

### OPEN Cursor
<a name="chap-sql-server-aurora-mysql.tsql.cursors.mysql.open"></a>

```
OPEN <Cursor Name>;
```

The `OPEN` command populates the cursor with the data, either dynamically or in a temporary table, and readies the first row for consumption by the `FETCH` statement.

### FETCH Cursor
<a name="chap-sql-server-aurora-mysql.tsql.cursors.mysql.fetch"></a>

```
FETCH [[NEXT] FROM] <Cursor Name>
INTO <Variable 1> [,<Variable n>]
```

The `FETCH` statement retrieves the current pointer row, assigns the column values to the variables listed in the `FETCH` statement, and advances the cursor pointer by one row. If the row isn’t available, meaning the cursor has been exhausted, Aurora MySQL raises a no data condition with the `SQLSTATE` value set to `0200000`.

To catch this condition, or the alternative `NOT FOUND` condition, create a condition handler. For more information, see [Error Handling](chap-sql-server-aurora-mysql.tsql.errorhandling.md).

**Note**  
Carefully plan your error handling flow. The same condition might be raised by other `SELECT` statements or other cursors than the one you intended. Place operations within `BEGIN-END` blocks to associate each cursor with its own handler.

### CLOSE Cursor
<a name="chap-sql-server-aurora-mysql.tsql.cursors.mysql.close"></a>

```
CLOSE <Cursor Name>;
```

The `CLOSE` statement closes an open cursor. If the cursor with the specified name doesn’t exist, Aurora MySQL raises an error. If a cursor isn’t explicitly closed, Aurora MySQL closes it automatically at the end of the `BEGIN …​ END` block in which it was declared.

### Migration Considerations
<a name="chap-sql-server-aurora-mysql.tsql.cursors.mysql.considerations"></a>

The Aurora MySQL Cursors framework is much simpler than SQL Server and provides only the basic types. If your code relies on advanced cursor features, these will need to be rewritten.

However, most applications use forward only, read only cursors, and those will be easy to migrate.

If your application uses cursors in ad-hoc batches, move the code to a stored procedure or a function.

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.cursors.mysql.examples"></a>

The following examples use a cursor to iterate over source rows and merges into the `OrderItems` table.

Create the `OrderItems` table.

```
CREATE TABLE OrderItems
(
    OrderID INT NOT NULL,
    Item VARCHAR(20) NOT NULL,
    Quantity SMALLINT NOT NULL,
    PRIMARY KEY(OrderID, Item)
);
```

Create and populate the `SourceTable` table.

```
CREATE TABLE SourceTable
(
    OrderID INT,
    Item VARCHAR(20),
    Quantity SMALLINT,
    PRIMARY KEY (OrderID, Item)
);
```

```
INSERT INTO SourceTable (OrderID, Item, Quantity)
VALUES
(1, 'M8 Bolt', 100),
(2, 'M8 Nut', 100),
(3, 'M8 Washer', 200);
```

Create a procedure to loop through `SourceTable` and insert rows.

**Note**  
There are syntax differences between T-SQL for the `CREATE PROCEDURE` and the `CURSOR` declaration. For more information, see [Stored Procedures](chap-sql-server-aurora-mysql.tsql.storedprocedures.md).

```
CREATE PROCEDURE LoopItems()
BEGIN
    DECLARE done INT DEFAULT FALSE;
    DECLARE var_OrderID INT;
    DECLARE var_Item VARCHAR(20);
    DECLARE var_Quantity SMALLINT;
    DECLARE ItemCursor CURSOR
    FOR
        SELECT OrderID,
            Item,
            Quantity
        FROM SourceTable;
    DECLARE CONTINUE HANDLER
        FOR NOT FOUND
        SET done = TRUE;
    OPEN ItemCursor;
    CursorStart: LOOP
    FETCH NEXT
        FROM ItemCursor
        INTO var_OrderID,
            var_Item,
            var_Quantity;
    IF Done
        THEN LEAVE CursorStart;
    END IF;
        INSERT INTO OrderItems (OrderID, Item, Quantity)
        VALUES (var_OrderID, var_Item, var_Quantity);
    END LOOP;
    CLOSE ItemCursor;
END;
```

Run the stored procedure.

```
CALL LoopItems();
```

Select all rows from the `OrderItems` table.

```
SELECT * FROM OrderItems;
OrderID  Item       Quantity
1        M8 Bolt    100
2        M8 Nut     100
3        M8 Washer  200
```

## Summary
<a name="chap-sql-server-aurora-mysql.tsql.cursors.summary"></a>


| Feature | SQL Server |  Aurora MySQL  | Comments | 
| --- | --- | --- | --- | 
|  Cursor options  |   `[FORWARD_ONLY \| SCROLL]`   `[STATIC \| KEYSET \| DYNAMIC \| FAST_FORWARD]`   `[READ_ONLY \| SCROLL_LOCKS \| OPTIMISTIC]`   |  |  | 
|  Updateable cursors  |   `DECLARE CURSOR…​ FOR UPDATE`   |  Not supported  |  | 
|  Declaration  |   `DECLARE CURSOR`   |   `DECLARE CURSOR`   |  No options for `DECLARE CURSOR` in Aurora MySQL.  | 
|  Open  |  OPEN  |  OPEN  |  | 
|  Fetch  |   `FETCH NEXT \| PRIOR \| FIRST \| LAST \| ABSOLUTE \| RELATIVE`   |   `FETCH NEXT`   |  | 
|  Close  |  CLOSE  |  CLOSE  |  | 
|  Deallocate  |  DEALLOCATE  |  N/A  |  Not required because the `CLOSE` statement deallocates the cursor  | 
|  Cursor end condition  |   `@@FETCH_STATUS` system variable  |  Event Handler  |  Event handlers aren’t specific to a cursor. For more information, see [Error Handling](chap-sql-server-aurora-mysql.tsql.errorhandling.md).  | 

For more information, see [Cursors](https://dev.mysql.com/doc/refman/5.7/en/cursors.html) in the *MySQL documentation*.

# Date and time functions for T-SQL
<a name="chap-sql-server-aurora-mysql.tsql.datetime"></a>

This topic provides reference information about date and time functions in Microsoft SQL Server and Amazon Aurora MySQL, which is valuable for database administrators and developers migrating from SQL Server to Aurora MySQL. You can understand the similarities and differences in how these two database systems handle temporal operations, including system date and time values, time zone considerations, and specific function equivalents.


| Feature compatibility |  AWS SCT / AWS DMS automation level |  AWS SCT action code index | Key differences | 
| --- | --- | --- | --- | 
|   ![\[Three star feature compatibility\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-compatibility-3.png)   |   ![\[Four star automation level\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-automation-4.png)   |   [Date and Time Functions](chap-sql-server-aurora-mysql.tools.actioncode.md#chap-sql-server-aurora-mysql.tools.actioncode.datetime)   |  Time zone handling. Syntax differences.  | 

## SQL Server Usage
<a name="chap-sql-server-aurora-mysql.tsql.datetime.sqlserver"></a>

Date and time functions are scalar functions that perform operations on temporal or numeric input and return temporal or numeric values.

System date and time values are derived from the operating system of the server where SQL Server is running.

**Note**  
This section doesn’t address time zone considerations and time zone aware functions. For more information, see [Data Types](chap-sql-server-aurora-mysql.sql.datatypes.md).

### Syntax and Examples
<a name="chap-sql-server-aurora-mysql.tsql.datetime.sqlserver.examples"></a>

The following table lists the most commonly used date and time functions.


| Function | Purpose | Example | Result | Comments | 
| --- | --- | --- | --- | --- | 
|   `GETDATE` and `GETUTCDATE`   |  Return a datetime value that contains the current local or UTC date and time.  |   `SELECT GETDATE()`   |  2018-04-05 15:53:01.380  |  | 
|   `DATEPART`, `DAY`, `MONTH`, and `YEAR`   |  Return an integer value representing the specified date part of a specified date.  |   `SELECT MONTH(GETDATE()), YEAR(GETDATE())`   |  4, 2018  |  | 
|   `DATEDIFF`   |  Returns an integer value of date part boundaries that are crossed between two dates.  |   `SELECT DATEDIFF(DAY, GETDATE(), EOMONTH(GETDATE()))`   |  25  |  How many days are left until the end of the month.  | 
|   `DATEADD`   |  Returns a datetime value that is calculated with an offset interval to the specified date part of a date.  |   `SELECT DATEADD(DAY, 25, GETDATE())`   |  2018-04-30 15:55:52.147  |  | 
|   `CAST` and `CONVERT`   |  Converts datetime values to and from string literals and to and from other datetime formats.  |   `SELECT CAST(GETDATE() AS DATE)`   `SELECT CONVERT(VARCHAR(20), GETDATE(), 112)`   |  2018-04-05 20180405  |  Default date format. Style 112 (ISO) with no separators.  | 

For more information, see [Date and Time functions](https://docs.microsoft.com/en-us/sql/t-sql/functions/date-and-time-data-types-and-functions-transact-sql?view=sql-server-ver15#DateandTimeFunctions) in the *SQL Server documentation*.

## MySQL Usage
<a name="chap-sql-server-aurora-mysql.tsql.datetime.mysql"></a>

 Amazon Aurora MySQL-Compatible Edition (Aurora MySQL) provides a very rich set of scalar date and time functions; more than SQL Server.

**Note**  
While some of the functions such as DATEDIFF seem to be similar to those in SQL Server, the functionality can be significantly different. Take extra care when migrating temporal logic to Aurora MySQL paradigms.

### Syntax and Examples
<a name="chap-sql-server-aurora-mysql.tsql.datetime.mysql.examples"></a>


| Function | Purpose | Example | Result | Comments | 
| --- | --- | --- | --- | --- | 
|   `NOW`, `LOCALTIME`, `CURRENT_TIMESTAMP`, and `SYSDATE`   |  Returns a datetime value that contains the current local date and time.  |   `SELECT NOW()`   |  2018-04-06 18:57:54  |   `SYSDATE` returns the time at which it runs, compared to `NOW`, which returns a constant time when the statement started running. Also, `SET TIMESTAMP` doesn’t affect `SYSDATE`.  | 
|   `UTC_TIMESTAMP`   |  Returns a datetime value that contains the current UTC date and time.  |   `SELECT UTC_TIMESTAMP()`   |  2018-04-07 04:57:54  |  | 
|   `SECOND`, `MINUTE`, `HOUR`, `DAY`, `WEEK`, `MONTH`, and `YEAR`   |  Returns an integer value representing the specified date part of a specified date function.  |   `SELECT MONTH(NOW()), YEAR(NOW())`   |  4, 2018  |  | 
|   `DATEDIFF`   |  Returns an integer value of the difference in days between two dates.  |   `SELECT DATEDIFF(NOW(),'2018-05-01')`   |  -25  |   `DATEDIFF` in Aurora MySQL is only for calculating difference in days. Use `TIMESTAMPDIFF` instead.  | 
|   `TIMESTAMPDIFF`   |  Returns an integer value of the difference in date part between two dates.  |   `SELECT TIMESTAMPDIFF(DAY, NOW(),'2018-05-01')`   |  24  |  | 
|   `DATE_ADD`, `DATE_SUB`   |  Returns a datetime value that is calculated with an offset interval to the specified date part of a date.  |   `SELECT DATE_ADD(NOW(),INTERVAL 1 DAY);`   |  2018-04-07 19:35:32  |  | 
|   `CAST` and `CONVERT`   |  Converts datetime values to and from string literals and to and from other datetime formats.  |   `SELECT CAST(GETDATE() AS DATE)`   `SELECT CONVERT(VARCHAR(20), GETDATE(), 112)`   |  2018-04-05 20180405  |  Default date format. Style 112 (ISO) with no separators.  | 

### Migration Considerations
<a name="chap-sql-server-aurora-mysql.tsql.datetime.mysql.considerations"></a>

The date and time handling paradigm in Aurora MySQL differs from SQL Server.

Be aware of the differences in data types, time zone awareness, and locale handling. For more information, see [Data Types](chap-sql-server-aurora-mysql.sql.datatypes.md).

## Summary
<a name="chap-sql-server-aurora-mysql.tsql.datetime.summary"></a>

The following table identifies similarities, differences, and key migration considerations.


| SQL Server function |  Aurora MySQL function | Comments | 
| --- | --- | --- | 
|   `GETDATE`, `CURRENT_TIMESTAMP`   |   `NOW`, `LOCALTIME`, `CURRENT_TIMESTAMP`, and `SYSDATE`   |   `CURRENT_TIMESTAMP` is the ANSI standard and it is compatible. `SYSDATE` returns the time at which it runs, unlike `NOW` which returns a constant time when the statement started running. Also, SET `TIMESTAMP` doesn’t affect `SYSDATE`.  | 
|   `GETUTCDATE`   |   `UTC_TIMESTAMP`   |  | 
|   `DAY`, `MONTH`, and `YEAR`   |   `DAY`, `MONTH`, `YEAR`   |  Compatible syntax.  | 
|   `DATEPART`   |   `EXTRACT`, or one of: `MICROSECOND`, `SECOND`, `MINUTE`, `HOUR`, `DAY`, `DAYNAME`, `DAYOFWEEK`, `DAYOFYEAR`, `WEEK`, `MONTH`, `MONTHNAME`, `QUARTER`, `YEAR`   |   Aurora MySQL supports `EXTRACT` as a generic `DATEPART` function. For example, `EXTRACT (YEAR FROM NOW())`. It also supports individual functions for each day part.  | 
|   `DATEDIFF`   |   `TIMESTAMPDIFF`   |   `DATEDIFF` in Aurora MySQL only calculates differences in days.  | 
|   `DATEADD`   |   `DATE_ADD`, `DATE_SUB`, `TIMESTAMPADD`   |   `DATEADD` in Aurora MySQL only adds full days to a datetime value. Aurora MySQL also supports `DATE_SUB` for subtracting date parts from a date time expression. The argument order and syntax is also different and requires a rewrite.  | 
|   `CAST` and `CONVERT`   |   `DATE_FORMAT`, `TIME_FORMAT`   |  Although Aurora MySQL supports both `CAST` and `CONVERT`, they aren’t used for style conversion as in SQL Server. Use `DATE_FORMAT` and `TIME_FORMAT`.  | 

For more information, see [Date and Time Functions](https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html) in the *MySQL documentation*.

# String functions for T-SQL
<a name="chap-sql-server-aurora-mysql.tsql.stringfunctions"></a>

This topic provides reference information about string function compatibility when migrating from Microsoft SQL Server 2019 to Amazon Aurora MySQL. You can use this guide to understand the similarities and differences in string manipulation capabilities between the two database systems.


| Feature compatibility |  AWS SCT / AWS DMS automation level |  AWS SCT action code index | Key differences | 
| --- | --- | --- | --- | 
|   ![\[Four star feature compatibility\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-compatibility-4.png)   |   ![\[Four star automation level\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-automation-4.png)   |  N/A  |  Differences with the UNICODE paradigm. For more information, see [Collations](chap-sql-server-aurora-mysql.tsql.collations.md). Syntax and option differences.  | 

## SQL Server Usage
<a name="chap-sql-server-aurora-mysql.tsql.stringfunctions.sqlserver"></a>

String functions are typically scalar functions that perform an operation on string input and return a string or a numeric value.

### Syntax and Examples
<a name="chap-sql-server-aurora-mysql.tsql.stringfunctions.sqlserver.examples"></a>

The following table lists the most commonly used string functions.


| Function | Purpose | Example | Result | Comments | 
| --- | --- | --- | --- | --- | 
|   `ASCII` and `UNICODE`   |  Convert an ASCII or UNICODE character to its ASCII or UNICODE code.  |   `SELECT ASCII ('A')`   |  65  |  Returns a numeric integer value.  | 
|   `CHAR` and `NCHAR`   |  Convert between ASCII or UNICODE code to a string character.  |   `SELECT CHAR(65)`   |  'A'  |  Numeric integer value as input.  | 
|   `CHARINDEX` and `PATINDEX`   |  Find the starting position of one string expression (or string pattern) within another string expression.  |   `SELECT CHARINDEX('ab', 'xabcdy')`   |  2  |  Returns a numeric integer value.  | 
|   `CONCAT` and `CONCAT_WS`   |  Combine multiple string input expressions into a single string with, or without, a separator character (WS).  |   `SELECT CONCAT('a','b'), CONCAT_WS(',','a','b')`   |  'ab', 'a,b'  |  | 
|   `LEFT`, `RIGHT`, and `SUBSTRING`   |  Return a partial string from another string expression based on position and length.  |   `SELECT LEFT('abs',2), SUBSTRING('abcd',2,2)`   |  'ab', 'bc'  |  | 
|   `LOWER` and `UPPER`   |  Return a string with all characters in lower or upper case. Use for presentation or to handle case insensitive expressions.  |   `SELECT LOWER('ABcd')`   |  'abcd'  |  | 
|   `LTRIM`, `RTRIM` and `TRIM`   |  Remove leading and trailing spaces.  |   `SELECT LTRIM ('abc d ')`   |  'abc d '  |  | 
|   `STR`   |  Convert a numeric value to a string.  |   `SELECT STR(3.1415927,5,3)`   |  3.142  |  Numeric expressions as input.  | 
|   `REVERSE`   |  Return a string in reverse order.  |   `SELECT REVERSE('abcd')`   |  'dcba'  |  | 
|   `REPLICATE`   |  Return a string that consists of zero or more concatenated copies of another string expression.  |   `SELECT REPLICATE('abc', 3)`   |  'abcabcabc'  |  | 
|   `REPLACE`   |  Replace all occurrences of a string expression with another.  |   `SELECT REPLACE('abcd', 'bc', 'xy')`   |  'axyd'  |  | 
|   `STRING_SPLIT`   |  Parse a list of values with a separator and return a set of all individual elements.  |   `SELECT * FROM STRING_SPLIT('1,2',',') AS X©`   |  1 2  |   `STRING_SPLIT` is a table-valued function.  | 
|   `STRING_AGG`   |  Return a string that consists of concatenated string values in row groups.  |   `SELECT STRING_AGG(C, ',') FROM VALUES(1,'a'), (1, 'b'), (2,'c') AS X (ID,C) GROUP BY I`   |  1 'ab' 2 'c'  |   `STRING_AGG` is an aggregate function.  | 

For more information, see [String Functions (Transact-SQL)](https://docs.microsoft.com/en-us/sql/t-sql/functions/string-functions-transact-sql?view=sql-server-ver15) in the *SQL Server documentation*.

## MySQL Usage
<a name="chap-sql-server-aurora-mysql.tsql.stringfunctions.mysql"></a>

 Amazon Aurora MySQL-Compatible Edition (Aurora MySQL) supports a large set of string functions; far more than SQL Server. See the link at the end of this section for the full list. Some of the functions, such as regular expressions (`REGEXP`), don’t exist in SQL Server and may be useful for your application.

### Syntax and Examples
<a name="chap-sql-server-aurora-mysql.tsql.stringfunctions.mysql.examples"></a>

The following table lists the most commonly used string functions.


| Function | Purpose | Example | Result | Comments | 
| --- | --- | --- | --- | --- | 
|   `ASCII` and `ORD`   |  Convert an ASCII or multi-byte code to its string character.  |   `SELECT ASCII ('A')`   |  65  |  Returns a numeric integer value.  | 
|   `CHAR`   |  Convert between a character and its UNICODE code.  |   `SELECT CHAR (65)`   |  'A'  |  Numeric integer value as input.  | 
|   `LOCATE`   |  Find the starting position of one string expression (or string pattern) within another string expression.  |   `SELECT LOCATE ('ab', 'xabcdy')`   |  2  |  Returns a numeric integer value.  | 
|   `CONCAT` and `CONCAT_WS`   |  Combine multiple string input expressions into a single string with or without a separator character (WS).  |  SELECT CONCAT ('a','b'), CONCAT\$1WS(',','a','b')  |  'ab', 'a,b'  |  | 
|   `LEFT`, `RIGHT`, and `SUBSTRING`   |  Return a partial string from another string expression based on position and length  |   `SELECT LEFT('abs',2), SUBSTRING('abcd',2,2)`   |  'ab', 'bc'  |  | 
|   `LOWER` and `UPPER`   |  Return a string with all characters in lower or upper case. Use for presentation or to handle case insensitive expressions.  |   `SELECT LOWER ('ABcd')`   |  'abcd'  |  These have no effect when applied to binary collation strings. Convert the string to a non-binary string collation to convert letter case.  | 
|   `LTRIM`, `RTRIM`, and `TRIM`   |  Remove leading and trailing spaces.  |   `SELECT LTRIM(' abc d ')`   `SELECT TRIM(LEADING 'x' FROM 'xxxabcxxx')`   |  'abc d ' 'abcxxx'  |   `TRIM` in Aurora MySQL is not limited to spaces.  `TRIM ([{BOTH \| LEADING \| TRAILING} [<Remove String>] FROM] <String>)`   | 
|   `FORMAT`   |  Convert a numeric value to a string.  |   `SELECT FORMAT (3.1415927,5)`   |  3.14159  |  Numeric expressions as input.  | 
|   `REVERSE`   |  Return a string in reverse order.  |   `SELECT REVERSE('abcd')`   |  'dcba'  |  | 
|   `REPEAT`   |  Return a string that consists of zero or more concatenated copies of another string expression.  |  SELECT REPEAT('abc', 3)  |  'abcabcabc'  |  | 
|   `REPLACE`   |  Replace all occurrence of a string expression with another.  |   `SELECT REPLACE('abcd', 'bc','xy')`   |  'axyd'  |  | 

### Migration Considerations
<a name="chap-sql-server-aurora-mysql.tsql.stringfunctions.mysql.considerations"></a>

 Aurora MySQL doesn’t handle `ASCII` and `UNICODE` types separately. Any string can be either `UNICODE` or `ASCII`, depending on its collation property. For more information, see [Data Types](chap-sql-server-aurora-mysql.sql.datatypes.md).

Many of the Aurora MySQL string functions that are compatible with SQL Server also support additional functionality. For example, the `TRIM` and `CHAR` functions. Aurora MySQL also supports many functions that SQL Server doesn’t support. For example, functions that deal with a delimited list set of values. Be sure to explore all options.

 Aurora MySQL also supports regular expressions. See the `REGEXP` and `RLIKE` functions to get started.

## Summary
<a name="chap-sql-server-aurora-mysql.tsql.stringfunctions.summary"></a>

The following table identifies similarities, differences, and key migration considerations.


| SQL Server function |  Aurora MySQL function | Comments | 
| --- | --- | --- | 
|   `ASCII` and `UNICODE`   |   `ASCII` and `ORD`   |  Compatible. For more information, see [Data Types](chap-sql-server-aurora-mysql.sql.datatypes.md).  | 
|   `CHAR` and `NCHAR`   |   `CHAR`   |  Unlike SQL Server, `CHAR` in Aurora MySQL accepts a list of values and constructs a concatenated string. For more information, see [Data Types](chap-sql-server-aurora-mysql.sql.datatypes.md).  | 
|   `CHARINDEX` and `PATINDEX`   |   `LOCATE` and `POSITION`   |   `LOCATE` and `POSITION` are synonymous but don’t support wildcards as `PATINDEX`. Use the `FIND_IN_SET` function to extract an element position in a comma separated value string.  | 
|   `CONCAT` and `CONCAT_WS`   |   `CONCAT` and `CONCAT_WS`   |  Compatible syntax.  | 
|   `LEFT`, `RIGHT`, and `SUBSTRING`   |   `LEFT`, `RIGHT`, and `SUBSTRING`   |  Compatible syntax. Aurora MySQL supports `MID` and `SUBSTR`, which are synonymous with `SUBSTRING`. Use the `SUBSTRING_INDEX` function to extract an element from a delimited list.  | 
|   `LOWER` and `UPPER`   |   `LOWER` AND `UPPER`   |  Compatible syntax. `LOWER` and `UPPER` have no effect when applied to binary collation strings.  | 
|   `LTRIM`, `RTRIM` and `TRIM`   |   `LTRIM`, `RTRIM` and `TRIM`   |  Compatible syntax. `TRIM` in Aurora MySQL is not limited to both ends and spaces. It can be used to trim either leading or trailing characters. The syntax is shown following:  `TRIM ([{BOTH \| LEADING \| TRAILING} [<Remove String>] FROM] <String>)`   | 
|   `STR`   |   `FORMAT`   |   `FORMAT` doesn’t support full precision and scale definition, but does support locale formatting.  | 
|   `REVERSE`   |   `REVERSE`   |  Compatible syntax.  | 
|   `REPLICATE`   |   `REPEAT`   |  Compatible arguments.  | 
|   `REPLACE`   |   `REPLACE`   |  Compatible syntax.  | 
|   `STRING_SPLIT`   |  Not supported.  |  Requires iterative code to extract elements with scalar string functions.  | 
|   `STRING_AGG`   |  Not supported  |  Requires iterative code to build a list with scalar string functions.  | 

For more information, see [String Functions and Operators](https://dev.mysql.com/doc/refman/5.7/en/string-functions.html) in the *MySQL documentation*.

# Databases and schemas for T-SQL
<a name="chap-sql-server-aurora-mysql.tsql.databasesschemas"></a>

This topic provides reference content comparing database and schema concepts between Microsoft SQL Server 2019 and Amazon Aurora MySQL. You can gain insight into how these two database systems differ in their approach to logical containers, security, and object hierarchies.


| Feature compatibility |  AWS SCT / AWS DMS automation level |  AWS SCT action code index | Key differences | 
| --- | --- | --- | --- | 
|   ![\[Two star feature compatibility\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-compatibility-2.png)   |   ![\[Two star automation level\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-automation-2.png)   |  N/A  |  Schema and database are synonymous.  | 

## SQL Server Usage
<a name="chap-sql-server-aurora-mysql.tsql.databasesschemas.sqlserver"></a>

Databases and schemas are logical containers for security and access control. Administrators can grant permissions collectively at both the databases and the schema levels. SQL Server instances provide security at three levels: individual objects, schemas (collections of objects), and databases (collections of schemas). For more information, see [Data Control Language](chap-sql-server-aurora-mysql.security.datacontrollanguage.md).

**Note**  
In previous versions of SQL server, the term *user* was interchangeable with the term *schema*. For backward compatibility, each database has several built-in security schemas including `guest`, `dbo`, `db_datareaded`, `sys`, `INFORMATION_SCHEMA`, and so on. You should migrate these schemas.

Each SQL Server instance can host and manage a collection of databases, which consist of SQL Server processes and the Master, Model, TempDB, and MSDB system databases.

The most common SQL Server administrator tasks at the database level are:
+  **Managing Physical Files** — Add, remove, change file growth settings, and re-size files.
+  **Managing Filegroups** — Partition schemes, object distribution, and read-only protection of tables.
+  **Managing default options**.
+  **Creating database snapshots**.

Unique object identifiers within an instance use three-part identifiers: `<Database name>.<Schema name>.<Object name>`.

The recommended way to view the metadata of database objects, including schemas, is to use the ANSI standard Information Schema views. In most cases, these views are compatible with other ANSI compliant RDBMS.

To view a list of all databases on the server, use the `sys.databases` table.

### Syntax
<a name="chap-sql-server-aurora-mysql.tsql.databasesschemas.sqlserver.syntax"></a>

Simplified syntax for `CREATE DATABASE`:

```
CREATE DATABASE <database name>
[ ON [ PRIMARY ] <file specifications>[,<filegroup>]
[ LOG ON <file specifications>
[ WITH <options specification> ] ;
```

Simplified syntax for CREATE SCHEMA:

```
CREATE SCHEMA <schema name> | AUTHORIZATION <owner name>;
```

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.databasesschemas.sqlserver.examples"></a>

Add a file to a database and create a table using the new file.

```
USE master;
```

```
ALTER DATABASE NewDB
ADD FILEGROUP NewGroup;
```

```
ALTER DATABASE NewDB
ADD FILE (
    NAME = 'NewFile',
    FILENAME = 'D:\NewFile.ndf',
    SIZE = 2 MB
    )
TO FILEGROUP NewGroup;
```

```
USE NewDB;
```

```
CREATE TABLE NewTable
(
    Col1 INT PRIMARY KEY
)
ON NewGroup;
```

```
SELECT Name
FROM sys.databases
WHERE database_id > 4;
```

Create a table within a new schema and database.

```
USE master
```

```
CREATE DATABASE NewDB;
```

```
USE NewDB;
```

```
CREATE SCHEMA NewSchema;
```

```
CREATE TABLE NewSchema.NewTable
(
    NewColumn VARCHAR(20) NOT NULL PRIMARY KEY
);
```

**Note**  
The preceding example uses default settings for the new database and schema.

For more information, see [sys.databases (Transact-SQL)](https://docs.microsoft.com/en-us/sql/relational-databases/system-catalog-views/sys-databases-transact-sql?view=sql-server-ver15), [CREATE SCHEMA (Transact-SQL)](https://docs.microsoft.com/en-us/sql/t-sql/statements/create-schema-transact-sql?view=sql-server-ver15), and [CREATE DATABASE](https://docs.microsoft.com/en-us/sql/t-sql/statements/create-database-transact-sql?view=sql-server-ver15&tabs=sqlpool) in the *SQL Server documentation*.

## MySQL Usage
<a name="chap-sql-server-aurora-mysql.tsql.databasesschemas.mysql"></a>

 Amazon Aurora MySQL-Compatible Edition (Aurora MySQL) supports both the `CREATE SCHEMA` and `CREATE DATABASE` statements. However, in Aurora MySQL, these statements are synonymous.

Unlike SQL Server, Aurora MySQL doesn’t have the concept of an instance hosting multiple databases, which in turn contain multiple schemas. Objects in Aurora MySQL are referenced as a two part name: `<schema>.<object>`. You can use the term *database* in place of schema, but it is conceptually the same thing.

**Note**  
This terminology conflict can lead to confusion for SQL Server database administrators unfamiliar with the Aurora MySQL concept of a database.

**Note**  
Each database and schema in Aurora MySQL is managed as a separate set of physical files similar to an SQL Server database.

 Aurora MySQL doesn’t have the concept of a schema owner. Permissions must be granted explicitly. However, Aurora MySQL supports a custom default collation at the schema level, whereas SQL Server supports it at the database level only. For more information, see [Collations](chap-sql-server-aurora-mysql.tsql.collations.md).

### Syntax
<a name="chap-sql-server-aurora-mysql.tsql.databasesschemas.mysql.syntax"></a>

Syntax for CREATE DATABASE:

```
CREATE {DATABASE | SCHEMA} <database name>
[DEFAULT] CHARACTER SET [=] <character set>|
[DEFAULT] COLLATE [=] <collation>
```

### Migration Considerations
<a name="chap-sql-server-aurora-mysql.tsql.databasesschemas.mysql.considerations"></a>

Similar to SQL Server, Aurora MySQL supports the USE command to specify the default database or schema for missing object qualifiers.

The syntax is identical to SQL Server:

```
USE <database name>;
```

After you run the `USE` command, the default database for the calling scope is changed to the specified database.

There is a relatively straightforward migration path for a class of common application architectures that use multiple databases but have all objects in a single schema (typically the default `dbo` schema) and require cross database queries. For these types of applications, create an Aurora MySQL Instance and then create multiple databases as you would in SQL Server using the `CREATE DATABASE` command.

Reference all objects using a two-part name instead of a three-part name by omitting the default schema identifier. For application code using the USE command instead of a three-part identifier, no rewrite is needed other than replacing the double dot with a single dot.

```
SELECT * FROM MyDB..MyTable -> SELECT * FROM MyDB.MyTable
```

For applications using a single database and multiple schemas, the migration path is the same and requires fewer rewrites because two-part names are already being used.

Applications that use multiple schemas and multiple databases will need to use multiple instances.

Use the `SHOW DATABASES` command to view databases or schemas in Aurora MySQL.

```
SHOW DATABASES;
```

For the preceding example, the result looks as shown following.

```
database

information_schema
Demo
mysql
performance_schema
sys
```

 Aurora MySQL also supports a `CREATE DATABASE` syntax reminder command.

```
SHOW CREATE DATABASE Demo;
```

For the preceding example, the result looks as shown following.

```
Database  Create Database
Demo      CREATE DATABASE `Demo` /*!40100 DEFAULT CHARACTER SET latin1 */
```

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.databasesschemas.mysql.examples"></a>

The following examples create a new table in a new database.

```
CREATE DATABASE NewDatabase;
```

```
USE NewDatabase;
```

```
CREATE TABLE NewTable
(
    NewColumn VARCHAR(20) NOT NULL PRIMARY KEY
);
```

```
INSERT INTO NewTable VALUES('NewValue');
```

```
SELECT * FROM NewTable;
```

## Summary
<a name="chap-sql-server-aurora-mysql.tsql.databasesschemas.summary"></a>

The following table summarizes the migration path for each architecture.


| Current object architecture | Migrate to Aurora MySQL  | Rewrites | 
| --- | --- | --- | 
|  Single database, all objects in dbo schema.  |  Single instance, single database or schema.  |  If the code already uses two-part object notation such as `dbo.<object>`, consider creating a `dbo` schema in Aurora MySQL to minimize code changes.  | 
|  Single database, objects in multiple schemas.  |  Single instance, multiple databases or schemas.  |  No identifier hierarchy rewrites needed. Code should be compatible with respect to the object hierarchy.  | 
|  Multiple databases, all objects in the `dbo` schema.  |  Single instance, multiple databases or schemas.  |  Identifier rewrite is required to remove the SQL Server schema name or the default dot. Change `SELECT * FROM MyDB..MyTable` to `SELECT * FROM MyDB.MyTable`.  | 
|  Multiple databases, objects in multiple schemas.  |  Multiple instances.  |  Connectivity between the instances will need to be implemented at the application level.  | 

For more information, see [CREATE DATABASE Statement](https://dev.mysql.com/doc/refman/5.7/en/create-database.html) in the *MySQL documentation*.

# Transactions for T-SQL
<a name="chap-sql-server-aurora-mysql.tsql.transactions"></a>

This topic provides reference information about transaction handling when migrating from Microsoft SQL Server 2019 to Amazon Aurora MySQL. You can gain insights into the key differences in transaction support, isolation levels, and syntax between these two database systems.


| Feature compatibility |  AWS SCT / AWS DMS automation level |  AWS SCT action code index | Key differences | 
| --- | --- | --- | --- | 
|   ![\[Three star feature compatibility\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-compatibility-3.png)   |   ![\[Four star automation level\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-automation-4.png)   |   [Transaction Isolation](chap-sql-server-aurora-mysql.tools.actioncode.md#chap-sql-server-aurora-mysql.tools.actioncode.transactionisolation)   |  Default isolation level is set to `REPEATABLE READ`. Default mechanism `CONSISTENT SNAPSHOT` is similar to `READ COMMITTED SNAPSHOT` isolation in SQL Server. Syntax and option differences.  | 

## SQL Server Usage
<a name="chap-sql-server-aurora-mysql.tsql.transactions.sqlserver"></a>

A *transaction* is a unit of work performed against a database and typically represents a change in the database. Transactions serve the following purposes:
+ Provide units of work that enable recovery from logical or physical system failures while keeping the database in a consistent state.
+ Provide units of work that enable recovery from failures while keeping a database in a consistent state when a logical or physical system failure occurs.
+ Provide isolation between users and programs accessing a database concurrently.

Transactions are an all-or-nothing unit of work. Each transactional unit of work must either complete, or it must rollback all data changes. Also, transactions must be isolated from other transactions. The results of the view of data for each transaction must conform to the defined database isolation level.

Database transactions must comply with ACID properties:
+  **Atomic** — Transactions are all-or-nothing. If any part of the transaction fails, the entire transaction fails and the database remains unchanged.
**Note**  
There are exceptions to this rule. For example, some constraint violations, for each ANSI definitions, shouldn’t cause a transaction rollback.
+  **Consistent** — All transactions must bring the database from one valid state to another valid state. Data must be valid according to all defined rules, constraints, triggers, and so on.
+  **Isolation** — Concurrent run of transactions must result in a system state that would occur if transactions were run sequentially.
**Note**  
There are several exceptions to this rule based on the lenience of the required isolation level.
+  **Durable** — After a transaction commits successfully and is acknowledged to the client, the engine must guarantee that its changes are persisted even in the event of power loss, system crashes, or any other errors.
**Note**  
By default, SQL Server uses the auto commit or implicit transactions mode set to ON. Every statement is treated as a transaction on its own unless a transaction was explicitly defined. This behavior is different than other engines like Oracle where, by default, every DML requires an explicit `COMMIT` statement to be persisted.

### Syntax
<a name="chap-sql-server-aurora-mysql.tsql.transactions.sqlserver.syntax"></a>

The following examples show the simplified syntax for the commands defining transaction boundaries.

Define the beginning of a transaction.

```
BEGIN TRAN | TRANSACTION [<transaction name>]
```

Commit work and the end of a transaction.

```
COMMIT WORK | [ TRAN | TRANSACTION [<transaction name>]]
```

Rollback work at the end of a transaction.

```
ROLLBACK WORK | [ TRAN | TRANSACTION [<transaction name>]]
```

SQL Server supports the standard ANSI isolation levels defined by the ANSI/ISO SQL standard (SQL92).

Each level provides a different approach for managing the concurrent run of transactions. The main purpose of a transaction isolation level is to manage the visibility of changed data as seen by other running transactions. Additionally, when concurrent transactions access the same data, the level of transaction isolation affects the way they interact with each other.
+  **Read uncommitted** — A current transaction can see uncommitted data from other transactions. If a transaction performs a rollback, all data is restored to its previous state.
+  **Read committed** — A transaction only sees data changes that were committed. Therefore, dirty reads aren’t possible. However, after issuing a commit, it would be visible to the current transaction while it’s still in a running state.
+  **Repeatable read** — A transaction sees data changes made by the other transactions only after both transactions issue a commit or are rolled back.
+  **Serializable** — This isolation level is the strictest because it doesn’t permit transaction overwrites of another transaction’s actions. Concurrent run of a set of serializable transactions is guaranteed to produce the same effect as running them sequentially in the same order.

The main difference between isolation levels is the phenomena they prevent from appearing. The three preventable phenomena are:
+  **Dirty reads** — A transaction can read data written by another transaction but not yet committed.
+  **Non-repeatable or fuzzy reads** — When reading the same data several times, a transaction can find the data has been modified by another transaction that has just committed. The same query ran twice can return different values for the same rows.
+  **Phantom or ghost reads** — Similar to a non-repeatable read, but it is related to new data created by another transaction. The same query ran twice can return different numbers of records.

The following table summarizes the four ANSI/ISO SQL standard (SQL92) isolation levels and indicates which phenomena are allowed or disallowed.


| Transaction isolation level | Dirty reads | Non-repeatable reads | Phantom reads | 
| --- | --- | --- | --- | 
|  Read uncommitted  |  Allowed  |  Allowed  |  Allowed  | 
|  Read committed  |  Disallowed  |  Allowed  |  Allowed  | 
|  Repeatable read  |  Disallowed  |  Disallowed  |  Allowed  | 
|  Serializable  |  Disallowed  |  Disallowed  |  Disallowed  | 

There are two common implementations for transaction isolation:
+  **Pessimistic isolation or locking** — Resources accessed by a transaction are locked for the duration of the transaction. Depending on the operation, resource, and transaction isolation level, other transactions can see changes made by the locking transaction, or they must wait for it to complete. With this mechanism, there is only one copy of the data for all transactions, which minimizes memory and disk resource consumption at the expense of transaction lock waits.
+  **Optimistic isolation (MVCC)** — Every transaction owns a set of the versions of the resources (typically rows) that it accessed. In this mode, transactions don’t have to wait for one another at the expense of increased memory and disk utilization. In this isolation mechanism, there is a chance that conflicts will arise when transactions attempt to commit. In case of a conflict, the application needs to be able to handle the rollback, and attempt a retry.

SQL Server implements both mechanisms. You can use them concurrently.

For optimistic isolation, SQL Server introduced two additional isolation levels: read-committed snapshot and snapshot.

Set the transaction isolation level using `SET` command. It affects the current run scope only.

```
SET TRANSACTION ISOLATION LEVEL { READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SNAPSHOT | SERIALIZABLE }
```

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.transactions.sqlserver.examples"></a>

The following example runs two DML statements within a serializable transaction.

```
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
INSERT INTO Table1
VALUES (1, 'A');
UPDATE Table2
  SET Column1 = 'Done'
WHERE KeyColumn = 1;
COMMIT TRANSACTION;
```

For more information, see [Transaction Isolation Levels (ODBC)](https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/transaction-isolation-levels?view=sql-server-ver15) and [SET TRANSACTION ISOLATION LEVEL (Transact-SQL)](https://docs.microsoft.com/en-us/sql/t-sql/statements/set-transaction-isolation-level-transact-sql?view=sql-server-ver15) in the *SQL Server documentation*.

## MySQL Usage
<a name="chap-sql-server-aurora-mysql.tsql.transactions.mysql"></a>

 Amazon Aurora MySQL-Compatible Edition (Aurora MySQL) supports the four transaction isolation levels specified in the SQL:1992 standard: `READ UNCOMMITTED`, `READ COMMITTED`, `REPEATABLE READ`, and `SERIALIZABLE`.

The simplified syntax for setting transaction boundaries in Aurora MySQL is shown following:

```
SET [SESSION] TRANSACTION ISOLATION LEVEL [READ WRITE | READ ONLY] | REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED | SERIALIZABLE]
```

**Note**  
Setting the `GLOBAL` isolation level isn’t supported in Aurora MySQL; only session scope can be changed. This behavior is similar to Oracle. Also, the default behavior of transactions is to use `REPEATABLE READ` and consistent reads. Applications designed to run with `READ COMMITTED` may need to be modified. Alternatively, explicitly change the default to `READ COMMITTED`.

The default isolation level for Aurora MySQL is `REPEATABLE READ`.

To set the transaction isolation level, you will need to set the `tx_isolation` parameter when using Aurora MySQL. For more information, see [Server Options](chap-sql-server-aurora-mysql.configuration.serveroptions.md).

**Note**  
 Amazon Relational Database Service (Amazon RDS) for MySQL 8 supports a new `innodb_deadlock_detect` dynamic variable. You can use this variable to turn off the deadlock detection. On high concurrency systems deadlock detection can cause a slowdown when numerous threads wait for the same lock. At times it may be more efficient to turn off deadlock detection and rely on the `innodb_lock_wait_timeout` setting for transaction rollback when a deadlock occurs.

Starting from MySQL 8, InnoDB supports `NOWAIT` and `SKIP LOCKED` options with `SELECT …​ FOR SHARE` and `SELECT …​ FOR UPDATE` locking read statements. `NOWAIT` causes the statement to return immediately if a requested row is locked by another transaction.

 `SKIP LOCKED` removes locked rows from the result set. `SELECT …​ FOR SHARE` replaces `SELECT …​ LOCK IN SHARE MODE` but `LOCK IN SHARE MODE` remains available for backward compatibility. The statements are equivalent. However, `FOR UPDATE` and `FOR SHARE` support `NOWAIT SKIP LOCKED` and `OF tbl_name` options. For more information, see [SELECT Statement](https://dev.mysql.com/doc/refman/8.0/en/select.html) in the *MySQL documentation*.

### Syntax
<a name="chap-sql-server-aurora-mysql.tsql.transactions.mysql.syntax"></a>

Simplified syntax for setting transaction boundaries:

```
SET [SESSION] TRANSACTION ISOLATION LEVEL [READ WRITE | READ ONLY] | REPEATABLE READ |
READ COMMITTED | READ UNCOMMITTED | SERIALIZABLE]
```

**Note**  
Setting a `GLOBAL` isolation level isn’t supported in Aurora MySQL. You can only change the session scope; similar to SQL Server `SET` scope. The default behavior of transactions is to use `REPEATABLE READ` and consistent reads. Applications designed to run with `READ COMMITTED` may need to be modified. Alternatively, they can explicitly change the default to `READ COMMITTED`.

In Aurora MySQL, you can optionally specify a transaction intent. Setting a transaction to `READ ONLY` turns off the transaction’s ability to modify or lock both transactional and non-transactional tables visible to other transactions, but the transaction can still modify or lock temporary tables. It also enables internal optimization to improve performance and concurrency. The default is `READ WRITE`.

Simplified syntax for the commands defining transaction boundaries:

```
START TRANSACTION WITH CONSISTENT SNAPSHOT | READ WRITE | READ ONLY
```

Or

```
BEGIN [WORK]
```

The `WITH CONSISTENT SNAPSHOT` option starts a consistent read transaction. The effect is the same as issuing a `START TRANSACTION` followed by a `SELECT` from any table. `WITH CONSISTENT SNAPSHOT` doesn’t change the transaction isolation level.

A consistent read uses snapshot information to make query results available based on a point in time regardless of modifications performed by concurrent transactions. If queried data has been changed by another transaction, the original data is reconstructed using the undo log. Consistent reads avoid locking issues that may reduce concurrency. With the `REPEATABLE READ` isolation level, the snapshot is based on the time the first read operation is performed. With the `READ COMMITTED` isolation level, the snapshot is reset to the time of each consistent read operation.

Use the following statement to commit work at the end of a transaction.

```
COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
```

Use the following statement to rollback work at the end of a transaction.

```
ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
```

One of the `ROLLBACK` options is `ROLLBACK TO SAVEPOINT<logical_name>`. This command will rollback all changes in current transaction up to the save point mentioned.

Create transaction save point during the transaction

```
SAVEPOINT <logical_name>
```

**Note**  
If the current transaction has a save point with the same name, the old save point is deleted and a new one is set.

 Aurora MySQL supports both auto commit and explicit commit modes. You can change mode using the `autocommit` system variable.

```
SET autocommit = {0 | 1}
```

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.transactions.mysql.examples"></a>

The following example runs two DML statements within a serializable transaction.

```
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
INSERT INTO Table1
VALUES (1, 'A');
UPDATE Table2
SET Column1 = 'Done'
WHERE KeyColumn = 1;
COMMIT;
```

## Summary
<a name="chap-sql-server-aurora-mysql.tsql.transactions.summary"></a>

The following table summarizes the key differences in transaction support and syntax when migrating from SQL Server to Aurora MySQL.


| Transaction property | SQL Server |  Aurora MySQL  | Comments | 
| --- | --- | --- | --- | 
|  Default isolation level  |   `READ COMMITTED`   |   `REPEATABLE READ`   |  The Aurora MySQL default isolation level is stricter than SQL Server. Evaluate application needs and set appropriately.  | 
|  Initialize transaction syntax  |   `BEGIN TRAN` or `BEGIN TRANSACTION`   |   `START TRANSACTION`   |  Code rewrite is required from `BEGIN` to `START`. If using the shorthand `TRAN`, rewrite to `TRANSACTION`.  | 
|  Default isolation mechanism  |  Pessimistic lock based  |  Lock based for writes, consistent read for `SELECT` statements.  |  The Aurora MySQL default mode is similar to the `READ COMMITTED SNAPSHOT` isolation in SQL Server.  | 
|  Commit transaction  |   `COMMIT [WORK\|TRAN\|TRANSACTION]`   |   `COMMIT [WORK]`   |  If you only use `COMMIT` or `COMMIT WORK`, no change is needed. Otherwise, rewrite `TRAN` and `TRANSACTION` to `WORK`.  | 
|  Rollback transaction  |   `ROLLBACK [WORK \|[ TRAN \| TRANSACTION]`   |   `ROLLBACK [WORK]`   |  If you only use `ROLLBACK` or `ROLLBACK WORK`, no change is needed. Otherwise, rewrite `TRAN` and `TRANSACTION` to `WORK`.  | 
|  Set autocommit off or on  |   `SET IMPLICIT_TRANSACTIONS OFF \| ON`   |   `SET autocommit = 0 \| 1`   |  For more information, see [Session Options](chap-sql-server-aurora-mysql.configuration.sessionoptions.md).  | 
|  ANSI isolation  |   `REPEATABLE READ` \$1 `READ COMMITTED` \$1 `READ UNCOMMITTED` \$1 `SERIALIZABLE`   |   `REPEATABLE READ` \$1 `READ COMMITTED` \$1 `READ UNCOMMITTED` \$1 `SERIALIZABLE`   |  Compatible syntax.  | 
|  MVCC  |   `SNAPSHOT` and `READ COMMITTED SNAPSHOT`   |   `WITH CONSISTENT SNAPSHOT`   |   Aurora MySQL consistent read in `READ COMMITTED` isolation is similar to `READ COMMITTED SNAPSHOT` in SQL Server.  | 
|  Nested transactions  |  Supported, view level with `@@trancount`   |  Not supported  |  Starting a new transaction in Aurora MySQL while another transaction is active causes a `COMMIT` of the previous transaction.  | 
|  Transaction chaining  |  Not supported  |  Causes a new transaction to open immediately upon transaction completion.  |  | 
|  Transaction release  |  Not supported  |  Causes the client session to disconnect upon transaction completion.  |  | 

For more information, see [Transaction Isolation Levels](https://dev.mysql.com/doc/refman/5.7/en/innodb-transaction-isolation-levels.html) in the *MySQL documentation*.

# DELETE and UPDATE FROM for T-SQL
<a name="chap-sql-server-aurora-mysql.tsql.deleteupdate"></a>

This topic provides reference information about the differences in SQL syntax and functionality between Microsoft SQL Server 2019 and Amazon Aurora MySQL, specifically regarding DELETE and UPDATE statements with joins. You can use this information to understand how to adapt your existing SQL Server queries when migrating to Aurora MySQL.


| Feature compatibility |  AWS SCT / AWS DMS automation level |  AWS SCT action code index | Key differences | 
| --- | --- | --- | --- | 
|   ![\[Four star feature compatibility\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-compatibility-4.png)   |   ![\[Four star automation level\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-automation-4.png)   |  N/A  |  Rewrite to use subqueries.  | 

## SQL Server Usage
<a name="chap-sql-server-aurora-mysql.tsql.deleteupdate.sqlserver"></a>

SQL Server supports an extension to the ANSI standard that allows using an additional `FROM` clause in `UPDATE` and `DELETE` statements.

You can use this additional `FROM` clause to limit the number of modified rows by joining the table being updated, or deleted from, to one or more other tables. This functionality is similar to using a `WHERE` clause with a derived table subquery. For `UPDATE`, you can use this syntax to set multiple column values simultaneously without repeating the subquery for every column.

However, these statements can introduce logical inconsistencies if a row in an updated table is matched to more than one row in a joined table. The current implementation chooses an arbitrary value from the set of potential values and is non deterministic.

### Syntax
<a name="chap-sql-server-aurora-mysql.tsql.deleteupdate.sqlserver.syntax"></a>

```
UPDATE <Table Name>
SET <Column Name> = <Expression> ,...
FROM <Table Source>
WHERE <Filter Predicate>;
```

```
DELETE FROM <Table Name>
FROM <Table Source>
WHERE <Filter Predicate>;
```

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.deleteupdate.sqlserver.examples"></a>

Delete customers with no orders.

```
CREATE TABLE Customers
(
    Customer VARCHAR(20) PRIMARY KEY
);
```

```
INSERT INTO Customers
VALUES
('John'),
('Jim'),
('Jack')
```

```
CREATE TABLE Orders
(
    OrderID INT NOT NULL PRIMARY KEY,
    Customer VARCHAR(20) NOT NULL,
    OrderDate DATE NOT NULL
);
```

```
INSERT INTO Orders (OrderID, Customer, OrderDate)
VALUES
(1, 'Jim', '20180401'),
(2, 'Jack', '20180402');
```

```
DELETE FROM Customers
FROM Customers AS C
    LEFT OUTER JOIN
    Orders AS O
    ON O.Customer = C.Customer
WHERE O.OrderID IS NULL;
```

```
SELECT *
FROM Customers;
```

For the preceding examples, the result looks as shown following.

```
Customer

Jim
Jack
```

Update multiple columns in `Orders` based on the values in `OrderCorrections`.

```
CREATE TABLE OrderCorrections
(
    OrderID INT NOT NULL PRIMARY KEY,
    Customer VARCHAR(20) NOT NULL,
    OrderDate DATE NOT NULL
);
```

```
INSERT INTO OrderCorrections
VALUES (1, 'Jack', '20180324');
```

```
UPDATE O
SET Customer = OC.Customer,
    OrderDate = OC.OrderDate
FROM Orders AS O
    INNER JOIN
    OrderCorrections AS OC
    ON O.OrderID = OD.OrderID;
```

```
SELECT *
FROM Orders;
```

For the preceding example, the result looks as shown following.

```
Customer  OrderDate
Jack      2018-03-24
Jack      2018-04-02
```

For more information, see [UPDATE (Transact-SQL)](https://docs.microsoft.com/en-us/sql/t-sql/queries/update-transact-sql?view=sql-server-ver15), [DELETE (Transact-SQL)](https://docs.microsoft.com/en-us/sql/t-sql/statements/delete-transact-sql?view=sql-server-ver15), and [FROM clause plus JOIN, APPLY, PIVOT (Transact-SQL)](https://docs.microsoft.com/en-us/sql/t-sql/queries/from-transact-sql?view=sql-server-ver15) in the *SQL Server documentation*.

## MySQL Usage
<a name="chap-sql-server-aurora-mysql.tsql.deleteupdate.mysql"></a>

 Amazon Aurora MySQL-Compatible Edition (Aurora MySQL) doesn’t support `DELETE` and `UPDATE FROM` syntax.

### Migration Considerations
<a name="chap-sql-server-aurora-mysql.tsql.deleteupdate.mysql.considerations"></a>

You can easily rewrite the `DELETE` and `UPDATE FROM` statements as subqueries.

For `DELETE`, place the subqueries in the `WHERE` clause.

For `UPDATE`, place the subqueries either in the `WHERE` or `SET` clause.

**Note**  
When rewriting `UPDATE FROM` queries, include a `WHERE` clause to limit which rows are updated even if the SQL Server version (where the rows were limited by the join condition) did not have one.

For `DELETE` statements, the workaround is simple and, in most cases, easier to read and understand.

For `UPDATE` statements, the workaround involves repeating the correlated subquery for each column being set.

Although this approach makes the code longer and harder to read, it does solve the logical challenges associated with updates having multiple matched rows in the joined tables.

In the current implementation, the SQL Server engine silently chooses an arbitrary value if more than one value exists for the same row.

When you rewrite the statement to use a correlated subquery, such as in the following example, if more than one value is returned from the sub query, a SQL error will be raised: `SQL Error [1242] [21000]: Subquery returns more than 1 row`.

Consult the documentation for the Aurora MySQL `UPDATE` statement as there are significant processing differences from SQL Server. For example:
+ In Aurora MySQL, you can update multiple tables in a single `UPDATE` statement.
+  `UPDATE` expressions are evaluated in order from left to right. This behavior differs from SQL Server and the ANSI standard, which require an all-at-once evaluation.

For example, in the statement `UPDATE Table SET Col1 = Col1 + 1, Col2 = Col1`, `Col2` is set to the new value of `Col1`. The end result is `Col1 = Col2`.

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.deleteupdate.mysql.examples"></a>

Delete customers with no orders.

```
CREATE TABLE Customers
(
    Customer VARCHAR(20) PRIMARY KEY
);
```

```
INSERT INTO Customers
VALUES
('John'),
('Jim'),
('Jack')
```

```
CREATE TABLE Orders
(
    OrderID INT NOT NULL PRIMARY KEY,
    Customer VARCHAR(20) NOT NULL,
    OrderDate DATE NOT NULL
);
```

```
INSERT INTO Orders (OrderID, Customer, OrderDate)
VALUES
(1, 'Jim', '20180401'),
(2, 'Jack', '20180402');
```

```
DELETE FROM Customers
WHERE Customer NOT IN (
    SELECT Customer
    FROM Orders
);
```

```
SELECT *
FROM Customers;
```

For the preceding example, the result looks as shown following.

```
Customer

Jim
Jack
```

Update multiple columns in `Orders` based on the values in `OrderCorrections`.

```
CREATE TABLE OrderCorrections
(
    OrderID INT NOT NULL PRIMARY KEY,
    Customer VARCHAR(20) NOT NULL,
    OrderDate DATE NOT NULL
);
```

```
INSERT INTO OrderCorrections
VALUES (1, 'Jack', '20180324');
```

```
UPDATE Orders
SET Customer = (
    SELECT Customer
    FROM OrderCorrections AS OC
    WHERE Orders.OrderID = OC.OrderID
),
OrderDate = (
    SELECT OrderDate
    FROM OrderCorrections AS OC
    WHERE Orders.OrderID = OC.OrderID
IN (
    SELECT OrderID
    FROM OrderCorrections
);
```

```
SELECT *
FROM Orders;
```

For the preceding example, the result looks as shown following.

```
Customer  OrderDate
Jack      2018-03-24
Jack      2018-04-02
```

## Summary
<a name="chap-sql-server-aurora-mysql.tsql.deleteupdate.summary"></a>

The following table identifies similarities, differences, and key migration considerations.


| Feature | SQL Server |  Aurora MySQL  | Comments | 
| --- | --- | --- | --- | 
|  Join as part of `DELETE`   |   `DELETE FROM …​ FROM`   |  N/A  |  Rewrite to use the `WHERE` clause with a subquery.  | 
|  Join as part of `UPDATE`   |   `UPDATE …​ FROM`   |  N/A  |  Rewrite to use correlated subquery in the `SET` clause and add the `WHERE` clause to limit updates set.  | 

For more information, see [UPDATE Statement](https://dev.mysql.com/doc/refman/5.7/en/update.html) and [DELETE Statement](https://dev.mysql.com/doc/refman/5.7/en/delete.html) in the *MySQL documentation*.

# Stored procedures for T-SQL
<a name="chap-sql-server-aurora-mysql.tsql.storedprocedures"></a>

This topic provides reference content comparing stored procedures in Microsoft SQL Server 2019 and Amazon Aurora MySQL. You can understand the key differences and similarities between these two database systems' implementations of stored procedures.


| Feature compatibility |  AWS SCT / AWS DMS automation level |  AWS SCT action code index | Key differences | 
| --- | --- | --- | --- | 
|   ![\[Three star feature compatibility\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-compatibility-3.png)   |   ![\[Four star automation level\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-automation-4.png)   |   [Stored Procedures](chap-sql-server-aurora-mysql.tools.actioncode.md#chap-sql-server-aurora-mysql.tools.actioncode.storedprocedures)   |  No support for table-valued parameters. Syntax and option differences.  | 

## SQL Server Usage
<a name="chap-sql-server-aurora-mysql.tsql.storedprocedures.sqlserver"></a>

Stored procedures are encapsulated, persisted code modules you can run using the `EXECUTE` T-SQL statement. They may have multiple input and output parameters. Table-valued user-defined types can be used as input parameters. IN is the default direction for parameters, but `OUT` must be explicitly specified. You can specify parameters as both `IN` and `OUT`.

In SQL Server, you can run stored procedures in any security context using the `EXECUTE AS` option. They can be explicitly recompiled for every run using the RECOMPILE option and can be encrypted in the database using the `ENCRYPTION` option to prevent unauthorized access to the source code.

SQL Server provides a unique feature that allows you to use a stored procedure as an input to an `INSERT` statement. When you use this feature, only the first row in the data set returned by the stored procedure is evaluated.

As part of the stored procedure syntax, SQL Server supports a default output integer parameter that can be specified along with the `RETURN` command, for example, `RETURN -1`. It’s typically used to signal status or error to the calling scope, which can use the syntax `EXEC @Parameter = <Stored Procedure Name>` to retrieve the `RETURN` value, without explicitly stating it as part of the parameter list.

### Syntax
<a name="chap-sql-server-aurora-mysql.tsql.storedprocedures.sqlserver.syntax"></a>

```
CREATE [ OR ALTER ] { PROC | PROCEDURE } <Procedure Name>
[<Parameter List>
[ WITH [ ENCRYPTION ]|[ RECOMPILE ]|[ EXECUTE AS ...]]
AS {
[ BEGIN ]
<SQL Code Body>
[RETURN [<Integer Value>]]
[ END ] }[;]
```

### Creating and Running a Stored Procedure
<a name="chap-sql-server-aurora-mysql.tsql.storedprocedures.sqlserver.create"></a>

Create a simple parameterized stored procedure to validate the basic format of an email.

```
CREATE PROCEDURE ValidateEmail
@Email VARCHAR(128), @IsValid BIT = 0 OUT
AS
BEGIN
IF @Email LIKE N'%@%'
    SET @IsValid = 1
ELSE
    SET @IsValid = 0
RETURN
END;
```

Run the procedure.

```
DECLARE @IsValid BIT
EXECUTE [ValidateEmail]
    @Email = 'X@y.com', @IsValid = @IsValid OUT;
SELECT @IsValid;

-- Returns 1
```

```
EXECUTE [ValidateEmail]
    @Email = 'Xy.com', @IsValid = @IsValid OUT;
SELECT @IsValid;

-- Returns 0
```

Create a stored procedure that uses `RETURN` to pass the application an error value.

```
CREATE PROCEDURE ProcessImportBatch
@BatchID INT
AS
BEGIN
    BEGIN TRY
        EXECUTE Step1 @BatchID
        EXECUTE Step2 @BatchID
        EXECUTE Step3 @BatchID
    END TRY
    BEGIN CATCH
        IF ERROR_NUMBER() = 235
            RETURN -1 -- indicate special condition
        ELSE
            THROW -- handle error normally
    END CATCH
END
```

### Using a Table-Valued Input Parameter
<a name="chap-sql-server-aurora-mysql.tsql.storedprocedures.sqlserver.tablevalued"></a>

Create and populate the `OrderItems` table.

```
CREATE TABLE OrderItems(
    OrderID INT NOT NULL,
    Item VARCHAR(20) NOT NULL,
    Quantity SMALLINT NOT NULL,
    PRIMARY KEY(OrderID, Item)
);
```

```
INSERT INTO OrderItems (OrderID, Item, Quantity)
VALUES
(1, 'M8 Bolt', 100),
(2, 'M8 Nut', 100),
(3, 'M8 Washer', 200),
(3, 'M6 Washer', 100);
```

Create a table-valued type for the `OrderItem` table-valued parameter.

```
CREATE TYPE OrderItems
AS TABLE
(
    OrderID INT NOT NULL,
    Item VARCHAR(20) NOT NULL,
    Quantity SMALLINT NOT NULL,
    PRIMARY KEY(OrderID, Item)
);
```

Create a procedure to process order items.

```
CREATE PROCEDURE InsertOrderItems
@OrderItems AS OrderItems READONLY
AS
BEGIN
    INSERT INTO OrderItems(OrderID, Item, Quantity)
    SELECT OrderID,
        Item,
        Quantity
    FROM @OrderItems
END;
```

Instantiate and populate the table valued variable and pass the data set to the stored procedure.

```
DECLARE @OrderItems AS OrderItems;

INSERT INTO @OrderItems ([OrderID], [Item], [Quantity])
VALUES
(1, 'M8 Bolt', 100),
(1, 'M8 Nut', 100),
(1, M8 Washer, 200);

EXECUTE [InsertOrderItems]
    @OrderItems = @OrderItems;

(3 rows affected)
   Item       Quantity
1  M8 Bolt    100
2  M8 Nut     100
3  M8 Washer  200
```

### INSERT…​ EXEC Syntax
<a name="chap-sql-server-aurora-mysql.tsql.storedprocedures.sqlserver.insert"></a>

```
INSERT INTO <MyTable>
EXECUTE <MyStoredProcedure>;
```

For more information, see [CREATE PROCEDURE (Transact-SQL)](https://docs.microsoft.com/en-us/sql/t-sql/statements/create-procedure-transact-sql?view=sql-server-ver15) in the *SQL Server documentation*.

## MySQL Usage
<a name="chap-sql-server-aurora-mysql.tsql.storedprocedures.mysql"></a>

 Amazon Aurora MySQL-Compatible Edition (Aurora MySQL) stored procedures provide similar functionality to SQL Server stored procedures.

As with SQL Server, Aurora MySQL supports security run context. It also supports input, output, and bi-directional parameters.

Stored procedures are typically used for: \$1 **Code reuse** — Stored procedures offer a convenient code encapsulation and reuse mechanism for multiple applications, potentially written in various languages, requiring the same database operations. \$1 **Security management** — By allowing access to base tables only through stored procedures, administrators can manage auditing and access permissions. This approach minimizes dependencies between application code and database code. Administrators can use stored procedures to process business rules and to perform auditing and logging. \$1 **Performance improvements** — Full SQL query text doesn’t need to be transferred from the client to the database.

Stored procedures, triggers, and user-defined functions in Aurora MySQL are collectively referred to as *stored routines*. When binary logging is enabled, MySQL `SUPER` privilege is required to run stored routines. However, you can run stored routines with binary logging enabled without `SUPER` privilege by setting `thelog_bin_trust_function_creators` parameter to true for the DB parameter group for your MySQL instance.

 Aurora MySQL permits stored routines to contain control flow, DML, DDL, and transaction management statements including `START TRANSACTION`, `COMMIT`, and `ROLLBACK`.

### Syntax
<a name="chap-sql-server-aurora-mysql.tsql.storedprocedures.mysql.syntax"></a>

```
CREATE [DEFINER = { user | CURRENT_USER }] PROCEDURE sp_name
([ IN | OUT | INOUT ] <Parameter> <Parameter Data Type> ... )
COMMENT 'string' |
LANGUAGE SQL |
[NOT] DETERMINISTIC |
{ CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA } |
SQL SECURITY { DEFINER | INVOKER }
<Stored Procedure Code Body>
```

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.storedprocedures.mysql.examples"></a>

Replace RETURN value parameter with standard OUTPUT parameters.

```
CREATE PROCEDURE ProcessImportBatch()
IN @BatchID INT, OUT @ErrorNumber INT
BEGIN
    CALL Step1 (@BatchID)
    CALL Step2 (@BatchID)
    CALL Step3 (@BatchID)
IF error_count > 1
    SET @ErrorNumber = -1 -- indicate special condition
END
```

Use a `LOOP` cursor with a source table to replace table valued parameters.

Create the `OrderItems` table.

```
CREATE TABLE OrderItems
(
    OrderID INT NOT NULL,
    Item VARCHAR(20) NOT NULL,
    Quantity SMALLINT NOT NULL,
    PRIMARY KEY(OrderID, Item)
);
```

Create and populate `SourceTable` as a temporary data store for incoming rows.

```
CREATE TABLE SourceTable
(
    OrderID INT,
    Item VARCHAR(20),
    Quantity SMALLINT,
    PRIMARY KEY (OrderID, Item)
);
```

```
INSERT INTO SourceTable (OrderID, Item, Quantity)
VALUES
(1, 'M8 Bolt', 100),
(2, 'M8 Nut', 100),
(3, 'M8 Washer', 200);
```

Create a procedure to loop through all rows in `SourceTable` and insert them into the `OrderItems` table.

```
CREATE PROCEDURE LoopItems()
BEGIN
    DECLARE done INT DEFAULT FALSE;
    DECLARE var_OrderID INT;
    DECLARE var_Item VARCHAR(20);
    DECLARE var_Quantity SMALLINT;
    DECLARE ItemCursor CURSOR
        FOR SELECT OrderID,
            Item,
            Quantity
        FROM SourceTable;
    DECLARE CONTINUE HANDLER
        FOR NOT FOUND SET done = TRUE;
    OPEN ItemCursor;
    CursorStart: LOOP
    FETCH NEXT FROM ItemCursor
        INTO var_OrderID, var_Item, var_Quantity;
    IF Done THEN LEAVE CursorStart;
    END IF;
        INSERT INTO OrderItems (OrderID, Item, Quantity)
        VALUES (var_OrderID, var_Item, var_Quantity);
    END LOOP;
    CLOSE ItemCursor;
END;
```

Call the stored procedure.

```
CALL LoopItems();
```

Select all rows from the OrderItems table.

```
SELECT * FROM OrderItems;
```

For the preceding example, the result looks as shown following.

```
OrderID  Item       Quantity
1        M8 Bolt    100
2        M8 Nut     100
3        M8 Washer  200
```

## Summary
<a name="chap-sql-server-aurora-mysql.tsql.storedprocedures.summary"></a>

The following table summarizes the differences between MySQL Stored Procedures and SQL Server Stored Procedures.


| Feature | SQL Server |  Aurora MySQL  | Workaround | 
| --- | --- | --- | --- | 
|  General `CREATE` syntax differences  |  <pre>CREATE PROC|PROCEDURE<br /><Procedure Name><br />@Parameter1 <Type>,<br />...n<br />AS<br /><Body></pre>  |  <pre>CREATE PROCEDURE<br /><Procedure Name><br />(Parameter1<br /><Type>,...n)<br /><Body></pre>  |  Rewrite stored procedure creation scripts to use `PROCEDURE` instead of `PROC`. Rewrite stored procedure creation scripts to omit the `AS` keyword. Rewrite stored procedure parameters to not use the `@` symbol in parameter names. Add parentheses around the parameter declaration. Rewrite stored procedure parameter direction `OUTPUT` to `OUT` or `INOUT` for bidirectional parameters. `IN` is the parameter direction for both MySQL and SQL Server.  | 
|  Security context  |  <pre>{ EXEC | EXECUTE } AS<br />{ CALLER | SELF | OWNER |<br />'user_name' }</pre>  |  <pre>DEFINER = 'user' |<br />CURRENT_USER</pre> in conjunction with <pre>SQL SECURITY {<br />DEFINER | INVOKER }</pre>  |  For stored procedures that use an explicit user name, rewrite the code from `EXECUTE AS 'user'` to `DEFINER = 'user'` and `SQL SECURITY DEFINER`. For stored procedures that use the `CALLER` option, rewrite the code to include `SQL SECURITY INVOKER`. For stored procedures that use the `SELF` option, rewrite the code to `DEFINER = CURRENT_USER` and `SQL SECURITY DEFINER`. Unlike SQL Server, `OWNER`s can’t be specified and must be explicitly named.  | 
|  Encryption  |  Use the `WITH ENCRYPTION` option.  |  Not supported in Aurora MySQL.  |  | 
|  Parameter direction  |   `IN` and `OUT\|OUTPUT`, by default `OUT` can be used as `IN` as well.  |   `IN`, `OUT`, and `INOUT`   |  Although the functionality of these parameters is the same for SQL Server and MySQL, make sure that you rewrite the code for syntax compliance. Use `OUT` instead of `OUTPUT`. Use `INOUT` instead of `OUT` for bidirectional parameters.  | 
|  Recompile  |  Use the `WITH RECOMPILE` option.  |  Not supported in Aurora MySQL.  |  | 
|  Table-valued parameters  |  Use declared table type user-defined parameters.  |  Not supported in Aurora MySQL.  |  See the preceding example for a workaround.  | 
|   `INSERT…​ EXEC`   |  Use the output of the stored procedure as input to an `INSERT` statement.  |  Not supported in Aurora MySQL.  |  Use tables to hold the data or pass string parameters formatted as CSV, XML, JSON (or any other convenient format) and then parse the parameters before the `INSERT` statement.  | 
|  Additional restrictions  |  Use `BULK INSERT` to load data from text file.  |  The LOAD DATA statement isn’t allowed in stored procedures.  |  | 
|   `RETURN` value  |   `RETURN <Integer Value>`   |  Not supported.  |  Use a standard `OUTPUT` parameter instead.  | 

For more information, see [Stored Procedures and Functions](https://dev.mysql.com/doc/refman/5.7/en/faqs-stored-procs.html) and [CREATE PROCEDURE and CREATE FUNCTION Statements](https://dev.mysql.com/doc/refman/5.7/en/create-procedure.html) in the *MySQL documentation*.

# Error handling for T-SQL
<a name="chap-sql-server-aurora-mysql.tsql.errorhandling"></a>

This topic provides reference content comparing error handling approaches between Microsoft SQL Server 2019 and Amazon Aurora MySQL. You can gain insights into the differences in error handling paradigms, syntax, and capabilities between these two database systems.


| Feature compatibility |  AWS SCT / AWS DMS automation level |  AWS SCT action code index | Key differences | 
| --- | --- | --- | --- | 
|   ![\[Four star feature compatibility\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-compatibility-4.png)   |   ![\[Four star automation level\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-automation-4.png)   |   [Error Handling](chap-sql-server-aurora-mysql.tools.actioncode.md#chap-sql-server-aurora-mysql.tools.actioncode.errorhandling)   |  Different paradigm and syntax requires rewrite of error handling code.  | 

## SQL Server Usage
<a name="chap-sql-server-aurora-mysql.tsql.errorhandling.sqlserver"></a>

SQL Server error handling capabilities have significantly improved throughout the years. However, previous features are retained for backward compatibility.

Before SQL Server 2008, only very basic error handling features were available. `RAISERROR` was the primary statement used for error handling.

Starting from SQL Server 2008, SQL Server has added extensive .NET-like error handling capabilities including `TRY/CATCH` blocks, `THROW` statements, the `FORMATMESSAGE` function, and a set of system functions that return metadata for the current error condition.

### TRY/CATCH Blocks
<a name="chap-sql-server-aurora-mysql.tsql.errorhandling.sqlserver.trycatch"></a>

 `TRY/CATCH` blocks implement error handling similar to Microsoft Visual C\$1 and Microsoft Visual C\$1\$1. `TRY …​ END TRY` statement blocks can contain T-SQL statements.

If an error is raised by any of the statements within the `TRY …​ END TRY` block, the run stops and is moved to the nearest set of statements that are bounded by a `CATCH …​ END CATCH` block.

 **Syntax** 

```
BEGIN TRY
<Set of SQL Statements>
END TRY
BEGIN CATCH
<Set of SQL Error Handling Statements>
END CATCH
```

### THROW
<a name="chap-sql-server-aurora-mysql.tsql.errorhandling.sqlserver.throw"></a>

The `THROW` statement raises an exception and transfers run of the `TRY …​ END TRY` block of statements to the associated `CATCH …​ END CATCH` block of statements.

Throw accepts either constant literals or variables for all parameters.

 **Syntax** 

```
THROW [Error Number>, <Error Message>, < Error State>] [;]
```

 **Examples** 

Use `TRY/CATCH` error blocks to handle key violations.

```
CREATE TABLE ErrorTest (Col1 INT NOT NULL PRIMARY KEY);
```

```
BEGIN TRY
    BEGIN TRANSACTION
        INSERT INTO ErrorTest(Col1) VALUES(1);
        INSERT INTO ErrorTest(Col1) VALUES(2);
        INSERT INTO ErrorTest(Col1) VALUES(1);
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    THROW; -- Throw with no parameters = RETHROW
END CATCH;
```

```
(1 row affected)
(1 row affected)
(0 rows affected)
Msg 2627, Level 14, State 1, Line 7
Violation of PRIMARY KEY constraint 'PK__ErrorTes__A259EE54D8676973'.
Cannot insert duplicate key in object 'dbo.ErrorTest'. The duplicate key value is (1).
```

**Note**  
Contrary to what many SQL developers believe, the values 1 and 2 are indeed inserted into `ErrorTestTable` in the preceding example. This behavior is in accordance with ANSI specifications stating that a constraint violation shouldn’t roll back an entire transaction.

Use `THROW` with variables.

```
BEGIN TRY
BEGIN TRANSACTION
INSERT INTO ErrorTest(Col1) VALUES(1);
INSERT INTO ErrorTest(Col1) VALUES(2);
INSERT INTO ErrorTest(Col1) VALUES(1);
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
DECLARE @CustomMessage VARCHAR(1000),
    @CustomError INT,
    @CustomState INT;
SET @CustomMessage = 'My Custom Text ' + ERROR_MESSAGE();
SET @CustomError = 54321;
SET @CustomState = 1;
THROW @CustomError, @CustomMessage, @CustomState;
END CATCH;
```

```
(0 rows affected)
Msg 54321, Level 16, State 1, Line 19
My Custom Text Violation of PRIMARY KEY constraint 'PK__ErrorTes__A259EE545CBDBB9A'.
Cannot insert duplicate key in object 'dbo.ErrorTest'. The duplicate key value is (1).
```

### RAISERROR
<a name="chap-sql-server-aurora-mysql.tsql.errorhandling.sqlserver.raiserror"></a>

The `RAISERROR` statement is used to explicitly raise an error message, similar to `THROW`. It causes an error state for the running session and forwards run to either the calling scope or, if the error occurred within a `TRY …​ END TRY` block, to the associated `CATCH …​ END CATCH` block. `RAISERROR` can reference a user-defined message stored in the `sys.messages` system table or can be used with dynamic message text.

The key differences between `THROW` and `RAISERROR` are:
+ Message IDs passed to `RAISERROR` must exist in the `sys.messages` system table. The error number parameter passed to `THROW` doesn’t.
+  `RAISERROR` message text may contain printf formatting styles. The message text of `THROW` may not.
+  `RAISERROR` uses the severity parameter for the error returned. For `THROW`, severity is always 16.

 **Syntax** 

```
RAISERROR (<Message ID>|<Message Text> ,<Message Severity> ,<Message State>
[WITH option [<Option List>]])
```

 **Example** 

Raise a custom error.

```
RAISERROR (N'This is a custom error message with severity 10 and state 1.', 10, 1)
```

### FORMATMESSAGE
<a name="chap-sql-server-aurora-mysql.tsql.errorhandling.sqlserver.formatmessage"></a>

 `FORMATMESSAGE` returns a sting message consisting of an existing error message in the `sys.messages` system table, or from a text string, using the optional parameter list replacements. The `FORMATMESSAGE` statement is similar to the `RAISERROR` statement.

 **Syntax** 

```
FORMATMESSAGE (<Message Number> | <Message String>, <Parameter List>)
```

### Error State Functions
<a name="chap-sql-server-aurora-mysql.tsql.errorhandling.sqlserver.examples"></a>

SQL Server provides the following error state functions:
+ ERROR\$1LINE
+ ERROR\$1MESSAGE
+ ERROR\$1NUMBER
+ ERROR\$1PROCEDURE
+ ERROR\$1SEVERITY
+ ERROR\$1STATE
+ @@ERROR

 **Examples** 

Use error state functions within a `CATCH` block.

```
CREATE TABLE ErrorTest (Col1 INT NOT NULL PRIMARY KEY);
```

```
BEGIN TRY;
    BEGIN TRANSACTION;
        INSERT INTO ErrorTest(Col1) VALUES(1);
        INSERT INTO ErrorTest(Col1) VALUES(2);
        INSERT INTO ErrorTest(Col1) VALUES(1);
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    SELECT ERROR_LINE(),
        ERROR_MESSAGE(),
        ERROR_NUMBER(),
        ERROR_PROCEDURE(),
        ERROR_SEVERITY(),
        ERROR_STATE(),
        @@Error;
THROW;
END CATCH;
```

```
6
Violation of PRIMARY KEY constraint 'PK__ErrorTes__A259EE543C8912D8'.
Cannot insert duplicate key in object 'dbo.ErrorTest'.
The duplicate key value is (1).
2627
NULL
14
1
2627
```

```
(1 row affected)
(1 row affected)
(0 rows affected)
(1 row affected)
Msg 2627, Level 14, State 1, Line 25
Violation of PRIMARY KEY constraint 'PK__ErrorTes__A259EE543C8912D8'.
Cannot insert duplicate key in object 'dbo.ErrorTest'.
The duplicate key value is (1).
```

For more information, see [RAISERROR (Transact-SQL)](https://docs.microsoft.com/en-us/sql/t-sql/language-elements/raiserror-transact-sql?view=sql-server-ver15), [TRY…​CATCH (Transact-SQL)](https://docs.microsoft.com/en-us/sql/t-sql/language-elements/try-catch-transact-sql?view=sql-server-ver15), and [THROW (Transact-SQL)](https://docs.microsoft.com/en-us/sql/t-sql/language-elements/throw-transact-sql?view=sql-server-ver15) in the *SQL Server documentation*.

## MySQL Usage
<a name="chap-sql-server-aurora-mysql.tsql.errorhandling.mysql"></a>

 Amazon Aurora MySQL-Compatible Edition (Aurora MySQL) offers a rich error handling framework with a different paradigm than SQL Server. The Aurora MySQL terminology is:
+  `CONDITION` — The equivalent of an `ERROR` in SQL Server.
+  `HANDLER` — An object that can handle conditions and perform actions.
+  `DIAGNOSTICS` — The metadata about the `CONDITION`.
+  `SIGNAL` and `RESIGNAL` — Statements similar to `THROW` and `RAISERROR` in SQL Server.

Errors in Aurora MySQL are identified by the follow items:
+ A numeric error code specific to MySQL and, therefore, is not compatible with other database systems.
+ A five character `SQLSTATE` value that uses the ANSI SQL and ODBC standard error conditions.
**Note**  
Not every MySQL error number has a corresponding `SQLSTATE` value. For errors that don’t have a corresponding `SQLSTATE`, the general `HY000` error is used.
+ A textual message string that describes the nature of the error.

### DECLARE …​ CONDITION
<a name="chap-sql-server-aurora-mysql.tsql.errorhandling.mysql.declare"></a>

The `DECLARE …​ CONDITION` statement declares a named error condition and associates the name with a condition that requires handling. You can reference this declared name in subsequent `DECLARE …​ HANDLER` statements.

 **Syntax** 

```
DECLARE <Condition Name> CONDITION
FOR <Condition Value>
```

```
<Condition Value> = <MySQL Error Code> | <SQLSTATE [VALUE] <SQLState Value>
```

 **Examples** 

Declare a condition for MySQL error 1051 (Unknown table error).

```
DECLARE TableDoesNotExist CONDITION FOR 1051;
```

Declare a condition for SQL State 42S02 (Base table or view not found) .

**Note**  
This SQLState error corresponds to the MySQL Error 1051.

```
DECLARE TableDoesNotExist CONDITION FOR SQLSTATE VALUE '42S02';
```

### DECLARE …​ HANDLER
<a name="chap-sql-server-aurora-mysql.tsql.errorhandling.mysql.handler"></a>

A `HANDLER` object defines the actions or statements to be ran when a `CONDITION` arises. The handler object may be used to `CONTINUE` or `EXIT` the run.

The condition may be a previously defined condition using the `DECLARE …​ CONDITION` statement or an explicit condition for one of the following items:
+ An explicit Aurora MySQL error code. For example 1051, which represents an **Unknown Table Error**.
+ An explicit `SQLSTATE` value. For example `42S02`.
+ Any `SQLWARNING` event representing any `SQLSTATE` with a `01` prefix.
+ Any `NOTFOUND` event representing any `SQLSTATE` with a `02` prefix. This condition is relevant for cursors. For more information, see [Cursors](chap-sql-server-aurora-mysql.tsql.cursors.md).
+ Any `SQLEXCEPTION` event, representing any `SQLSTATE` without a `00`, `01`, or `02` prefix. These conditions are considered exception errors.

**Note**  
 `SQLSTATE` events with a `00` prefix aren’t errors; they are used to represent successful runs of statements.

 **Syntax** 

```
DECLARE {CONTINUE | EXIT | UNDO}
HANDLER FOR
<MySQL Error Code> |
<SQLSTATE [VALUE] <SQLState Value> |
<Condition Name> |
SQLWARNING |
NOT FOUND |
SQLEXCEPTION
<Statement Block>
```

 **Examples** 

Declare a handler to ignore warning messages and continue run by assigning an empty statement block.

```
DECLARE CONTINUE HANDLER
FOR SQLWARNING BEGIN END
```

Declare a handler to `EXIT` upon duplicate key violation and log a message to a table.

```
DECLARE EXIT HANDLER
FOR SQLSTATE '23000'
BEGIN
    INSERT INTO MyErrorLogTable
        VALUES(NOW(), CURRENT_USER(), 'Error 23000')
END
```

### GET DIAGNOSTICS
<a name="chap-sql-server-aurora-mysql.tsql.errorhandling.mysql.diagnostics"></a>

Each run of an SQL statement produces diagnostic information that is stored in the diagnostics area. The `GET DIAGNOSTICS` statement enables users to retrieve and inspect this information.

**Note**  
 Aurora MySQL also supports the SHOW WARNINGS and SHOW ERRORS statements to retrieve conditions and errors.

The `GET DIAGNOSTICS` statement is typically used in the handler code within a stored routine. `GET CURRENT DIAGNOSTICS` is permitted outside the context of a handler to check the run result of an SQL statement.

The `CURRENT` keyword causes retrieval of the current diagnostics area. The `STACKED` keyword causes retrieval of the information from the second diagnostics area. The second diagnostic area is only available if the current context is within a code block of a condition handler. The default is `CURRENT`.

 **Syntax** 

```
GET [CURRENT | STACKED] DIAGNOSTICS
<@Parameter = NUMBER | ROW_COUNT>
|
CONDITION <Condition Number> <@Parameter = CLASS_ORIGIN | SUBCLASS_ORIGIN | RETURNED_
SQLSTATE | MESSAGE_TEXT | MYSQL_ERRNO | CONSTRAINT_CATALOG | CONSTRAINT_SCHEMA |
CONSTRAINT_NAME | CATALOG_NAME | SCHEMA_NAME | TABLE_NAME | COLUMN_NAME | CURSOR_NAME>
```

 **Example** 

Retrieve `SQLSTATE` and `MESSAGE_TEXT` from the diagnostic area for the last statement that you ran.

```
GET DIAGNOSTICS CONDITION 1 @p1 = RETURNED_SQLSTATE, @p2 = MESSAGE_TEXT
```

### SIGNAL/RESIGNAL
<a name="chap-sql-server-aurora-mysql.tsql.errorhandling.mysql.signal"></a>

The `SIGNAL` statement is used to raise an explicit condition or error. It can be used to provide full error information to a handle, to an outer scope of run, or to the SQL client. The SIGNAL statement enables explicitly defining the error’s properties such as error number, `SQLSTATE` value, message, and so on.

The difference between `SIGNAL` and `RESIGNAL` is that `RESIGNAL` is used to pass on the error condition information available during the run of a condition handler within a compound statement inside a stored routine or an event. `RESIGNAL` can be used to change none, some, or all the related condition information before passing it for processing in the next calling scope of the stack.

**Note**  
It is not possible to issue `SIGNAL` statements using variables.

 **Syntax** 

```
SIGNAL | RESIGNAL <SQLSTATE [VALUE] sqlstate_value | <Condition Name>
[SET <Condition Information Item Name> = <Value> [,...n]]
<Condition Information Item Name> = CLASS_ORIGIN | SUBCLASS_ORIGIN | RETURNED_SQLSTATE
| MESSAGE_TEXT | MYSQL_ERRNO | CONSTRAINT_CATALOG | CONSTRAINT_SCHEMA | CONSTRAINT_
NAME | CATALOG_NAME | SCHEMA_NAME | TABLE_NAME | COLUMN_NAME | CURSOR_NAME
```

 **Examples** 

Raise an explicit error with `SQLSTATE` 55555.

```
SIGNAL SQLSTATE '55555'
```

Re-raise an error with an explicit MySQL error number.

```
RESIGNAL SET MYSQL_ERRNO = 5
```

### Migration Considerations
<a name="chap-sql-server-aurora-mysql.tsql.errorhandling.mysql.considerations"></a>

**Note**  
Error handling is a critical aspect of any software solution. Code migrated from one paradigm to another should be carefully evaluated and tested.

The basic operations of raising, processing, responding, and obtaining metadata is similar in nature for most relational database management systems. The technical aspects of rewriting the code to use different types of objects isn’t difficult.

In SQL Server, there can only be one handler, or `CATCH` code block, that handles exceptions for a given statement. In Aurora MySQL, multiple handler objects can be declared. A condition may trigger more than one handler. Be sure the correct handlers are ran as expected, especially when there are multiple handlers. The following sections provides rules to help establish your requirements.

### Handler Scope
<a name="chap-sql-server-aurora-mysql.tsql.errorhandling.mysql.scope"></a>

A handler can be specific or general. Specific handlers are handlers defined for a specific MySQL error code, `SQLSTATE`, or a condition name. Therefore, only one type of event will trigger a specific handler. General handlers are handlers defined for conditions in the `SQLWARNING`, `SQLEXCEPTION`, or `NOT FOUND` classes. More than one event may trigger the handler.

A handler is in scope for the block in which it is declared. It can’t be triggered by conditions occurring outside the block boundaries.

A handler declared in a `BEGIN …​ END` block is in scope for the SQL statements that follow the handler declaration.

One or more handlers may be declared in different or the same scopes using different specifications. For example, a specific MySQL error code handler may be defined in an outer code block while a more general `SQLWARNING` handler is defined within an inner code block. Specific MySQL error code handlers and a general `SQLWARNING` class handler may exist within the same code block.

### Handler Choice
<a name="chap-sql-server-aurora-mysql.tsql.errorhandling.mysql.choice"></a>

Only one handler is triggered for a single event. Aurora MySQL decides which handler should be triggered. The decision regarding which handler should be triggered as a response to a condition depends on the handler’s scope and value. It also depends on whether or not other handlers are present that may be more appropriate to handle the event.

When a condition occurs in a stored routine, the server searches for valid handlers in the current `BEGIN …​ END` block scope. If none are found, the engine searches for handlers in each successive containing `BEGIN …​ END` code block scope. When the server finds one or more applicable handlers at any given scope, the choice of which one to trigger is based on the following condition precedence:
+ A MySQL error code handler takes precedence over a `SQLSTATE` value handler.
+ An `SQLSTATE` value handler takes precedence over general `SQLWARNING`, `SQLEXCEPTION`, or `NOT FOUND` handlers.
+ An `SQLEXCEPTION` handler takes precedence over an `SQLWARNING` handler.

Multiple applicable handlers with the same precedence may exist for a condition. For example, a statement could generate several warnings having different error codes. There may exist a specific MySQL error handler for each. In such cases, the choice is non-deterministic. Different handlers may be triggered at different times depending on the circumstances.

## Summary
<a name="chap-sql-server-aurora-mysql.tsql.errorhandling.summary"></a>

The following table identifies similarities, differences, and key migration considerations.


| SQL Server error handling feature | Migrate to Aurora MySQL  | Comments | 
| --- | --- | --- | 
|   `TRY …​ END TRY` and `CATCH …​ END CATCH` blocks.  |  Nested `BEGIN …​ END` code blocks with per-scope handlers.  |   `DECLARE` specific event handlers for each `BEGIN-END` code block. Note that unlike `CATCH` blocks, the handlers must be defined first, not later. Review the handler scope and handler choice sections.  | 
|   `THROW` and `RAISERROR`   |   `SIGNAL` and `RESIGNAL`   |  Review the handler scope and handler choice sections.  | 
|   `THROW` with variables.  |  Not supported.  |  | 
|  FORMATMESSAGE  |  N/A  |  | 
|  Error state functions.  |  GET DIAGNOSTIC  |  | 
|  Proprietary error messages in `sys.messages` system table.  |  Proprietary MySQL error codes and `SQLSTATE` ANSI and ODBC standard.  |  When rewriting error handling code, consider switching to the more standard `SQLSTATE` error codes.  | 
|  Deterministic rules regarding condition handler run — always the next code block in statement order.  |  May be non-deterministic if multiple handlers have the same precedence and scope.  |  Review the handler scope and handler choice sections.  | 

For more information, see [The MySQL Diagnostics Area](https://dev.mysql.com/doc/refman/5.7/en/diagnostics-area.html) and [Condition Handling](https://dev.mysql.com/doc/refman/5.7/en/condition-handling.html) in the *MySQL documentation*.

# Flow control for T-SQL
<a name="chap-sql-server-aurora-mysql.tsql.flowcontrol"></a>

This topic provides reference information about flow control in SQL Server and Amazon Aurora MySQL, comparing their respective capabilities and syntax differences. You can use this guide to understand how to adapt your existing SQL Server flow control statements when migrating to Aurora MySQL.


| Feature compatibility |  AWS SCT / AWS DMS automation level |  AWS SCT action code index | Key differences | 
| --- | --- | --- | --- | 
|   ![\[Four star feature compatibility\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-compatibility-4.png)   |   ![\[Four star automation level\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-automation-4.png)   |   [Flow Control](chap-sql-server-aurora-mysql.tools.actioncode.md#chap-sql-server-aurora-mysql.tools.actioncode.flowcontrol)   |  Syntax and option differences, similar functionality.  | 

## SQL Server Usage
<a name="chap-sql-server-aurora-mysql.tsql.flowcontrol.sqlserver"></a>

Although SQL is a mostly declarative language, it does support flow control commands, which provide run time dynamic changes in script run paths.

**Note**  
Before SQL/PSM was introduced in SQL:1999, the ANSI standard did not include flow control constructs. Therefore, there are significant syntax differences among RDBMS engines.

SQL Server provides the following flow control keywords.
+  `BEGIN…​ END` — Define boundaries for a block of commands that run together.
+  `RETURN` — Exit a server code module such as stored procedure, function, and so on and return control to the calling scope. You can use `RETURN <value>` to return an `INT` value to the calling scope.
+  `BREAK` — Exit `WHILE` loop run.
+  `THROW` — Raise errors and potentially return control to the calling stack.
+  `CONTINUE` — Restart a `WHILE` loop.
+  `TRY…​ CATCH` — Error handling. For more information, see [Error Handling](chap-sql-server-aurora-mysql.tsql.errorhandling.md).
+  `GOTO label` — Moves the run point to the location of the specified label.
+  `WAITFOR` — Delay.
+  `IF…​ ELSE` — Conditional flow control.
+  `WHILE <condition>` — Continue looping while `<condition>` returns `TRUE`.
**Note**  
 `WHILE` loops are commonly used with cursors and use the system variable `@@FETCH_STATUS` to determine when to exit. For more information, see [Cursors](chap-sql-server-aurora-mysql.tsql.cursors.md).

For more information, see [Error Handling](chap-sql-server-aurora-mysql.tsql.errorhandling.md).

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.flowcontrol.sqlserver.examples"></a>

Create and populate the `OrderItems` table.

```
CREATE TABLE OrderItems
(
    OrderID INT NOT NULL,
    Item VARCHAR(20) NOT NULL,
    Quantity SMALLINT NOT NULL,
    PRIMARY KEY(OrderID, Item)
);
```

```
INSERT INTO OrderItems (OrderID, Item, Quantity)
VALUES
(1, 'M8 Bolt', 100),
(2, 'M8 Nut', 100),
(3, 'M8 Washer', 200);
```

 **WAITFOR** 

Use `WAITFOR` to introduce a one minute delay between background batches purging old data.

```
SET ROWCOUNT 1000;
WHILE @@ROWCOUNT > 0;
BEGIN;
    DELETE FROM OrderItems
    WHERE OrderDate < '19900101';
    WAITFOR DELAY '00:01:00';
END;
```

 **GOTO** 

Use `GOTO` to skip a code section based on an input parameter in a stored procedure.

```
CREATE PROCEDURE ProcessOrderItems
@OrderID INT, @Item VARCHAR(20), @Quantity INT, @UpdateInventory BIT
AS
BEGIN
        INSERT INTO OrderItems (OrderID, Item, Quantity)
        SELECT @OrderID, @item, @Quantity
    IF @UpdateInventory = 0
        GOTO Finish
    UPDATE Inventory
    SET Stock = Stock - @Quantity
    WHERE Item = @Item
    /* Additional Inventory Processing */
finish:
/* Generate Results Log*/
END
```

 **Dynamic Procedure Run Path** 

The following example demonstrates a solution for running different processes based on the number of items in an order.

Declare a cursor for looping through all OrderItems and calculating the total quantity for each order.

```
DECLARE OrderItemCursor CURSOR FAST_FORWARD
FOR
SELECT OrderID,
    SUM(Quantity) AS NumItems
FROM OrderItems
GROUP BY OrderID
ORDER BY OrderID;
DECLARE @OrderID INT, @NumItems INT;

-- Instantiate the cursor and loop through all orders.
OPEN OrderItemCursor;

FETCH NEXT FROM OrderItemCursor
INTO @OrderID, @NumItems

WHILE @@Fetch_Status = 0
BEGIN;

IF @NumItems > 100
    PRINT 'EXECUTING LogLargeOrder - '
    + CAST(@OrderID AS VARCHAR(5))
    + ' ' + CAST(@NumItems AS VARCHAR(5));
ELSE
    PRINT 'EXECUTING LogSmallOrder - '
    + CAST(@OrderID AS VARCHAR(5))
    + ' ' + CAST(@NumItems AS VARCHAR(5));

FETCH NEXT FROM OrderItemCursor
INTO @OrderID, @NumItems;
END;

-- Close and deallocate the cursor.
CLOSE OrderItemCursor;
DEALLOCATE OrderItemCursor;
```

For the preceding example, the result looks as shown following.

```
EXECUTING LogSmallOrder - 1 100
EXECUTING LogSmallOrder - 2 100
EXECUTING LogLargeOrder - 3 200
```

For more information, see [Control-of-Flow](https://docs.microsoft.com/en-us/sql/t-sql/language-elements/control-of-flow?view=sql-server-ver15) in the *SQL Server documentation*.

## MySQL Usage
<a name="chap-sql-server-aurora-mysql.tsql.flowcontrol.mysql"></a>

 Amazon Aurora MySQL-Compatible Edition (Aurora MySQL) provides the following flow control constructs:
+  `BEGIN…​ END` — Define boundaries for a block of commands that are ran together.
+  `CASE` — Run a set of commands based on a predicate (not to be confused with `CASE` expressions).
+  `IF…​ ELSE` — Conditional flow control.
+  `ITERATE` — Restart a `LOOP`, `REPEAT`, and `WHILE` statement.
+  `LEAVE` — Exit a server code module such as stored procedure, function, and so on, and return control to the calling scope.
+  `LOOP` — Loop indefinitely.
+  `REPEAT…​ UNTIL` — Loop until the predicate is true.
+  `RETURN` — Terminate the run of the current scope and return to the calling scope.
+  `WHILE` — Continue looping while the condition returns `TRUE`.
+  `SLEEP` — Pause the run for a specified number of seconds.

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.flowcontrol.mysql.examples"></a>

Create and populate the `OrderItems` table.

```
CREATE TABLE OrderItems
(
    OrderID INT NOT NULL,
    Item VARCHAR(20) NOT NULL,
    Quantity SMALLINT NOT NULL,
    PRIMARY KEY(OrderID, Item)
);
```

```
INSERT INTO OrderItems (OrderID, Item, Quantity)
VALUES
(1, 'M8 Bolt', 100),
(2, 'M8 Nut', 100),
(3, 'M8 Washer', 200);
```

Rewrite of SQL Server `WAITFOR` delay using `SLEEP`.

```
CREATE PROCEDURE P()
BEGIN
    DECLARE RR INT;
    SET RR = (
        SELECT COUNT(*)
        FROM OrderItems
        WHERE OrderDate < '19900101'
        );
    WHILE RR > 0 DO
    DELETE FROM OrderItems
    WHERE OrderDate < '19900101';
        DO SLEEP (60);
    SET RR = (
        SELECT COUNT(*)
        FROM OrderItems
        WHERE OrderDate < '19900101'
        );
    END WHILE;
END;
```

Rewrite of SQL Server `GOTO` using nested blocks.

```
CREATE PROCEDURE ProcessOrderItems
(Var_OrderID INT, Var_Item VARCHAR(20), Var_Quantity INT, UpdateInventory BIT)
BEGIN
        INSERT INTO OrderItems (OrderID, Item, Quantity)
        VALUES(Var_OrderID, Var_Item, Var_Quantity)
    IF @UpdateInventory = 1
    BEGIN
        UPDATE Inventory
        SET Stock = Stock - @Quantity
        WHERE Item = @Item
        /* Additional Inventory Processing...*/
    END
/* Generate Results Log */
END
```

 **Dynamic Procedure Run Path** 

The following example demonstrates a solution for running different processes based on the number of items in an order.

This example provides the same functionality as the example for SQL Server flow control. However, unlike the SQL Server example which you run as a batch script, Aurora MySQL variables can only be used in stored routines such as procedures and functions.

Create a procedure to declare a cursor and loop through the order items.

```
CREATE PROCEDURE P()
BEGIN
    DECLARE done INT DEFAULT FALSE;
    DECLARE var_OrderID INT;
    DECLARE var_NumItems INT;

    DECLARE OrderItemCursor CURSOR FOR
    SELECT OrderID,
        SUM(Quantity) AS NumItems
    FROM OrderItems
    GROUP BY OrderID
    ORDER BY OrderID;

    DECLARE CONTINUE HANDLER
    FOR NOT FOUND SET done = TRUE;

    OPEN OrderItemCursor;

    CursorStart: LOOP
    FETCH NEXT FROM OrderItemCursor
        INTO var_OrderID, var_NumItems;
    IF done
        THEN LEAVE CursorStart;
    END IF;
    IF var_NumItems > 100
        THEN SELECT CONCAT('EXECUTING LogLargeOrder - ', CAST(var_OrderID AS VARCHAR(5)),' Num Items: ', CAST(var_ NumItems AS VARCHAR(5)))
    ELSE SELECT CONCAT('EXECUTING LogSmallOrder - ', CAST(var_OrderID AS VARCHAR(5)),     ' Num Items: ', CAST(var_NumItems AS VARCHAR(5)))
    END IF;
END LOOP;

CLOSE OrderItemCursor;

END;
```

## Summary
<a name="chap-sql-server-aurora-mysql.tsql.flowcontrol.summary"></a>

While there are some syntax differences between SQL Server and Aurora MySQL flow control statements, most rewrites should be straightforward. The following table summarizes the differences and identifies how to modify T-SQL code to support similar functionality in Aurora MySQL.


| Feature | SQL Server |  Aurora MySQL  | Workaround | 
| --- | --- | --- | --- | 
|   `BEGIN…​ END`   |  Define command block boundaries.  |  Define command block boundaries.  |  Compatible.  | 
|   `RETURN`   |  Exit the current scope and return to caller. Supported for both scripts and stored code such as procedures and functions.  |  Exit a stored function and return to caller.  |  For Aurora MySQL, `RETURN` is valid only in stored or user-defined functions. It isn’t used in stored procedures, triggers, or events. Rewrite the T-SQL code using the `LEAVE` keyword. The `RETURN` statement can return a value in both products. However, `LEAVE` doesn’t support return parameters. Rewrite the code to use output parameters. You can’t `RETURN` in Aurora MySQL for scripts that aren’t part of a stored routine.  | 
|   `BREAK`   |  Exit the `WHILE` loop run.  |  Not supported.  |  Rewrite the logic to explicitly set a value that will render the `WHILE` condition `FALSE`. For example, `WHILE a<100 AND control = 1`. Explicitly `SET control = 0`, and use `ITERATE` to return to the beginning of the loop.  | 
|   `THROW`   |  Raise errors and potentially return control to the calling stack.  |  Errors are handled by `HANDLER` objects.  |  For more information, see [Error Handling](chap-sql-server-aurora-mysql.tsql.errorhandling.md).  | 
|   `TRY - CATCH`   |  Error handling  |  Errors are handled by `HANDLER` objects.  |  For more information, see [Error Handling](chap-sql-server-aurora-mysql.tsql.errorhandling.md).  | 
|   `GOTO`   |  Move run to specified label.  |  Not supported.  |  Consider rewriting the flow logic using either `CASE` statements or nested stored procedures. You can use nested stored procedures to circumvent this limitation by separating code sections and encapsulating them in sub-procedures. Use `IF <condition> CALL <stored procedure>` in place of `GOTO`.  | 
|   `WAITFOR`   |  Delay.  |  Not supported.  |  Replace `WAITFOR` with Aurora MySQL `SLEEP`. `SLEEP` is less flexible than `WAITFOR` and only supports delays specified in seconds. Rewrite the code using `SLEEP` to replace `WAITFOR DELAY` and convert the units to seconds.  `WAITFOR TIME` isn’t supported in Aurora MySQL. You can calculate the difference in seconds between the desired time and current time using date and rime functions and use the result to dynamically generate the `SLEEP` statement. Alternatively, consider using `CREATE EVENT` with a predefined schedule.  | 
|   `IF…​ ELSE`   |  Conditional flow control.  |  Conditional flow control.  |  The functionality is compatible, but the syntax differs. SQL Server uses `IF <condition> <statement> ELSE <statement>`. Aurora MySQL uses `IF <condition> THEN <statement> ELSE <statement> ENDIF`. Rewrite T-SQL code to add the mandatory `THEN` and `ENDIF` keywords.  | 
|   `WHILE`   |  Continue running while condition is `TRUE`.  |  Continue running while condition is `TRUE`.  |  The functionality is compatible, but the syntax differs. SQL Server uses `WHILE <condition> BEGIN…​END`, Aurora MySQL uses `WHILE <condition> DO…​ END WHILE`. Aurora MySQL doesn’t require a `BEGIN…​END` block. Rewrite T-SQL code to use the Aurora MySQL keywords.  | 

For more information, see [Flow Control Statements](https://dev.mysql.com/doc/refman/5.7/en/flow-control-statements.html) in the *MySQL documentation*.

# Full-text search for T-SQL
<a name="chap-sql-server-aurora-mysql.tsql.fulltextsearch"></a>

This topic provides reference information about full-text search capabilities in Microsoft SQL Server 2019 and Amazon Aurora MySQL. It compares the two systems, highlighting their similarities and differences in implementing full-text search functionality. You can understand how full-text indexes are created and used in both platforms, including the types of columns that support full-text indexing and the basic syntax for creating and querying these indexes.


| Feature compatibility |  AWS SCT / AWS DMS automation level |  AWS SCT action code index | Key differences | 
| --- | --- | --- | --- | 
|   ![\[Two star feature compatibility\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-compatibility-2.png)   |   ![\[No automation\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-automation-0.png)   |   [Full-Text Search](chap-sql-server-aurora-mysql.tools.actioncode.md#chap-sql-server-aurora-mysql.tools.actioncode.fulltextsearch)   |  Syntax and option differences, less comprehensive but simpler. Most common basic functionality is similar. Requires rewrite of administration logic and queries.  | 

## SQL Server Usage
<a name="chap-sql-server-aurora-mysql.tsql.fulltextsearch.sqlserver"></a>

SQL Server supports an optional framework for running full-text search queries against character-based data in SQL Server tables using an integrated, in-process full-text engine, and the `fdhost.exe` filter daemon host process.

To run full-text queries, a full-text catalog must first be created, which in turn may contain one or more full-text indexes. A full-text index is comprised of one or more textual columns of a table.

Full-text queries perform smart linguistic searches against Full-Text indexes by identifying words and phrases based on specific language rules. The searches can be for simple words, complex phrases, or multiple forms of a word or a phrase. They can return ranking scores for matches also known as hits.

### Full-Text Indexes
<a name="chap-sql-server-aurora-mysql.tsql.fulltextsearch.sqlserver.indexes"></a>

A full-text index can be created on one of more columns of a table or view for any of the following data types:
+  `CHAR` — Fixed size ASCII string column data type.
+  `VARCHAR` — Variable size ASCII string column data type.
+  `NCHAR` — Fixed size UNICODE string column data type.
+  `NVARCHAR` — Variable size UNICODE string column data type.
+  `TEXT` — ASCII BLOB string column data type (deprecated).
+  `NTEXT` — UNICODE BLOB string column data type (deprecated).
+  `IMAGE` — Binary BLOB data type (deprecated).
+  `XML` — XML structured BLOB data type.
+  `VARBINARY(MAX)` — Binary BLOB data type.
+  `FILESTREAM` — File-based storage data type.
**Note**  
For more information about data types, [Data Types](chap-sql-server-aurora-mysql.sql.datatypes.md).

Full-text indexes are created using the `CREATE FULLTEXT INDEX` statement. A full-text index may contain up to 1024 columns from a single table or view. For more information, see [CREATE FULLTEXT INDEX (Transact-SQL)](https://docs.microsoft.com/en-us/sql/t-sql/statements/create-fulltext-index-transact-sql?view=sql-server-ver15) in the *SQL Server documentation*.

When creating full-text indexes on `BINARY` type columns, documents such as Microsoft Word can be stored as a binary stream and parsed correctly by the full-text engine.

### Full-Text Catalogs
<a name="chap-sql-server-aurora-mysql.tsql.fulltextsearch.sqlserver.catalogs"></a>

Full-text indexes are contained within full-text catalog objects. A full-text catalog is a logical container for one or more full-text indexes and can be used to collectively administer them as a group for tasks such as back-up, restore, refresh content, and so on.

Full-text catalogs are created using the `CREATE FULLTEXT CATALOG` statement. A full-text catalog may contain zero or more full-text indexes and is limited in scope to a single database. For more information, see [CREATE FULLTEXT CATALOG (Transact-SQL)](https://docs.microsoft.com/en-us/sql/t-sql/statements/create-fulltext-catalog-transact-sql?view=sql-server-ver15) in the *SQL Server documentation*.

### Full-Text Queries
<a name="chap-sql-server-aurora-mysql.tsql.fulltextsearch.sqlserver.queries"></a>

After a full-text catalog and index have been create and populated, users can perform full-text queries against these indexes to query for:
+ Simple term match for one or more words or phrases.
+ Prefix term match for words that begin with a set of characters.
+ Generational term match for inflectional forms of a word.
+ Proximity term match for words or phrases which are close to another word or phrase.
+ Thesaurus search for synonymous forms of a word.
+ Weighted term match for finding words or phrases with weighted proximity values.

Full-text queries are integrated into T-SQL, and use the following predicates and functions:
+  `CONTAINS` predicate.
+  `FREETEXT` predicate.
+  `CONTAINSTABLE` table valued function.
+  `FREETEXTTABLE` table valued function.
**Note**  
Don’t confuse full-text functionality with the `LIKE` predicate, which is used for pattern matching only.

### Updating Full-Text Indexes
<a name="chap-sql-server-aurora-mysql.tsql.fulltextsearch.sqlserver.updating"></a>

By default, full-text indexes are automatically updated when the underlying data is modified, similar to a normal B-tree or columnstore index. However, large changes to the underlying data may inflict a performance impact for the full-text indexes update because it is a resource intensive operation. In these cases, you can turn off the automatic update of the catalog and update it manually, or on a schedule, to keep the catalog up to date with the underlying tables.

**Note**  
You can monitor the status of full-text catalog by using the `FULLTEXTCATALOGPROPERTY (<Full-text Catalog Name>, 'Populatestatus')` function.

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.fulltextsearch.sqlserver.examples"></a>

Create the `ProductReviews` table.

```
CREATE TABLE ProductReviews
(
    ReviewID INT NOT NULL
    IDENTITY(1,1),
    CONSTRAINT PK_ProductReviews PRIMARY KEY(ReviewID),
    ProductID INT NOT NULL
        /*REFERENCES Products(ProductID)*/,
    ReviewText VARCHAR(4000) NOT NULL,
    ReviewDate DATE NOT NULL,
    UserID INT NOT NULL
    /*REFERENCES Users(UserID)*/
);
```

```
INSERT INTO ProductReviews
( ProductID, ReviewText, ReviewDate, UserID)
VALUES
(1, 'This is a review for product 1, it is excellent and works as expected',
'20180701', 2),
(1, 'This is a review for product 1, it isn't that great and failed after two days',
'20180702', 2),
(2, 'This is a review for product 3, it has exceeded my expectations. A+++',
'20180710', 2);
```

Create a full-text catalog for product reviews.

```
CREATE FULLTEXT CATALOG ProductFTCatalog;
```

Create a full-text index for ProductReviews.

```
CREATE FULLTEXT INDEX
ON ProductReviews (ReviewText)
KEY INDEX PK_ProductReviews
ON ProductFTCatalog;
```

Query the full-text index for reviews containing the word *excellent*.

```
SELECT *
FROM ProductReviews
WHERE CONTAINS(ReviewText, 'excellent');
```

```
ReviewID  ProductID  ReviewText                                                              ReviewDate  UserID
1         1          This is a review for product 1, it is excellent and works as expected.  2018-07-01  2
```

For more information, see [Full-Text Search](https://docs.microsoft.com/en-us/previous-versions/sql/2014/relational-databases/search/full-text-search?view=sql-server-2014&viewFallbackFrom=sql-server-ver15) in the *SQL Server documentation*.

## MySQL Usage
<a name="chap-sql-server-aurora-mysql.tsql.fulltextsearch.mysql"></a>

 Amazon Aurora MySQL-Compatible Edition (Aurora MySQL) supports all the native full-text capabilities of MySQL InnoDB full-text indexes. Full-text indexes are used to speed up textual searches performed against textual data by using the full-text `MATCH …​ AGAINST` predicate.

Full-text indexes can be created on any textual column of the following types:
+  `CHAR` — Fixed length string data type.
+  `VARCHAR` — Variable length string data type.
+  `TEXT` — String BLOB data type.

Full-text indexes can be created as part of the `CREATE TABLE`, `ALTER TABLE`, and `CREATE INDEX` statements. Full-text indexes in Aurora MySQL use an inverted index design where a list of individual words is stored alongside a list of documents where the words were found. Proximity search is also supported by storing a byte offset position for each word.

Creating a full-text index in Aurora MySQL creates a set of index system tables that can be viewed using the `INFORMATION_SCHEMA.INNODB_SYS_TABLES` view. These tables include the auxiliary index tables representing the inverted index and a set of management tables that help facilitate management of the indexes such as deletes and sync with the underlying data, caching, configuration, and syncing processes.

### Full-Text Index Cache
<a name="chap-sql-server-aurora-mysql.tsql.fulltextsearch.mysql.indexcache"></a>

The index cache temporarily caches index entries for recent rows to minimize the contention associated with inserting documents. These inserts, even small ones, typically result in many singleton insertions to the auxiliary tables, which may prove to be challenging in terms of concurrency. Caching and batch flushing help minimize these frequent updates. In addition, batching also helps alleviate the overhead involved with multiple auxiliary table insertions for words and minimizes duplicate entries as insertions are merged and written to disk as a single entry.

### Full-Text Index Document ID and FTS\$1DOC\$1ID Column
<a name="chap-sql-server-aurora-mysql.tsql.fulltextsearch.mysql.document"></a>

 Aurora MySQL assigns a document identifier that maps words in the index to the document rows where those words are found. This warrants a schema change to the source table, namely adding an indicator column to point to the associated document. This column, known as `FTS_DOC_ID` must exist in the table where the full-text index is created. If the column is not present, Aurora MySQL adds it when the full-text index is created.

**Note**  
Adding a column to a table in Aurora MySQL triggers a full rebuild of the table. That may be resource intensive for larger tables and a warning is issued.

Running a `SHOW WARNINGS` statement after creating a full-text index on a table that doesn’t have this column generates a warning. Consider the following example.

```
CREATE TABLE TestFT
(
    KeyColumn INT AUTO_INCREMENT NOT NULL
    PRIMARY KEY,
    TextColumn TEXT(200)
);
```

```
CREATE FULLTEXT INDEX FTIndex1
ON TestFT(TextColumn);
```

```
SHOW WARNINGS;
```

```
Level    Code  Message
Warning  124   InnoDB rebuilding table to add column FTS_DOC_ID.
```

If the full-text index is created as part of the `CREATE TABLE` statement, the `FTS_DOC_ID` column is added silently and no warning is issued. It is recommended to create the `FTS_DOC_ID` column for tables where full-text indexes will be created as part of the `CREATE TABLE` statement to avoid an expensive rebuild of a table that is already loaded with large amounts of data. Creating the `FTS_DOC_ID` column as an `AUTO_INCREMENT` column may improve performance of data loading.

**Note**  
Dropping a full-text index from a table doesn’t drop the `FTS_DOC_ID` column.

### Full-Text Index Deletes
<a name="chap-sql-server-aurora-mysql.tsql.fulltextsearch.mysql.deletes"></a>

Similar to the insert issue described earlier, deleting rows from a table with a Full-Text index may also result in concurrency challenges due to multiple singleton deletions from the auxiliary tables.

To minimize the impact of this issue, Aurora MySQL logs the deletion of a document ID (`DOC_ID`) in a dedicated internal system table named `FTS_*_DELETED` instead of actually deleting it from the auxiliary tables. The existence of a `DOC_ID` in the `DELETED` table is a type of soft-delete. The engine consults it to determine if a row that had a match in the auxiliary tables should be discarded, or if it should be returned to the client. This approach makes deletes much faster at the expense of somewhat larger index size.

**Note**  
Soft deleted documents aren’t automatically managed. Make sure that you issue an `OPTIMIZE TABLE` statement and the `innodb_optimize_fulltext_only=ON` option to rebuild the full-text index.

### Transaction Control
<a name="chap-sql-server-aurora-mysql.tsql.fulltextsearch.mysql.transactioncontrol"></a>

Due to the caching and batch processing properties of the full-text indexes, `UPDATE` and `INSERT` to a full-text index are committed when a transaction commits. Full-text search can only access committed data.

### Full-Text Search Functions
<a name="chap-sql-server-aurora-mysql.tsql.fulltextsearch.mysql.searchfunctions"></a>

To query full-text indexes, use the `MATCH…​ AGAINST` predicate. The `MATCH` clause accepts a list of column names, separated by commas, that define the column names of the columns that have a full-text index defined and need to be searched. In the `AGAINST` clause, define the string you want searched. It also accepts an optional modifier that indicates the type of search to perform.

### MATCH…​ AGAINST Syntax
<a name="chap-sql-server-aurora-mysql.tsql.fulltextsearch.mysql.matchagainst"></a>

```
MATCH (<Column List>)
AGAINST (
<String Expression>
[ IN NATURAL LANGUAGE MODE
    | IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION
    | IN BOOLEAN MODE
    | WITH QUERY EXPANSION]
)
```

**Note**  
The search expression must be constant for all rows searched. Therefore a table column isn’t permitted.

The three types of full-text searches are natural language, Boolean, and query expansion.

### Natural Language Search
<a name="chap-sql-server-aurora-mysql.tsql.fulltextsearch.mysql.nls"></a>

If no modifier is provided, or the `IN NATURAL LANGUAGE MODE` modifier is explicitly provided, the search string is interpreted as natural human language phrase. For this type of search, the stop-word list is considered and stop words are excluded. For each row, the search returns a relevance value, which denotes the similarity of the search string to the text, for the row, in all the columns listed in the `MATCH` column list. For more information, see [Full-Text Stopwords](https://dev.mysql.com/doc/refman/5.7/en/fulltext-stopwords.html) in the *MySQL documentation*.

### Boolean Search
<a name="chap-sql-server-aurora-mysql.tsql.fulltextsearch.mysql.boolean"></a>

The `IN BOOLEAN MODE` modifier specifies a Boolean search. When using Boolean search, some characters imply special meaning either at the beginning or the end of the words that make up the search string. The `+` and `—` operators are used to indicate that a word must be present or absent for the match to resolve to `TRUE`.

For example, the following statement returns rows for which the `ReviewText` column contains the word *Excellent*, but not the word *England*.

```
SELECT *
FROM ProductReviews
WHERE MATCH (ReviewText) AGAINST ('+Excellent -England' IN BOOLEAN MODE);
```

Additional Boolean operators include: \$1 The `@distance` operator tests if two or more words start within a specified distance, or the number of words between them. \$1 The `<` and `>` operators change a word’s contribution to the relevance value assigned for a specific row match. \$1 Parentheses `()` are used to group words into sub-expressions and may be nested. \$1 The tilde `~` is used as negative operator, resulting in the word’s contribution to be deducted from the total relevance value. Use this operator to mark noise words that are rated lower, but not excluded, as with the `-` operator. \$1 The asterisk `*` operator is used as a wildcard operator and is appended to the word. \$1 Double quotes ` are used for exact, literal phrase matching.

For more information, see [Boolean Full-Text Searches](https://dev.mysql.com/doc/refman/5.7/en/fulltext-boolean.html) in the *MySQL documentation*.

### Query Expansion
<a name="chap-sql-server-aurora-mysql.tsql.fulltextsearch.mysql.queryexpansion"></a>

The `WITH QUERY EXPANSION` or `IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION` is useful when a search phrase is too short, which may indicate that the user is looking for implied knowledge that the full-text engine doesn’t have.

For example, a user that searches for *Car* may need to match specific car brands such as *Ford*, *Toyota*, *Mercedes-Benz*, and so on.

Blind query expansions, also known as automatic relevance feedback, performs the searches twice. On the first pass, the engine looks for the most relevant documents. It then performs a second pass using the original search phrase concatenated with the results of the first pass. For example, if the search was looking for *Cars* and the most relevant documents included the word *Ford*, the seconds search would find the documents that also mention *Ford*.

For more information, see [Full-Text Searches with Query Expansion](https://dev.mysql.com/doc/refman/5.7/en/fulltext-query-expansion.html) in the *MySQL documentation*.

### Migration Considerations
<a name="chap-sql-server-aurora-mysql.tsql.fulltextsearch.mysql.considerations"></a>

Migrating full-text indexes from SQL Server to Aurora MySQL requires a full rewrite of the code that deals with both creating, management, and querying of full-text searches.

Although the Aurora MySQL full-text engine is significantly less comprehensive than SQL Server, it is also much simpler to create and manage and is sufficiently powerful for most common, basic full-text requirements.

For more complex full-text workloads, Amazon Relational Database Service (Amazon RDS) offers CloudSearch, a managed service in the AWS Cloud that makes it simple and cost-effective to set up, manage, and scale an enterprise grade search solution. Amazon CloudSearch supports 34 languages and advanced search features such as highlighting, autocomplete, and geospatial search.

Currently, there is no direct tooling integration with Aurora MySQL and, therefore, you must create a custom application to synchronize the data between Amazon RDS instances and the CloudSearch Service.

For more information, see [Amazon CloudSearch](https://aws.amazon.com/cloudsearch/).

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.fulltextsearch.mysql.examples"></a>

```
CREATE TABLE ProductReviews
(
    ReviewID INT
        AUTO_INCREMENT NOT NULL
        PRIMARY KEY,
    ProductID INT NOT NULL
        /*REFERENCES Products(ProductID)*/,
    ReviewText TEXT(4000) NOT NULL,
    ReviewDate DATE NOT NULL,
    UserID INT NOT NULL
    /*REFERENCES Users(UserID)*/
);
```

```
INSERT INTO ProductReviews
(ProductID, ReviewText, ReviewDate, UserID)
VALUES
(1, 'This is a review for product 1, it is excellent and works as expected',
'20180701', 2),
(1, 'This is a review for product 1, it isn't that great and failed after two days',
'20180702', 2),
(2, 'This is a review for product 3, it has exceeded my expectations. A+++',
'20180710', 2);
```

Query the full-text index for reviews containing the word *excellent*.

```
SELECT *
FROM ProductReviews
WHERE MATCH (ReviewText) AGAINST ('Excellent' IN NATURAL LANGUAGE MODE);
```

For more information, see [InnoDB Full-Text Indexes](https://dev.mysql.com/doc/refman/5.7/en/innodb-fulltext-index.html) in the *MySQL documentation*.

# SQL server graph features for T-SQL
<a name="chap-sql-server-aurora-mysql.tsql.graph"></a>

This topic provides reference content about graph database capabilities in Microsoft SQL Server 2019 and compares them to Amazon Aurora MySQL. You can understand the key features of graph databases in SQL Server, including nodes, edges, and their relationships.


| Feature compatibility |  AWS SCT / AWS DMS automation level |  AWS SCT action code index | Key differences | 
| --- | --- | --- | --- | 
|   ![\[One star feature compatibility\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-compatibility-1.png)   |   ![\[No automation\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-automation-0.png)   |  N/A  |  Feature isn’t supported. Migration will require implementing a workaround.  | 

## SQL Server Usage
<a name="chap-sql-server-aurora-mysql.tsql.graph.sqlserver"></a>

SQL Server offers graph database capabilities to model many-to-many relationships. The graph relationships are integrated into Transact-SQL and receive the benefits of using SQL Server as the foundational database management system.

A graph database is a collection of nodes or vertices and edges or relationships. A node represents an entity (for example, a person or an organization) and an edge represents a relationship between the two nodes that it connects (for example, likes or friends). Both nodes and edges may have properties associated with them. Here are some features that make a graph database unique:
+ Edges or relationships are first class entities in a graph database and can have attributes or properties associated with them.
+ A single edge can flexibly connect multiple nodes in a graph database.
+ You can express pattern matching and multi-hop navigation queries easily.
+ You can express transitive closure and polymorphic queries easily.

A relational database can achieve anything a graph database can. However, a graph database makes it easier to express certain kinds of queries. Also, with specific optimizations, certain queries may perform better. Your decision to choose either a relational or graph database is based on following factors:
+ Your application has hierarchical data. The `HierarchyID` data type can be used to implement hierarchies, but it has some limitations. For example, it doesn’t allow you to store multiple parents for a node.
+ Your application has complex many-to-many relationships; as application evolves, new relationships are added.
+ You need to analyze interconnected data and relationships.

SQL Server 2017 adds new graph database capabilities for modeling graph many-to-many relationships. They include a new `CREATE TABLE` syntax for creating node and edge tables, and the keyword `MATCH` for queries.

For more information, see [Graph processing with SQL Server and Azure SQL Database](https://docs.microsoft.com/en-us/sql/relational-databases/graphs/sql-graph-overview?view=sql-server-ver15) in the *SQL Server documentation*.

Consider the following `CREATE TABLE` example:

```
CREATE TABLE Person (ID INTEGER PRIMARY KEY, Name VARCHAR(100), Age INT) AS NODE;

CREATE TABLE friends (StartDate date) AS EDGE;
```

The new `MATCH` clause is introduced to support pattern matching and multi-hop navigation through the graph. The `MATCH` function uses ASCII-art style syntax for pattern matching. Consider the following example:

```
-- Find friends of John
SELECT Person2.Name
FROM Person Person1, Friends, Person Person2
WHERE MATCH(Person1-(Friends)->Person2)
AND Person1.Name = 'John';
```

SQL Server 2019 adds ability to define cascaded delete actions on an edge constraint in a graph database. Edge constraints enable users to add constraints to their edge tables, thereby enforcing specific semantics and also maintaining data integrity. For more information, see [Edge constraints](https://docs.microsoft.com/en-us/sql/relational-databases/tables/graph-edge-constraints?view=sql-server-ver15) in the *SQL Server documentation*.

In SQL Server 2019, graph tables have support for table and index partitioning. For more information, see [Partitioned tables and indexes](https://docs.microsoft.com/en-us/sql/relational-databases/partitions/partitioned-tables-and-indexes?view=sql-server-ver15) in the *SQL Server documentation*.

## MySQL Usage
<a name="chap-sql-server-aurora-mysql.tsql.graph.mysql"></a>

Currently, MySQL doesn’t provide native graph database features.

# JSON and XML for T-SQL
<a name="chap-sql-server-aurora-mysql.tsql.xml"></a>

This topic provides reference information about XML and JSON support in Microsoft SQL Server 2019 and Amazon Aurora MySQL. It compares how these two database systems handle semi-structured data formats, highlighting their respective strengths and limitations. You can understand the differences in native data type support, available functions, and indexing capabilities for XML and JSON between SQL Server and Aurora MySQL.


| Feature compatibility |  AWS SCT / AWS DMS automation level |  AWS SCT action code index | Key differences | 
| --- | --- | --- | --- | 
|   ![\[Two star feature compatibility\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-compatibility-2.png)   |   ![\[Four star automation level\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-automation-4.png)   |   [XML and JSON](chap-sql-server-aurora-mysql.tools.actioncode.md#chap-sql-server-aurora-mysql.tools.actioncode.xml)   |  Minimal XML support, extensive JSON support. No XQUERY support, optionally convert to JSON.  | 

## SQL Server Usage
<a name="chap-sql-server-aurora-mysql.tsql.xml.sqlserver"></a>

Java Script Object Notation (JSON) and eXtensible Markup Language (XML) are the two most common types of semi-structured data documents used by a variety of data interfaces and NoSQL databases. Most REST web service APIs support JSON as their native data transfer format. XML is an older, more mature framework still widely used. It also provides many extensions such as XQuery, name spaces, schemas, and more.

The following example is a JSON document:

```
[{
    "name": "Robert",
    "age": "28"
}, {
    "name": "James",
    "age": "71"
    "lastname": "Drapers"
}]
```

Its XML counterpart is show following:

```
<?xml version="1.0" encoding="UTF-16" ?>
<root>
    <Person>
        <name>Robert</name>
        <age>28</age>
    </Person>
    <Person>
        <name>James</name>
        <age>71</age>
        <lastname>Drapers</lastname>
    </Person>
</root>
```

SQL Server provides native support for XML and JSON in the database using the familiar and convenient T-SQL interface.

### XML Data
<a name="chap-sql-server-aurora-mysql.tsql.xml.sqlserver.data"></a>

SQL Server provides extensive native support for working with XML data including XML data types, XML columns, XML indexes, and XQuery.

### XML Data Types and Columns
<a name="chap-sql-server-aurora-mysql.tsql.xml.sqlserver.columns"></a>

XML data can be stored using the following data types:
+ The native XML data type uses a BLOB structure but preserves the XML infoset, which consists of the containment hierarchy, document order, and element or attribute values. An XML typed document may differ from the original text; whitespace is removed and the order of objects may change. XML data stored as a native XML data type has the additional benefit of schema validation.
+ You can use an annotated schema (AXSD) to distribute XML documents to one or more tables. Hierarchical structure is maintained, but element order is not.
+ CLOB or BLOB such as `VARCHAR(MAX)` and `VARBINARY(MAX)` can be used to store the original XML document.

### XML Indexes
<a name="chap-sql-server-aurora-mysql.tsql.xml.sqlserver.indexes"></a>

In SQL Server, you can create `PRIMARY` and `SECONDARY` XML indexes on columns with a native XML data type. You can create secondary indexes for `PATH`, `VALUE`, or `PROPERTY`, which are helpful for various types of workload queries.

### XQuery
<a name="chap-sql-server-aurora-mysql.tsql.xml.sqlserver.xquery"></a>

SQL Server supports a sub set of the W3C XQUERY language specification. In SQL Server, you can run queries directly against XML data and use them as expressions or sets in standard T-SQL statements. Consider the following example:

```
DECLARE @XMLVar XML = '<Root><Data>My XML Data</Data></Root>';
SELECT @XMLVar.query('/Root/Data');
```

For the preceding example, the result looks as shown following.

```
Result: <Data>My XML Data</Data>
```

### JSON Data
<a name="chap-sql-server-aurora-mysql.tsql.xml.sqlserver.json"></a>

SQL Server doesn’t support a dedicated JSON data type. However, you can store JSON documents in an `NVARCHAR` column. For more information, see [Data Types](chap-sql-server-aurora-mysql.sql.datatypes.md).

SQL Server provides a set of JSON functions that can be used for the following tasks:
+ Retrieve and modify values in JSON documents.
+ Convert JSON objects to a set (table) format.
+ Use standard T-SQL queries with converted JSON objects.
+ Convert tabular results of T-SQL queries to JSON format.

The functions are:
+  `ISJSON` tests whether a string contains a valid JSON string. Use in the `WHERE` clause to avoid errors.
+  `JSON_VALUE` retrieves a scalar value from a JSON document.
+  `JSON_QUERY` retrieves a whole object or array from a JSON document.
+  `JSON_MODIFY` modifies values in a JSON document.
+  `OPENJSON` converts a JSON document to a `SET` that can be used in the `FROM` clause of a T-SQL query.

The `FOR JSON` clause of `SELECT` queries can be used to convert a tabular set to a JSON document.

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.xml.sqlserver.examples"></a>

The following example creates a table with a native typed XML column.

```
CREATE TABLE MyTable
(
    XMLIdentifier INT NOT NULL PRIMARY KEY,
    XMLDocument XML NULL
);
```

The following example queries a JSON document.

```
DECLARE @JSONVar NVARCHAR(MAX);
SET @JSONVar = '{"Data":{"Person":[{"Name":"John"},{"Name":"Jane"},
{"Name":"Maria"}]}}';
SELECT JSON_QUERY(@JSONVar, '$.Data');
```

For more information, see [JSON data in SQL Server](https://docs.microsoft.com/en-us/sql/relational-databases/json/json-data-sql-server?view=sql-server-ver15) and [XML Data](https://docs.microsoft.com/en-us/sql/relational-databases/xml/xml-data-sql-server?view=sql-server-ver15) in the *SQL Server documentation*.

## MySQL Usage
<a name="chap-sql-server-aurora-mysql.tsql.xml.mysql"></a>

 Amazon Aurora MySQL-Compatible Edition (Aurora MySQL) support for unstructured data is the opposite of SQL Server.

There is minimal support for XML, but a native JSON data type and more than 25 dedicated JSON functions.

MySQL 5.7.22 also added the JSON utility function `JSON_PRETTY()` which outputs an existing JSON value in an easy-to-read format; each JSON object member or array value is printed on a separate line and a child object or array is indented 2 spaces with respect to its parent. This function also works with a string that can be parsed as a JSON value. For more information, see [JSON Utility Functions](https://dev.mysql.com/doc/refman/8.0/en/json-utility-functions.html) in the *MySQL documentation*.

MySQL 5.7.22 also added the JSON utility functions `JSON_STORAGE_SIZE()` and `JSON_STORAGE_FREE()`. `JSON_STORAGE_SIZE()` returns the storage space in bytes used for the binary representation of a JSON document prior to any partial update.

 `JSON_STORAGE_FREE()` shows the amount of space freed after it has been partially updated using `JSON_SET()` or `JSON_REPLACE();` this is greater than zero if the binary representation of the new value is less than that of the previous value. Each of these functions also accepts a valid string representation of a JSON document.

For such a value `JSON_STORAGE_SIZE()` returns the space used by its binary representation following its conversion to a JSON document. For a variable containing the string representation of a JSON document `JSON_STORAGE_FREE()` returns zero. Either function produces an error if its (non-null) argument can’t be parsed as a valid JSON document and NULL if the argument is NULL. For more information, see [JSON Utility Functions](https://dev.mysql.com/doc/refman/8.0/en/json-utility-functions.html) in the *MySQL documentation*.

**Note**  
 Amazon Relational Database Service (Amazon RDS) for MySQL 8 added two JSON aggregation functions `JSON_ARRAYAGG()` and `JSON_OBJECTAGG()`. `JSON_ARRAYAGG()` takes a column or expression as its argument and aggregates the result as a single JSON array. The expression can evaluate to any MySQL data type; this doesn’t have to be a JSON value. `JSON_OBJECTAGG()` takes two columns or expressions which it interprets as a key and a value; it returns the result as a single JSON object. For more information, see [Aggregate Functions](https://dev.mysql.com/doc/refman/8.0/en/aggregate-functions-and-modifiers.html) in the *MySQL documentation*.

**Note**  
 Amazon RDS for MySQL 8.0.17 adds two functions `JSON_SCHEMA_VALID()` and `JSON_SCHEMA_VALIDATION_REPORT()` for validating JSON documents. `JSON_SCHEMA_VALID()` returns `TRUE` or `1` if the document validates against the schema and `FALSE` or `0` if it doesn’t. `JSON_SCHEMA_VALIDATION_REPORT()` returns a JSON document containing detailed information about the results of the validation.

### XML Support
<a name="chap-sql-server-aurora-mysql.tsql.xml.mysql.xmlsupport"></a>

 Aurora MySQL supports two XML functions: `ExtractValue` and `UpdateXML`.

 `ExtractValue` accepts an XML document, or fragment, and an `XPATH` expression. The function returns the character data of the child or element matched by the `XPATH` expression. If there is more than one match, the function returns the content of child nodes as a space delimited character string. `ExtractValue` returns only `CDATA`. It doesn’t return tags sub-tags contained within a matching tag or its content.

Consider the following example.

```
SELECT ExtractValue('<Root><Person>John</Person><Person>Jim</Person></Root>', '/Root/Person');
```

```
Results: John Jim
```

You can use `UpdateXML` to replace an XML fragment with another fragment using `XPATH` expressions similar to `ExtractValue`. If a match is found, it returns the new, updated XML. If there are no matches, or multiple matches, the original XML is returned.

Consider the following example.

```
SELECT UpdateXML('<Root><Person>John</Person><Person>Jim</Person></Root>', '/Root', '<Person>Jack</Person>')
```

```
Results: <Person>Jack</Person>
```

**Note**  
 Aurora MySQL doesn’t support MySQL `LOAD XML` syntax. For more information, see [Loading data into an Amazon Aurora MySQL DB cluster from text files in an Amazon S3 bucket](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/AuroraMySQL.Integrating.LoadFromS3.html) in the *User Guide for Aurora*.

### JSON Data Type
<a name="chap-sql-server-aurora-mysql.tsql.xml.mysql.jsondatatype"></a>

 Aurora MySQL 5.7 supports a native JSON data type for storing JSON documents, which provides several benefits over storing the same document as a generic string. The first major benefit is that all JSON documents stored as a JSON data type are validated for correctness. If the document isn’t valid JSON, it is rejected and an error condition is raised.

In addition, more efficient storage algorithms enable optimized read access to elements within the document. The optimized internal binary representation of the document enables much faster operation on the data without requiring expensive re-parsing.

Consider the following example.

```
CREATE TABLE JSONTable (DocumentIdentifier INT NOT NULL PRIMARY KEY, JSONDocument JSON);
```

### JSON Functions
<a name="chap-sql-server-aurora-mysql.tsql.xml.mysql.functions"></a>

 Aurora MySQL supports a rich set of more than 25 targeted functions for working with JSON data. These functions enable adding, modifying, and searching JSON data. Additionally, you can use spatial JSON functions for GeoJSON documents. For more information, see [Spatial GeoJSON Functions](https://dev.mysql.com/doc/refman/5.7/en/spatial-geojson-functions.html) in the *MySQL documentation*.

The `JSON_ARRAY`, `JSON_OBJECT`, and `JSON_QUOTE` functions return a JSON document from a list of values, a list of key-value pairs, or a JSON value respectively.

Consider the following example.

```
SELECT JSON_OBJECT('Person', 'John', 'Country', 'USA');
```

```
{"Person": "John", "Country": "USA"}
```

You can use The `JSON_CONTAINS`, `JSON_CONTAINS_PATH`, `JSON_EXTRACT`, `JSON_KEYS`, and `JSON_SEARCH` functions to query and search the content of a JSON document.

The `CONTAINS` functions are Boolean functions that return 1 or 0 (`TRUE` or `FALSE`). `JSON_EXTRACT` returns a subset of the document based on the XPATH expression.

 `JSON_KEYS` returns a JSON array consisting of the top-level key or path top-level values of a JSON document.

The `JSON_SEARCH` function returns the path to one or all of the instances of the search string.

Consider the following example.

```
SELECT JSON_EXTRACT('["Mary", "Paul", ["Jim", "Ryan"]]', '$[1]');
```

```
"Paul"
```

```
SELECT JSON_SEARCH('["Mary", "Paul", ["Jim", "Ryan"]]', 'one', 'Paul');
```

```
"$[1]"
```

 Aurora MySQL supports the following functions for adding, deleting, and modifying JSON data: `JSON_INSERT`, `JSON_REMOVE`, `JSON_REPLACE`, and their `ARRAY` counterparts, which are used to create, delete, and replace existing data elements.

Consider the following example.

```
SELECT JSON_ARRAY_INSERT('["Mary", "Paul", "Jim"]', '$[1]', 'Jack');
```

```
["Mary", "Jack", "Paul", "Jim"]
```

You can use `JSON_SEARCH` to find the location of an element value within a JSON document.

Consider the following example.

```
SELECT JSON_SEARCH('["Mary", "Paul", ["Jim", "Ryan"]]', 'one', 'Paul');
```

```
"$[1]"
```

### JSON Indexes
<a name="chap-sql-server-aurora-mysql.tsql.xml.mysql.indexes"></a>

JSON columns are effectively a `BINARY` family type, which can’t be indexed.

To index JSON data, use `CREATE TABLE` or `ALTER TABLE` to add generated columns that represent some value from the JSON document and create an index on this generated column.

For more information, see [Indexes](chap-sql-server-aurora-mysql.indexes.md).

**Note**  
If indexes on generated columns exist for JSON documents, the query optimizer can use them to match JSON expressions and optimize data access.

## Summary
<a name="chap-sql-server-aurora-mysql.tsql.xml.summary"></a>

The following table identifies similarities, differences, and key migration considerations.


| Feature | SQL Server |  Aurora MySQL  | 
| --- | --- | --- | 
|  XML and JSON native data types  |  XML with schema collections  |  JSON  | 
|  JSON functions  |   `IS_JSON`, `JSON_VALUE`, `JSON_QUERY`, `JSON_MODFIY`, `OPEN_JSON`, FOR `JSON`   |  A set of over 25 dedicated JSON functions. For more information, see [JSON Function Reference](https://dev.mysql.com/doc/refman/5.7/en/json-function-reference.html) in the *MySQL documentation*.  | 
|  XML functions  |   `XQUERY` and `XPATH`, `OPEN_XML`, `FOR XML`   |   `ExtractValue` and `UpdateXML`.  | 
|  XML and JSON indexes  |  Primary and secondary `PATH`, `VALUE`, and `PROPERTY` indexes  |  Requires adding always-generated (computed and persisted) columns with JSON expressions and indexing them explicitly. The optimizer can make use of JSON expressions only.  | 

For more information, see [XML Functions](https://dev.mysql.com/doc/refman/5.7/en/xml-functions.html), [The JSON Data Type](https://dev.mysql.com/doc/refman/5.7/en/json.html), and [JSON Functions](https://dev.mysql.com/doc/refman/5.7/en/json-functions.html) in the *MySQL documentation*.

# MERGE for T-SQL
<a name="chap-sql-server-aurora-mysql.tsql.merge"></a>

This topic provides reference information about migrating from Microsoft SQL Server 2019’s MERGE statement to equivalent functionality in Amazon Aurora MySQL. You can understand the key differences and similarities between SQL Server’s MERGE capabilities and alternatives such as REPLACE and INSERT…​ON DUPLICATE KEY UPDATE statements.


| Feature compatibility |  AWS SCT / AWS DMS automation level |  AWS SCT action code index | Key differences | 
| --- | --- | --- | --- | 
|   ![\[Three star feature compatibility\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-compatibility-3.png)   |   ![\[No automation\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-automation-0.png)   |   [MERGE](chap-sql-server-aurora-mysql.tools.actioncode.md#chap-sql-server-aurora-mysql.tools.actioncode.merge)   |  Rewrite to use `REPLACE` and `ON DUPLICATE KEY`, or individual constituent DML statements.  | 

## SQL Server Usage
<a name="chap-sql-server-aurora-mysql.tsql.merge.sqlserver"></a>

 `MERGE` is a complex , hybrid DML/DQL statement for performing `INSERT`, `UPDATE`, or `DELETE` operations on a target table based on the results of a logical join of the target table and a source data set.

 `MERGE` can also return row sets similar to `SELECT` using the `OUTPUT` clause, which gives the calling scope access to the actual data modifications of the `MERGE` statement.

The `MERGE` statement is most efficient for non-trivial conditional DML. For example, inserting data if a row key value doesn’t exist and updating the existing row if the key value already exists.

You can manage additional logic such as deleting rows from the target that don’t appear in the source. For simple, straightforward updates of data in one table based on data in another, it is typically more efficient to use simple `INSERT`, `DELETE`, and `UPDATE` statements. You can replicate all `MERGE` functionality using `INSERT`, `DELETE`, and `UPDATE` statements, but not necessarily less efficiently.

The SQL Server MERGE statement offers a wide range of functionality and flexibility and is compatible with ANSI standard SQL:2008. SQL Server has many extensions to MERGE that provide efficient T-SQL solutions for synchronizing data.

### Syntax
<a name="chap-sql-server-aurora-mysql.tsql.merge.sqlserver.syntax"></a>

```
MERGE [INTO] <Target Table> [AS] <Table Alias>]
USING <Source Table>
ON <Merge Predicate>
[WHEN MATCHED [AND <Predicate>]
THEN UPDATE SET <Column Assignments...> | DELETE]
[WHEN NOT MATCHED [BY TARGET] [AND <Predicate>]
THEN INSERT [(<Column List>)]
VALUES (<Values List>) | DEFAULT VALUES]
[WHEN NOT MATCHED BY SOURCE [AND <Predicate>]
THEN UPDATE SET <Column Assignments...> | DELETE]
OUTPUT [<Output Clause>]
```

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.merge.sqlserver.examples"></a>

Perform a simple one-way synchronization of two tables.

```
CREATE TABLE SourceTable
(
    Col1 INT NOT NULL PRIMARY KEY,
    Col2 VARCHAR(20) NOT NULL
);
```

```
CREATE TABLE TargetTable
(
    Col1 INT NOT NULL PRIMARY KEY,
    Col2 VARCHAR(20) NOT NULL
);
```

```
INSERT INTO SourceTable (Col1, Col2)
VALUES
(2, 'Source2'),
(3, 'Source3'),
(4, 'Source4');
```

```
INSERT INTO TargetTable (Col1, Col2)
VALUES
(1, 'Target1'),
(2, 'Target2'),
(3, 'Target3');
```

```
MERGE INTO TargetTable AS TGT
USING SourceTable AS SRC ON TGT.Col1 = SRC.Col1
WHEN MATCHED
    THEN UPDATE SET TGT.Col2 = SRC.Col2
WHEN NOT MATCHED
    THEN INSERT (Col1, Col2)
    VALUES (SRC.Col1, SRC.Col2);
```

```
SELECT * FROM TargetTable;
```

```
Col1  Col2
1     Target1
2     Source2
3     Source3
4     Source4
```

Perform a conditional two-way synchronization using NULL for no change and `DELETE` from the target when the data isn’t found in the source.

```
TRUNCATE TABLE SourceTable;
INSERT INTO SourceTable (Col1, Col2) VALUES (3, NULL), (4, 'NewSource4'), (5, 'Source5');
```

```
MERGE INTO TargetTable AS TGT
USING SourceTable AS SRC ON TGT.Col1 = SRC.Col1
WHEN MATCHED AND SRC.Col2 IS NOT NULL
    THEN UPDATE SET TGT.Col2 = SRC.Col2
WHEN NOT MATCHED
    THEN INSERT (Col1, Col2)
    VALUES (SRC.Col1, SRC.Col2)
WHEN NOT MATCHED BY SOURCE
    THEN DELETE;
```

```
SELECT *
FROM TargetTable;
```

```
Col1  Col2
3     Source3
4     NewSource4
5     Source5
```

For more information, see [MERGE (Transact-SQL)](https://docs.microsoft.com/en-us/sql/t-sql/statements/merge-transact-sql?view=sql-server-ver15) in the *SQL Server documentation*.

## MySQL Usage
<a name="chap-sql-server-aurora-mysql.tsql.merge.mysql"></a>

 Amazon Aurora MySQL-Compatible Edition (Aurora MySQL) doesn’t support the `MERGE` statement. However, it provides two other statements for merging data: `REPLACE`, and `INSERT…​ ON DUPLICATE KEY UPDATE`.

 `REPLACE` deletes a row and inserts a new row if a duplicate key conflict occurs. `INSERT…​ ON DUPLICATE KEY UPDATE` performs an in-place update. Both `REPLACE` and `ON DUPLICATE KEY UPDATE` rely on an existing primary key and unique constraints. It isn’t possible to define custom `MATCH` conditions as with SQL Server `MERGE` statement.

### REPLACE
<a name="chap-sql-server-aurora-mysql.tsql.merge.mysql.replace"></a>

 `REPLACE` provides a function similar to `INSERT`. The difference is that `REPLACE` first deletes an existing row if a duplicate key violation for a `PRIMARY KEY` or `UNIQUE` constraint occurs.

 `REPLACE` is a MySQL extension that isn’t ANSI compliant. It either performs only an INSERT when no duplicate key violations occur, or it performs a `DELETE` and then an `INSERT` if violations occur.

 **Syntax** 

```
REPLACE [INTO] <Table Name> (<Column List>)
VALUES (<Values List>)
```

```
REPLACE [INTO] <Table Name>
SET <Assignment List: ColumnName = VALUE...>
```

```
REPLACE [INTO] <Table Name> (<Column List>)
SELECT ...
```

### INSERT …​ ON DUPLICATE KEY UPDATE
<a name="chap-sql-server-aurora-mysql.tsql.merge.mysql.insert"></a>

The `ON DUPLICATE KEY UPDATE` clause of the `INSERT` statement acts as a dual DML hybrid. Similar to `REPLACE`, it runs the assignments in the `SET` clause instead of raising a duplicate key error. `ON DUPLICATE KEY UPDATE` is a MySQL extension that in not ANSI compliant.

 **Syntax** 

```
INSERT [INTO] <Table Name> [<Column List>]
VALUES (<Value List>
ON DUPLICATE KEY <Assignment List: ColumnName = Value...>
```

```
INSERT [INTO] <Table Name>
SET <Assignment List: ColumnName = Value...>
ON DUPLICATE KEY
    UPDATE <Assignment List: ColumnName = Value...>
```

```
INSERT [INTO] <Table Name> [<Column List>]
SELECT ...
ON DUPLICATE KEY
    UPDATE <Assignment List: ColumnName = Value...>
```

### Migration Considerations
<a name="chap-sql-server-aurora-mysql.tsql.merge.mysql.considerations"></a>

 `REPLACE` and `INSERT …​ ON DUPLICATE KEY UPDATE` don’t provide a full functional replacement for `MERGE` in SQL Server. The key differences are:
+ Key violation conditions are mandated by the primary key or unique constraints that exist on the target table. They can’t be defined using an explicit predicate.
+ There is no alternative for the WHEN NOT MATCHED BY SOURCE clause.
+ There is no alternative for the OUTPUT clause.

The key difference between `REPLACE` and `INSERT ON DUPLICATE KEY UPDATE` is that with `REPLACE`, the violating row is deleted or attempted to be deleted. If foreign keys are in place, the `DELETE` operation may fail, which may fail the entire transaction.

For `INSERT …​ ON DUPLICATE KEY UPDATE`, the update is performed on the existing row in place without attempting to delete it.

It should be straightforward to replace most `MERGE` statements with either `REPLACE` or `INSERT…​ ON DUPLICATE KEY UPDATE`.

Alternatively, break down the operations into their constituent `INSERT`, `UPDATE`, and `DELETE` statements.

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.merge.mysql.examples"></a>

Use `REPLACE` to create a simple one-way, two-table sync.

```
CREATE TABLE SourceTable
(
    Col1 INT NOT NULL PRIMARY KEY,
    Col2 VARCHAR(20) NOT NULL
);
```

```
CREATE TABLE TargetTable
(
    Col1 INT NOT NULL PRIMARY KEY,
    Col2 VARCHAR(20) NOT NULL
);
```

```
INSERT INTO SourceTable (Col1, Col2)
VALUES
(2, 'Source2'),
(3, 'Source3'),
(4, 'Source4');
```

```
INSERT INTO TargetTable (Col1, Col2)
VALUES
(1, 'Target1'),
(2, 'Target2'),
(3, 'Target3');
```

```
REPLACE INTO TargetTable(Col1, Col2)
SELECT Col1,
    Col2
FROM SourceTable;
```

```
SELECT *
FROM TargetTable;
```

```
Col1  Col2
1     Target1
2     Source2
3     Source3
4     Source4
```

Create a conditional two-way sync using NULL for no change and `DELETE` from target when not found in source.

```
TRUNCATE TABLE SourceTable;
```

```
INSERT INTO SourceTable(Col1, Col2)
VALUES
(3, NULL),
(4, 'NewSource4'),
(5, 'Source5');
```

```
DELETE FROM TargetTable
WHERE Col1 NOT IN (SELECT Col1 FROM SourceTable);
```

```
INSERT INTO TargetTable (Col1, Col2)
SELECT Col1,
    Col2
FROM SourceTable AS SRC
WHERE SRC.Col1 NOT IN (
    SELECT Col1
    FROM TargetTable
);
```

```
UPDATE TargetTable AS TGT
SET Col2 = (
    SELECT COALESCE(SRC.Col2, TGT.Col2)
    FROM SourceTable AS SRC
    WHERE SRC.Col1 = TGT.Col1
    )
WHERE TGT.Col1 IN (
    SELECT Col1
    FROM SourceTable
);
```

```
SELECT *
FROM TargetTable;
```

```
Col1  Col2
3     Source3
4     NewSource4
5     Source5
```

## Summary
<a name="chap-sql-server-aurora-mysql.tsql.merge.summary"></a>

The following table describes similarities, differences, and key migration considerations.


| SQL Server MERGE feature | Migrate to Aurora MySQL  | Comments | 
| --- | --- | --- | 
|  Define source set in `USING` clause.  |  Define source set in a `SELECT` query or in a table.  |  | 
|  Define logical duplicate key condition with an `ON` predicate.  |  Duplicate key condition mandated by primary key and unique constraints on target table.  |  | 
|   `WHEN MATCHED THEN UPDATE`   |   `REPLACE` or `INSERT…​ ON DUPLICATE KEY UPDATE`   |  When using `REPLACE`, the violating row will be deleted, or attempted to be deleted. If there are foreign keys in place, the `DELETE` operation may fail, which may fail the entire transaction. With `INSERT …​ ON DUPLICATE KEY UPDATE`, the updated is performed on the existing row in place, without attempting to delete it.  | 
|   `WHEN MATCHED THEN DELETE`   |   `DELETE FROM Target WHERE Key IN (SELECT Key FROM Source)`   |  | 
|   `WHEN NOT MATCHED THEN INSERT`   |   `REPLACE` or `INSERT…​ ON DUPLICATE KEY UPDATE`   |  See the preceding comment.  | 
|   `WHEN NOT MATCHED BY SOURCE UPDATE`   |   `UPDATE Target SET <assignments> WHERE Key NOT IN (SELECT Key FROM Source)`   |  | 
|   `WHEN NOT MATCHED BY SOURCE DELETE`   |   `DELETE FROM Target WHERE KEY NOT IN (SELECT Key FROM Source)`   |  | 
|   `OUTPUT` clause  |  N/A  |  | 

For more information, see [REPLACE Statement](https://dev.mysql.com/doc/refman/5.7/en/replace.html) and [INSERT …​ ON DUPLICATE KEY UPDATE Statement](https://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html) in the *MySQL documentation*.

# PIVOT and UNPIVOT for T-SQL
<a name="chap-sql-server-aurora-mysql.tsql.pivot"></a>

This topic provides reference content on migrating from Microsoft SQL Server 2019 to Amazon Aurora MySQL, specifically focusing on the PIVOT and UNPIVOT operators. You can use this guidance to understand the compatibility differences between these database systems and learn how to adapt your SQL queries.


| Feature compatibility |  AWS SCT / AWS DMS automation level |  AWS SCT action code index | Key differences | 
| --- | --- | --- | --- | 
|   ![\[Three star feature compatibility\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-compatibility-3.png)   |   ![\[No automation\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-automation-0.png)   |   [PIVOT and UNPIVOT](chap-sql-server-aurora-mysql.tools.actioncode.md#chap-sql-server-aurora-mysql.tools.actioncode.pivot)   |  Straightforward rewrite to use traditional SQL syntax.  | 

## SQL Server Usage
<a name="chap-sql-server-aurora-mysql.tsql.pivot.sqlserver"></a>

 `PIVOT` and `UNPIVOT` are relational operations used to transform a set by rotating rows into columns and columns into rows.

### PIVOT
<a name="chap-sql-server-aurora-mysql.tsql.pivot.sqlserver.pivot"></a>

The `PIVOT` operator consists of several clauses and implied expressions.

The *Anchor* column is the column that isn’t be pivoted and results in a single row for each unique value, similar to `GROUP BY`.

The pivoted columns are derived from the `PIVOT` clause and are the row values transformed into columns. The values for these columns are derived from the source column defined in the `PIVOT` clause.

#### Syntax
<a name="chap-sql-server-aurora-mysql.tsql.pivot.sqlserver.pivot.syntax"></a>

```
SELECT <Anchor column>,
    [Pivoted Column 1] AS <Alias>,
    [Pivoted column 2] AS <Alias>
    ...n
FROM
    (<SELECT Statement of Set to be Pivoted>)
    AS <Set Alias>
PIVOT
(
    <Aggregate Function>(<Aggregated Column>)
FOR
[<Column With the Values for the Pivoted Columns Names>]
    IN ( [Pivoted Column 1], [Pivoted column 2] ...)
) AS <Pivot Table Alias>;
```

#### PIVOT Examples
<a name="chap-sql-server-aurora-mysql.tsql.pivot.sqlserver.pivot.examples"></a>

Create and populate the `Orders` table.

```
CREATE TABLE Orders
(
    OrderID INT NOT NULL
    IDENTITY(1,1) PRIMARY KEY,
    OrderDate DATE NOT NULL,
    Customer VARCHAR(20) NOT NULL
);
```

```
INSERT INTO Orders (OrderDate, Customer)
VALUES
('20180101', 'John'),
('20180201', 'Mitch'),
('20180102', 'John'),
('20180104', 'Kevin'),
('20180104', 'Larry'),
('20180104', 'Kevin'),
('20180104', 'Kevin');
```

Create a simple `PIVOT` for the number of orders for each day. Days of month from 5 to 31 are omitted for example simplicity.

```
SELECT 'Number of Orders Per Day' AS DayOfMonth,
    [1], [2], [3], [4] /*...[31]*/
FROM (
    SELECT OrderID,
        DAY(OrderDate) AS OrderDay
    FROM Orders
) AS SourceSet
PIVOT
(
    COUNT(OrderID)
    FOR OrderDay IN ([1], [2], [3], [4] /*...[31]*/)
) AS PivotSet;
```

For the preceding example, the result looks as shown following.

```
DayOfMonth                     1  2  3  4  /*...[31]*/
Number of Orders for Each Day  2  1  0  4
```

**Note**  
The result set is now oriented in rows and columns. The first column is the description of the columns to follow.

 `PIVOT` for number of orders for each day for each customer.

```
SELECT Customer,
    [1], [2], [3], [4] /*...[31]*/
FROM (
    SELECT OrderID,
        Customer,
        DAY(OrderDate) AS OrderDay
    FROM Orders
    ) AS SourceSet
PIVOT
(
    COUNT(OrderID)
    FOR OrderDay IN ([1], [2], [3], [4] /*...[31]*/)
) AS PivotSet;
```

```
Customer  1  2  3  4
John      1  1  0  0
Kevin     0  0  0  3
Larry     0  0  0  1
Mitch     1  0  0  0
```

### UNPIVOT
<a name="chap-sql-server-aurora-mysql.tsql.pivot.sqlserver.unpivot"></a>

 `UNPIVOT` is similar to `PIVOT` in reverse, but spreads existing column values into rows.

The source set is similar to the result of the `PIVOT` with values pertaining to particular entities listed in columns.

Because the result set has more rows than the source, aggregations aren’t required.

It is less commonly used than `PIVOT` because most data in relational databases have attributes in columns; not the other way around.

#### UNPIVOT Examples
<a name="chap-sql-server-aurora-mysql.tsql.pivot.sqlserver.unpivot.examples"></a>

Create an populate the pivot-like `EmployeeSales` table. In real life, this is most likely a view or a set from an external source.

```
CREATE TABLE EmployeeSales
(
    SaleDate DATE NOT NULL PRIMARY KEY,
    John INT,
    Kevin INT,
    Mary INT
);
```

```
INSERT INTO EmployeeSales
VALUES
('20180101', 150, 0, 300),
('20180102', 0, 0, 0),
('20180103', 250, 50, 0),
('20180104', 500, 400, 100);
```

Unpivot employee sales for each date into individual rows for each employee.

```
SELECT SaleDate,
    Employee,
    SaleAmount
FROM
(
    SELECT SaleDate, John, Kevin, Mary
    FROM EmployeeSales
) AS SourceSet
UNPIVOT (
    SaleAmount
    FOR Employee IN (John, Kevin, Mary)
)AS UnpivotSet;
```

```
SaleDate    Employee  SaleAmount
2018-01-01  John      150
2018-01-01  Kevin     0
2018-01-01  Mary      300
2018-01-02  John      0
2018-01-02  Kevin     0
2018-01-02  Mary      0
2018-01-03  John      250
2018-01-03  Kevin     50
2018-01-03  Mary      0
2018-01-04  John      500
2018-01-04  Kevin     400
2018-01-04  Mary      100
```

For more information, see [FROM - Using PIVOT and UNPIVOT](https://docs.microsoft.com/en-us/sql/t-sql/queries/from-using-pivot-and-unpivot?vieww=%20sql-server-ver15&view=sql-server-ver15) in the *SQL Server documentation*.

## MySQL Usage
<a name="chap-sql-server-aurora-mysql.tsql.pivot.mysql"></a>

 Amazon Aurora MySQL-Compatible Edition (Aurora MySQL) doesn’t support the `PIVOT` and `UNPIVOT` relational operators.

Functionality of both operators can be rewritten to use standard SQL syntax, as shown in the following examples.

### PIVOT Examples
<a name="chap-sql-server-aurora-mysql.tsql.pivot.mysql.pivotexamples"></a>

Create and populate the `Orders` table.

```
CREATE TABLE Orders
(
    OrderID INT
    AUTO_INCREMENT NOT NULL PRIMARY KEY,
    OrderDate DATE NOT NULL,
    Customer VARCHAR(20) NOT NULL
);
```

```
INSERT INTO Orders (OrderDate, Customer)
VALUES
('20180101', 'John'),
('20180201', 'Mitch'),
('20180102', 'John'),
('20180104', 'Kevin'),
('20180104', 'Larry'),
('20180104', 'Kevin'),
('20180104', 'Kevin');
```

Create a simple `PIVOT` for the number of orders for each day. Days of month from 5 to 31 are omitted for example simplicity.

```
SELECT 'Number of Orders Per Day' AS DayOfMonth,
    COUNT(CASE WHEN DAY(OrderDate) = 1 THEN 'OrderDate' ELSE NULL END) AS '1',
    COUNT(CASE WHEN DAY(OrderDate) = 2 THEN 'OrderDate' ELSE NULL END) AS '2',
    COUNT(CASE WHEN DAY(OrderDate) = 3 THEN 'OrderDate' ELSE NULL END) AS '3',
    COUNT(CASE WHEN DAY(OrderDate) = 4 THEN 'OrderDate' ELSE NULL END) AS '4' /*...[31]*/
FROM Orders AS O;
```

For the preceding example, the result looks as shown following.

```
DayOfMonth                     1  2  3  4  /*...[31]*/
Number of Orders for Each Day  2  1  0  4
```

 `PIVOT` for number of orders for each day for each customer.

```
SELECT Customer,
    COUNT(CASE WHEN DAY(OrderDate) = 1 THEN 'OrderDate' ELSE NULL END) AS '1',
    COUNT(CASE WHEN DAY(OrderDate) = 2 THEN 'OrderDate' ELSE NULL END) AS '2',
    COUNT(CASE WHEN DAY(OrderDate) = 3 THEN 'OrderDate' ELSE NULL END) AS '3',
    COUNT(CASE WHEN DAY(OrderDate) = 4 THEN 'OrderDate' ELSE NULL END) AS '4' /*...[31]*/
FROM Orders AS O
GROUP BY Customer;
```

```
Customer  1  2  3  4
John      1  1  0  0
Kevin     0  0  0  3
Larry     0  0  0  1
Mitch     1  0  0  0
```

### UNPIVOT Examples
<a name="chap-sql-server-aurora-mysql.tsql.pivot.mysql.examples"></a>

Create an populate the pivot-like `EmployeeSales` table. In real life, this is most likely a view or a set from an external source.

```
CREATE TABLE EmplyeeSales
(
    SaleDate DATE NOT NULL PRIMARY KEY,
    John INT,
    Kevin INT,
    Mary INT
);
```

```
INSERT INTO EmplyeeSales
VALUES
('20180101', 150, 0, 300),
('20180102', 0, 0, 0),
('20180103', 250, 50, 0),
('20180104', 500, 400, 100);
```

Unpivot employee sales for each date into individual rows for each employee.

```
SELECT SaleDate,
    Employee,
    SaleAmount
FROM
(
    SELECT SaleDate,
        Employee,
        CASE
            WHEN Employee = 'John' THEN John
            WHEN Employee = 'Kevin' THEN Kevin
            WHEN Employee = 'Mary' THEN Mary
        END AS SaleAmount
    FROM EmployeeSales
    CROSS JOIN
    (
        SELECT 'John' AS Employee
        UNION ALL
        SELECT 'Kevin'
        UNION ALL
        SELECT 'Mary'
    ) AS Employees
) AS UnpivotedSet;
```

```
SaleDate    Employee  SaleAmount
2018-01-01  John      150
2018-01-01  Kevin     0
2018-01-01  Mary      300
2018-01-02  John      0
2018-01-02  Kevin     0
2018-01-02  Mary      0
2018-01-03  John      250
2018-01-03  Kevin     50
2018-01-03  Mary      0
2018-01-04  John      500
2018-01-04  Kevin     400
2018-01-04  Mary      100
```

For more information, see [MySQL/Pivot table](https://en.wikibooks.org/wiki/MySQL/Pivot_table) in the *MySQL documentation*.

# Synonyms for T-SQL
<a name="chap-sql-server-aurora-mysql.tsql.synonyms"></a>

This topic provides reference information about the compatibility of synonyms between Microsoft SQL Server 2019 and Amazon Aurora MySQL. You can understand the differences in how these database systems handle synonyms, which are alternative identifiers for database objects.


| Feature compatibility |  AWS SCT / AWS DMS automation level |  AWS SCT action code index | Key differences | 
| --- | --- | --- | --- | 
|   ![\[One star feature compatibility\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-compatibility-1.png)   |   ![\[No automation\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-automation-0.png)   |   [Synonyms](chap-sql-server-aurora-mysql.tools.actioncode.md#chap-sql-server-aurora-mysql.tools.actioncode.synonyms)   |  Use stored procedures and functions to abstract instance-wide objects.  | 

## SQL Server Usage
<a name="chap-sql-server-aurora-mysql.tsql.synonyms.sqlserver"></a>

Synonyms are database objects that server as alternative identifiers for other database objects. The referenced database object is called the *base object* and may reside in the same database, another database on the same instance, or a remote server.

Synonyms provide an abstraction layer to isolate client application code from changes to the name or location of the base object.

In SQL Server, synonyms are often used to simplify the use of four-part identifiers when accessing remote instances.

For example, table A resides on server A, and the client application accesses it directly. For scale out reasons, table A needs to be moved to server B to offload resource consumption on server A. Without synonyms, the client application code must be rewritten to access server B. Instead, you can create a synonym called table A and it will transparently redirect the calling application to Server B without any code changes.

You can create synonyms for the following objects:
+ Assembly stored procedures, table-valued functions, scalar functions, and aggregate functions.
+ Replication filter procedures.
+ Extended stored procedures.
+ SQL scalar functions, table-valued functions, inline-tabled-valued functions, views, and stored procedures.
+ User-defined tables including local and global temporary tables.

### Syntax
<a name="chap-sql-server-aurora-mysql.tsql.synonyms.sqlserver.syntax"></a>

```
CREATE SYNONYM [ <Synonym Schema> ] . <Synonym Name>
FOR [ <Server Name> ] . [ <Database Name> ] . [ Schema Name> ] . <Object Name>
```

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.synonyms.sqlserver.examples"></a>

Create a synonym for a local object in a separate database.

```
CREATE TABLE DB1.Schema1.MyTable
(
    KeyColumn INT IDENTITY PRIMARY KEY,
    DataColumn VARCHAR(20) NOT NULL
);
USE DB2;
CREATE SYNONYM Schema2.MyTable
FOR DB1.Schema1.MyTable
```

Create a synonym for a remote object.

```
-- On ServerA
CREATE TABLE DB1.Schema1.MyTable
(
KeyColumn INT IDENTITY PRIMARY KEY,
DataColumn VARCHAR(20) NOT NULL
);

-- On Server B
USE DB2;
CREATE SYNONYM Schema2.MyTable
FOR ServerA.DB1.Schema1.MyTable;
```

**Note**  
This example assumes a linked server named server A exists on server B that points to server A.

For more information, see [CREATE SYNONYM (Transact-SQL)](https://docs.microsoft.com/en-us/sql/t-sql/statements/create-synonym-transact-sql?view=sql-server-ver15) in the *SQL Server documentation*.

## MySQL Usage
<a name="chap-sql-server-aurora-mysql.tsql.synonyms.mysql"></a>

 Amazon Aurora MySQL-Compatible Edition (Aurora MySQL) doesn’t support synonyms and there is no known generic workaround.

For accessing tables or views, a partial workaround is to use encapsulating views as an abstraction layer. Similarly, you can use functions or stored procedures that call other functions or stored procedures.

**Note**  
Synonyms are often used in conjunction with linked servers, which aren’t supported by Aurora MySQL.

For more information, see [Linked Servers](chap-sql-server-aurora-mysql.management.linkedservers.md), [Views](chap-sql-server-aurora-mysql.sql.views.md), [User-Defined Functions](chap-sql-server-aurora-mysql.tsql.udf.md), and [Stored Procedures](chap-sql-server-aurora-mysql.tsql.storedprocedures.md).

# SQL Server TOP and FETCH and MySQL LIMIT for T-SQL
<a name="chap-sql-server-aurora-mysql.tsql.topfetch"></a>

This topic provides reference information about feature compatibility between Microsoft SQL Server 2019 and Amazon Aurora MySQL, specifically focusing on result set limiting and paging techniques. You can understand how the TOP and FETCH clauses in SQL Server correspond to the LIMIT and OFFSET clauses in Aurora MySQL.


| Feature compatibility |  AWS SCT / AWS DMS automation level |  AWS SCT action code index | Key differences | 
| --- | --- | --- | --- | 
|   ![\[Four star feature compatibility\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-compatibility-4.png)   |   ![\[Four star automation level\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-automation-4.png)   |   [TOP and FETCH](chap-sql-server-aurora-mysql.tools.actioncode.md#chap-sql-server-aurora-mysql.tools.actioncode.topfetch)   |  Syntax rewrite, very similar functionality. Convert `PERCENT` and `TIES` to subqueries.  | 

## SQL Server Usage
<a name="chap-sql-server-aurora-mysql.tsql.topfetch.sqlserver"></a>

SQL Server supports two options for limiting and paging result sets returned to the client. `TOP` is a legacy, proprietary T-SQL keyword that is still supported due to its wide usage. The ANSI compliant syntax of `FETCH` and `OFFSET` were introduced in SQL Server 2012 and are recommended for paginating results sets.

### TOP
<a name="chap-sql-server-aurora-mysql.tsql.topfetch.sqlserver.top"></a>

The `TOP (n)` operator is used in the `SELECT` list and limits the number of rows returned to the client based on the `ORDER BY` clause.

**Note**  
When you use TOP with no `ORDER BY` clause, the query is non-deterministic and may return any rows up to the number specified by the `TOP` operator.

You can use `TOP (n)` used with two modifier options:
+  `TOP (n) PERCENT` is used to designate a percentage of the rows to be returned instead of a fixed maximal row number limit (n). When using PERCENT, n can be any value from 1-100.
+  `TOP (n) WITH TIES` is used to allow overriding the n maximal number (or percentage) of rows specified in case there are additional rows with the same ordering values as the last row.

**Note**  
If `TOP (n)` is used without `WITH TIES` and there are additional rows that have the same ordering value as the last row in the group of n rows, the query is also non-deterministic because the last row may be any of the rows that share the same ordering value.

 **Syntax** 

```
SELECT TOP (<Limit Expression>) [PERCENT] [ WITH TIES ] <Select Expressions List>
FROM...
```

### OFFSET…​ FETCH
<a name="chap-sql-server-aurora-mysql.tsql.topfetch.sqlserver.offset"></a>

 `OFFSET…​ FETCH` as part of the `ORDER BY` clause is the ANSI compatible syntax for limiting and paginating result sets. It allows specification of the starting position and limits the number of rows returned, which enables easy pagination of result sets.

Similar to `TOP`, `OFFSET…​ FETCH` relies on the presentation order defined by the `ORDER BY` clause. Unlike `TOP`, it is part of the `ORDER BY` clause and can’t be used without it.

**Note**  
Queries using `FETCH…​ OFFSET` can still be non-deterministic if there is more than one row that has the same ordering value as the last row.

 **Syntax** 

```
ORDER BY <Ordering Expression> [ ASC | DESC ] [ ,...n ]
OFFSET <Offset Expression> { ROW | ROWS }
[FETCH { FIRST | NEXT } <Page Size Expression> { ROW | ROWS } ONLY ]
```

 **Examples** 

Create the `OrderItems` table.

```
CREATE TABLE OrderItems
(
    OrderID INT NOT NULL,
    Item VARCHAR(20) NOT NULL,
    Quantity SMALLINT NOT NULL,
    PRIMARY KEY(OrderID, Item)
);
```

```
INSERT INTO OrderItems (OrderID, Item, Quantity)
VALUES
(1, 'M8 Bolt', 100),
(2, 'M8 Nut', 100),
(3, 'M8 Washer', 200),
(3, 'M6 Locking Nut', 300);
```

Retrieve the three most ordered items by quantity.

```
-- Using TOP
SELECT TOP (3) *
FROM OrderItems
ORDER BY Quantity DESC;

-- USING FETCH
SELECT *
FROM OrderItems
ORDER BY Quantity DESC
OFFSET 0 ROWS FETCH NEXT 3 ROWS ONLY;
```

```
OrderID  Item            Quantity
3        M6 Locking Nut  300
3        M8 Washer       200
2        M8 Nut          100
```

Include rows with ties.

```
SELECT TOP (3) WITH TIES *
FROM OrderItems
ORDER BY Quantity DESC;
```

```
OrderID  Item            Quantity
3        M6 Locking Nut  300
3        M8 Washer       200
2        M8 Nut          100
1        M8 Bolt         100
```

Retrieve half of the rows based on quantity.

```
SELECT TOP (50) PERCENT *
FROM OrderItems
ORDER BY Quantity DESC;
```

```
OrderID  Item            Quantity
3        M6 Locking Nut  300
3        M8 Washer       200
```

For more information, see [SELECT - ORDER BY Clause (Transact-SQL)](https://docs.microsoft.com/en-us/sql/t-sql/queries/select-order-by-clause-transact-sql?view=sql-server-ver15) and [TOP (Transact-SQL)](https://docs.microsoft.com/en-us/sql/t-sql/queries/top-transact-sql?view=sql-server-ver15) in the *SQL Server documentation*.

## MySQL Usage
<a name="chap-sql-server-aurora-mysql.tsql.topfetch.mysql"></a>

 Amazon Aurora MySQL-Compatible Edition (Aurora MySQL) supports the non-ANSI compliant but popular with other database engines `LIMIT…​ OFFSET` operator for paging results sets.

The `LIMIT` clause limits the number of rows returned and doesn’t require an `ORDER BY` clause, although that would make the query non-deterministic.

The `OFFSET` clause is zero-based, similar to SQL Server and used for pagination.

### Migration Considerations
<a name="chap-sql-server-aurora-mysql.tsql.topfetch.mysql.considerations"></a>

 `LIMIT…​ OFFSET` syntax can be used to replace the functionality of both `TOP(n)` and `FETCH…​ OFFSET` in SQL Server. It is automatically converted by the AWS Schema Conversion Tool (AWS SCT except for the `WITH TIES` and `PERCENT` modifiers.

To replace the `PERCENT` option, first calculate how many rows the query returns and then calculate the fixed number of rows to be returned based on that number.

**Note**  
Because this technique involves added complexity and accessing the table twice, consider changing the logic to use a fixed number instead of percentage.

To replace the `WITH TIES` option, rewrite the logic to add another query that checks for the existence of additional rows that have the same ordering value as the last row returned from the `LIMIT` clause.

**Note**  
Because this technique introduces significant added complexity and three accesses to the source table, consider changing the logic to introduce a tie-breaker into the `ORDER BY` clause.

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.topfetch.mysql.examples"></a>

Create the `OrderItems` table.

```
CREATE TABLE OrderItems
(
    OrderID INT NOT NULL,
    Item VARCHAR(20) NOT NULL,
    Quantity SMALLINT NOT NULL,
    PRIMARY KEY(OrderID, Item)
);
```

```
INSERT INTO OrderItems (OrderID, Item, Quantity)
VALUES
(1, 'M8 Bolt', 100),
(2, 'M8 Nut', 100),
(3, 'M8 Washer', 200),
(3, 'M6 Locking Nut', 300);
```

Retrieve the three most ordered items by quantity.

```
SELECT *
FROM OrderItems
ORDER BY Quantity DESC
LIMIT 3 OFFSET 0;
```

For the preceding example, the result looks as shown following.

```
OrderID  Item            Quantity
3        M6 Locking Nut  300
3        M8 Washer       200
2        M8 Nut          100
```

Include rows with ties.

```
SELECT *
FROM
(
    SELECT *
    FROM OrderItems
    ORDER BY Quantity DESC
    LIMIT 3 OFFSET 0
) AS X
UNION
SELECT *
FROM OrderItems
WHERE Quantity = (
    SELECT Quantity
    FROM OrderItems
    ORDER BY Quantity DESC
    LIMIT 1 OFFSET 2
)
ORDER BY Quantity DESC
```

For the preceding example, the result looks as shown following.

```
OrderID  Item            Quantity
3        M6 Locking Nut  300
3        M8 Washer       200
2        M8 Nut          100
1        M8 Bolt         100
```

Retrieve half of the rows based on quantity.

```
CREATE PROCEDURE P(Percent INT)
BEGIN
DECLARE N INT;
SELECT COUNT(*) * Percent / 100 FROM OrderItems INTO N;
SELECT *
FROM OrderItems
ORDER BY Quantity DESC
LIMIT N OFFSET 0;
END
```

```
CALL P(50);
```

For the preceding example, the result looks as shown following.

```
OrderID  Item            Quantity
3        M6 Locking Nut  300
3        M8 Washer       200
```

## Summary
<a name="chap-sql-server-aurora-mysql.tsql.topfetch.summary"></a>


| SQL Server |  Aurora MySQL  | Comments | 
| --- | --- | --- | 
|   `TOP (n)`   |   `LIMIT n`   |  | 
|   `TOP (n) WITH TIES`   |  Not supported  |  See examples for the workaround.  | 
|   `TOP (n) PERCENT`   |  Not supported  |  See examples for the workaround.  | 
|   `OFFSET…​ FETCH`   |   `LIMIT…​ OFFSET`   |  | 

For more information, see [SELECT Statement](https://dev.mysql.com/doc/refman/5.7/en/select.html) and [LIMIT Query Optimization](https://dev.mysql.com/doc/refman/5.7/en/limit-optimization.html) in the *MySQL documentation*.

# Triggers for T-SQL
<a name="chap-sql-server-aurora-mysql.tsql.triggers"></a>

This topic provides reference information about migrating triggers from Microsoft SQL Server 2019 to Amazon Aurora MySQL. It compares the trigger functionality between the two database systems, highlighting key differences and similarities. You can understand how triggers work in both environments, including their scope, access to change sets, supported event types, and execution phases.


| Feature compatibility |  AWS SCT / AWS DMS automation level |  AWS SCT action code index | Key differences | 
| --- | --- | --- | --- | 
|   ![\[Two star feature compatibility\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-compatibility-2.png)   |   ![\[Four star automation level\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-automation-4.png)   |   [Triggers](chap-sql-server-aurora-mysql.tools.actioncode.md#chap-sql-server-aurora-mysql.tools.actioncode.triggers)   |  Only `FOR EACH ROW` processing. No DDL or `EVENT` triggers. `BEFORE` triggers replace `INSTEAD OF` triggers.  | 

## SQL Server Usage
<a name="chap-sql-server-aurora-mysql.tsql.triggers.sqlserver"></a>

Triggers are special type of stored procedure that run automatically in response to events and are most commonly used for Data Manipulation Language (DML).

SQL Server supports `AFTER/FOR` and `INSTEAD OF` triggers, which can be created on tables and views. `AFTER` and `FOR` are synonymous. SQL Server also provides an event trigger framework at the server and database levels that includes Data Definition Language (DDL), Data Control Language (DCL), and general system events such as login.

**Note**  
SQL Server doesn’t support `FOR EACH ROW` triggers in which the trigger code is run once for each row of modified data.

### Trigger Run
<a name="chap-sql-server-aurora-mysql.tsql.triggers.sqlserver.run"></a>
+  `AFTER` triggers run after DML statements complete run.
+  `INSTEAD OF` triggers run code in place of the original DML statement.

You can create `AFTER` triggers only on a table. You can create `INSTEAD OF` triggers on tables and views.

You can create only a single `INSTEAD OF` trigger for any given object and event. When multiple `AFTER` triggers exist for the same event and object, you can partially set the trigger order by using the `sp_settriggerorder` system stored procedure. It enables setting the first and last triggers to be run, but not the order of others.

### Trigger Scope
<a name="chap-sql-server-aurora-mysql.tsql.triggers.sqlserver.scope"></a>

SQL Server supports only statement level triggers. The trigger code runs only once for each statement. The data modified by the DML statement is available to the trigger scope and is saved in two virtual tables: `INSERTED` and `DELETED`. These tables contain the entire set of changes performed by the DML statement that caused trigger run.

SQL triggers always run within the transaction of the statement that triggered the run. If the trigger code issues an explicit ROLLBACK, or causes an exception that mandates a rollback, the DML statement is also rolled back. For `INSTEAD OF` triggers, the DML statement isn’t run and, therefore, doesn’t require a rollback.

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.triggers.sqlserver.examples"></a>

 **Use a DML trigger to audit invoice deletions** 

The following example demonstrates how to use a trigger to log rows deleted from a table.

Create and populate the `Invoices` table.

```
CREATE TABLE Invoices
(
InvoiceID INT NOT NULL PRIMARY KEY,
Customer VARCHAR(20) NOT NULL,
TotalAmount DECIMAL(9,2) NOT NULL
);

INSERT INTO Invoices (InvoiceID,Customer,TotalAmount)
VALUES
(1, 'John', 1400.23),
(2, 'Jeff', 245.00),
(3, 'James', 677.22);
```

Create the `InvoiceAuditLog` table.

```
CREATE TABLE InvoiceAuditLog
(
    InvoiceID INT NOT NULL PRIMARY KEY,
    Customer VARCHAR(20) NOT NULL,
    TotalAmount DECIMAL(9,2) NOT NULL,
    DeleteDate DATETIME NOT NULL DEFAULT (GETDATE()),
    DeletedBy VARCHAR(128) NOT NULL DEFAULT (CURRENT_USER)
);
```

Create an `AFTER DELETE` trigger to log deletions from the `Invoices` table to the audit log.

```
CREATE TRIGGER LogInvoiceDeletes
ON Invoices
AFTER DELETE
AS
BEGIN
INSERT INTO InvoiceAuditLog (InvoiceID, Customer, TotalAmount)
SELECT InvoiceID,
    Customer,
    TotalAmount
FROM Deleted
END;
```

Delete an invoice.

```
DELETE FROM Invoices
WHERE InvoiceID = 3;
```

Query the content of both tables.

```
SELECT *
FROM Invoices AS I
FULL OUTER JOIN
InvoiceAuditLog AS IAG
ON I.InvoiceID = IAG.InvoiceID;
```

For the preceding example, the result looks as shown following.

```
InvoiceID  Customer TotalAmount  InvoiceID  Customer  TotalAmount  DeleteDate      DeletedBy
1          John     1400.23      NULL       NULL      NULL         NULL            NULL
2          Jeff     245.00       NULL       NULL      NULL         NULL            NULL
NULL       NULL     NULL         3          James     677.22       20180224 13:02  Domain/JohnCortney
```

 **Create a DDL trigger** 

Create a trigger to protect all tables in the database from accidental deletion.

```
CREATE TRIGGER PreventTableDrop
ON DATABASE FOR DROP_TABLE
AS
BEGIN
    RAISERROR ('Tables can't be dropped in this database', 16, 1)
    ROLLBACK TRANSACTION
END;
```

Test the trigger by attempting to drop a table.

```
DROP TABLE [Invoices];
    GO
```

The system displays the follow message indicating the `Invoices` table can’t be dropped.

```
Msg 50000, Level 16, State 1, Procedure PreventTableDrop, Line 5 [Batch Start Line 56]
Tables Can't be dropped in this database
Msg 3609, Level 16, State 2, Line 57
The transaction ended in the trigger. The batch has been aborted.
```

For more information, see [DML Triggers](https://docs.microsoft.com/en-us/sql/relational-databases/triggers/dml-triggers?view=sql-server-ver15) and [DDL Triggers](https://docs.microsoft.com/en-us/sql/relational-databases/triggers/ddl-triggers?view=sql-server-ver15) in the *SQL Server documentation*.

## MySQL Usage
<a name="chap-sql-server-aurora-mysql.tsql.triggers.mysql"></a>

 Amazon Aurora MySQL-Compatible Edition (Aurora MySQL) provides Data manipulation Language (DML) triggers only.

MySQL supports `BEFORE` and `AFTER` triggers for `INSERT`, `UPDATE`, and `DELETE` with full control over trigger run order.

MySQL triggers differ substantially from SQL Server. However, you can migrate most common use cases with minimal code changes. The following list identifies the major differences between the SQL Server and Aurora MySQL triggers:
+  Aurora MySQL triggers are run once for each row, not once for each statement as with SQL Server.
+  Aurora MySQL doesn’t support DDL or system event triggers.
+  Aurora MySQL supports `BEFORE` triggers; SQL Server doesn’t support `BEFORE` triggers. l Aurora MySQL supports full run order control for multiple triggers.

**Note**  
Stored procedures, triggers, and user-defined functions in Aurora MySQL are collectively referred to as stored routines. When binary logging is turned on, MySQL `SUPER` privilege is required to run stored routines. However, you can run stored routines with binary logging enabled without `SUPER` privilege by setting `thelog_bin_trust_function_creators` parameter to true for the DB parameter group for your MySQL instance.

### Syntax
<a name="chap-sql-server-aurora-mysql.tsql.triggers.mysql.syntax"></a>

```
CREATE [DEFINER = { user | CURRENT_USER }] TRIGGER <Trigger Name>
{ BEFORE | AFTER } { INSERT | UPDATE | DELETE }
ON <Table Name>
FOR EACH ROW
[{ FOLLOWS | PRECEDES } <Other Trigger Name>]
<Trigger Code Body>
```

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.triggers.mysql.examples"></a>

 **Use a DML trigger to audit invoice deletions** 

The following example demonstrates how to use a trigger to log rows deleted from a table.

Create and populate the `Invoices` table.

```
CREATE TABLE Invoices
(
    InvoiceID INT NOT NULL PRIMARY KEY,
    Customer VARCHAR(20) NOT NULL,
    TotalAmount DECIMAL(9,2) NOT NULL
);

INSERT INTO Invoices (InvoiceID, Customer, TotalAmount)
VALUES
(1, 'John', 1400.23),
(2, 'Jeff', 245.00),
(3, 'James', 677.22);
```

Create the `InvoiceAuditLog` table.

```
CREATE TABLE InvoiceAuditLog
(
    InvoiceID INT NOT NULL
        PRIMARY KEY,
    Customer VARCHAR(20) NOT NULL,
    TotalAmount DECIMAL(9,2) NOT NULL,
    DeleteDate DATETIME NOT NULL
        DEFAULT (GETDATE()),
    DeletedBy VARCHAR(128) NOT NULL
        DEFAULT (CURRENT_USER)
);
```

Create a trigger to log deleted rows.

```
CREATE OR REPLACE TRIGGER LogInvoiceDeletes
ON Invoices
FOR EACH ROW
AFTER DELETE
AS
    BEGIN
    INSERT INTO InvoiceAuditLog (InvoiceID, Customer, TotalAmount, DeleteDate, DeletedBy)
    SELECT InvoiceID,
        Customer,
        TotalAmount,
        NOW(),
        CURRENT_USER()
    FROM OLD
END;
```

Test the trigger by deleting an invoice.

```
DELETE FROM Invoices
WHERE InvoiceID = 3;
```

Select all rows from the `InvoiceAuditLog` table.

```
SELECT * FROM InvoiceAuditLog;
```

For the preceding example, the result looks as shown following.

```
InvoiceID  Customer  TotalAmount  DeleteDate      DeletedBy
3          James     677.22       20180224 13:02  George
```

**Note**  
Additional code changes were required for this example because the `GETDATE()` function isn’t supported by MySQL. For more information, see [Date and Time Functions](chap-sql-server-aurora-mysql.tsql.datetime.md).

## Summary
<a name="chap-sql-server-aurora-mysql.tsql.triggers.summary"></a>


| Feature | SQL Server |  Aurora MySQL  | Workaround | 
| --- | --- | --- | --- | 
|  DML triggers scope  |  Statement-level only  |   `FOR EACH ROW` only  |  Most trigger code, such as the SQL Server example in the previous section, will work without significant code changes. Even though SQL Server triggers process a set of rows at once, typically no changes are needed to process one row at a time. A set of one row, is a valid set and should be processed correctly either way. The main drawback of `FOR EACH ROW` triggers, is that you can’t access other rows that were modified in the same operation. The `NEW` and `OLD` virtual tables can only reference the current row. Therefore, for example, tasks such as logging aggregate data for the entire DML statement set, may require more significant code changes. If your SQL Server trigger code uses loops and cursors to process one row at a time, the loop and cursor sections can be safely removed.  | 
|  Access to change set  |   `INSERTED` and `DELETED` virtual multi-row tables  |   `OLD` and `NEW` virtual one-row tables  |  Make sure that you modify the trigger code to use `NEW` instead of `INSERTED`, and `OLD` instead of `DELETED`.  | 
|  System event triggers  |  DDL, DCL and other event types  |  Not supported  |  | 
|  Trigger run phase  |   `AFTER` and `INSTEAD OF`   |   `AFTER` and `BEFORE`   |  For `INSTEAD OF` triggers, make sure that your modify the trigger code to remove the explicit run of the calling DML, which isn’t needed in a `BEFORE` trigger. In Aurora MySQL, the `OLD` and `NEW` tables are updateable. If your trigger code needs to modify the change set, update the `OLD` and `NEW` tables with the changes. The updated data is applied to the table data when the trigger run completes.  | 
|  Multi-trigger run order  |  Can only set first and last using `sp_settriggerorder`.  |  Can set any run order using `PRECEDS` and `FOLLOWS`.  |  Update the trigger code to reflect the desired run order.  | 
|  Drop a trigger  |   `DROP TRIGGER <trigger name>;`   |   `DROP TRIGGER <trigger name>;`   |  Compatible syntax.  | 
|  Modify trigger code  |  Use the `ALTER TRIGGER` statement.  |  Not supported  |  | 
|  Turn on and turn off a trigger  |  Use the `ALTER TRIGGER <trigger name> ENABLE;` and `ALTER TRIGGER <trigger name> DISABLE;`   |  Not supported  |  A common workaround is to use a database table with flags indicating which trigger to run. Modify the trigger code using conditional flow control (IF) to query the table and determine whether or not the trigger should run additional code or exit without performing any modifications to the database.  | 
|  Triggers on views  |   `INSTEAD OF` triggers only  |  Not supported  |  | 

For more information, see [Trigger Syntax and Examples](https://dev.mysql.com/doc/refman/5.7/en/trigger-syntax.html) and [CREATE TRIGGER Statement](https://dev.mysql.com/doc/refman/5.7/en/create-trigger.html) in the *MySQL documentation*.

# User-defined functions for T-SQL
<a name="chap-sql-server-aurora-mysql.tsql.udf"></a>

This topic provides reference content comparing user-defined functions (UDFs) in Microsoft SQL Server 2019 and Amazon Aurora MySQL. It explains the capabilities, limitations, and key differences between UDFs in these two database systems. You’ll learn about the types of UDFs supported, their behavior, and important considerations when migrating from SQL Server to Aurora MySQL.


| Feature compatibility |  AWS SCT / AWS DMS automation level |  AWS SCT action code index | Key differences | 
| --- | --- | --- | --- | 
|   ![\[Two star feature compatibility\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-compatibility-2.png)   |   ![\[Three star automation level\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-automation-3.png)   |   [User-Defined Functions](chap-sql-server-aurora-mysql.tools.actioncode.md#chap-sql-server-aurora-mysql.tools.actioncode.udf)   |  Scalar functions only, rewrite inline TVF as views or derived tables, and multi-statement TVF as stored procedures.  | 

## SQL Server Usage
<a name="chap-sql-server-aurora-mysql.tsql.udf.sqlserver"></a>

User-defined functions (UDF) are code objects that accept input parameters and return either a scalar value or a set consisting of rows and columns.

SQL Server UDFs can be implemented using T-SQL or Common Language Runtime (CLR) code.

**Note**  
This section doesn’t cover CLR code objects.

Function invocations can’t have any lasting impact on the database. They must be contained and can only modify objects and data local to their scope (for example, data in local variables). Functions aren’t allowed to modify data or the structure of a database.

Functions may be deterministic or non-deterministic. Deterministic functions always return the same result when you run them with the same data. Non-deterministic functions may return different results each time they run. For example, a function that returns the current date or time.

SQL Server supports three types of T-SQL UDFs: scalar functions, table-valued functions, and multi-statement table-valued functions.

SQL Server 2019 adds scalar user-defined functions inlining. Inlining transforms functions into relational expressions and embeds them in the calling SQL query. This transformation improves the performance of workloads that take advantage of scalar UDFs. Scalar UDF inlining facilitates cost-based optimization of operations inside UDFs. The results are efficient, set-oriented, and parallel instead of inefficient, iterative, serial run plans. For more information, see [Scalar UDF Inlining](https://docs.microsoft.com/en-us/sql/relational-databases/user-defined-functions/scalar-udf-inlining?view=sql-server-ver15) in the *SQL Server documentation*.

### Scalar User-Defined Functions
<a name="chap-sql-server-aurora-mysql.tsql.udf.sqlserver.scalar"></a>

Scalar UDFs accept zero or more parameters and return a scalar value. You can use scalar UDFs in T-SQL expressions.

 **Syntax** 

```
CREATE FUNCTION <Function Name> ([{<Parameter Name> [AS] <Data Type> [= <Default Value>] [READONLY]} [,...n]])
RETURNS <Return Data Type>
[AS]
BEGIN
<Function Body Code>
RETURN <Scalar Expression>
END[;]
```

 **Examples** 

Create a scalar function to change the first character of a string to upper case.

```
CREATE FUNCTION dbo.UpperCaseFirstChar (@String VARCHAR(20))
RETURNS VARCHAR(20)
AS
BEGIN
RETURN UPPER(LEFT(@String, 1)) + LOWER(SUBSTRING(@String, 2, 19))
END;
```

```
SELECT dbo.UpperCaseFirstChar ('mIxEdCasE');

Mixedcase
```

### User-Defined Table-Valued Functions
<a name="chap-sql-server-aurora-mysql.tsql.udf.sqlserver.tablevalued"></a>

Inline table-valued UDFs are similar to views or a Common Table Expressions (CTE) with the added benefit of parameters. They can be used in `FROM` clauses as subqueries and can be joined to other source table rows using the `APPLY` and `OUTER APPLY` operators. In-line table valued UDFs have many associated internal optimizer optimizations due to their simple, view-like characteristics.

 **Syntax** 

```
CREATE FUNCTION <Function Name> ([{<Parameter Name> [AS] <Data Type> [= <Default
Value>] [READONLY]} [,...n]])
RETURNS TABLE
[AS]
RETURN (<SELECT Query>)[;]
```

 **Examples** 

Create a table valued function to aggregate employee orders.

```
CREATE TABLE Orders
(
    OrderID INT NOT NULL PRIMARY KEY,
    EmployeeID INT NOT NULL,
    OrderDate DATETIME NOT NULL
);
```

```
INSERT INTO Orders (OrderID, EmployeeID, OrderDate)
VALUES
(1, 1, '20180101 13:00:05'),
(2, 1, '20180201 11:33:12'),
(3, 2, '20180112 10:22:35');
```

```
CREATE FUNCTION dbo.EmployeeMonthlyOrders
(@EmployeeID INT)
RETURNS TABLE AS
RETURN
(
SELECT EmployeeID,
    YEAR(OrderDate) AS OrderYear,
    MONTH(OrderDate) AS OrderMonth,
    COUNT(*) AS NumOrders
FROM Orders AS O
WHERE EmployeeID = @EmployeeID
GROUP BY EmployeeID,
    YEAR(OrderDate),
    MONTH(OrderDate)
);
```

```
SELECT *
FROM dbo.EmployeeMonthlyOrders (1)

EmployeeID  OrderYear  OrderMonth  NumOrders
1           2018       1           1
1           2018       2           1
```

### Multi-Statement User-Defined Table-Valued Functions
<a name="chap-sql-server-aurora-mysql.tsql.udf.sqlserver.multistatement"></a>

Multi-statement table valued UDFs, like inline UDFs, are also similar to views or CTEs, with the added benefit of allowing parameters. They can be used in FROM clauses as sub queries and can be joined to other source table rows using the `APPLY` and `OUTER APPLY` operators.

The difference between multi-statement UDFs and the inline UDFs is that multi-statement UDFs aren’t restricted to a single `SELECT` statement. They can consist of multiple statements including logic implemented with flow control, complex data processing, security checks, and so on.

The downside of using multi-statement UDFs is that there are far less optimizations possible and performance may suffer.

 **Syntax** 

```
CREATE FUNCTION <Function Name> ([{<Parameter Name> [AS] <Data Type> [= <Default
Value>] [READONLY]} [,...n]])
RETURNS <@Return Variable> TABLE <Table Definition>
[AS]
BEGIN
<Function Body Code>
RETURN
END[;]
```

For more information, see [CREATE FUNCTION (Transact-SQL)](https://docs.microsoft.com/en-us/sql/t-sql/statements/create-function-transact-sql?view=sql-server-ver15) in the *SQL Server documentation*.

## MySQL Usage
<a name="chap-sql-server-aurora-mysql.tsql.udf.mysql"></a>

 Amazon Aurora MySQL-Compatible Edition (Aurora MySQL) supports the creation of user-defined scalar functions only. There is no support for table-valued functions.

Unlike SQL Server, Aurora MySQL enables routines to read and write data using `INSERT`, `UPDATE`, and `DELETE`. It also allows DDL statements such as `CREATE` and `DROP`. Aurora MySQL doesn’t permit stored functions to contain explicit SQL transaction statements such as `COMMIT` and `ROLLBACK`.

In Aurora MySQL, you can explicitly specify several options with the `CREATE FUNCTION` statement. These characteristics are saved with the function definition and are viewable with the `SHOW CREATE FUNCTION` statement.
+ The `DETERMINISTIC` option must be explicitly stated. Otherwise, the engine assumes it is not deterministic.
**Note**  
The MySQL engine doesn’t check the validity of the deterministic property declaration. If you wrongly specify a function as `DETERMINISTIC` when in fact it is not, unexpected results and errors may occur.
+  `CONTAINS SQL` indicates the function code doesn’t contain statements that read or modify data.
+  `READS SQL DATA` indicates the function code contains statements that read data (for example, `SELECT`) but not statements that modify data (for example, `INSERT`, `DELETE`, or `UPDATE`).
+  `MODIFIES SQL DATA` indicates the function code contains statements that may modify data.
**Note**  
The preceding options are advisory only. The server doesn’t constrain the function code based on the declaration. This feature is useful in assisting code management.

### Syntax
<a name="chap-sql-server-aurora-mysql.tsql.udf.mysql.syntax"></a>

```
CREATE FUNCTION <Function Name> ([<Function Parameter>[,...]])
RETURNS <Returned Data Type> [characteristic ...]
<Function Code Body>
```

```
characteristic:
COMMENT '<Comment>' | LANGUAGE SQL | [NOT] DETERMINISTIC
| { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
| SQL SECURITY { DEFINER | INVOKER }
```

### Migration Considerations
<a name="chap-sql-server-aurora-mysql.tsql.udf.mysql.considerations"></a>

For scalar functions, migration should be straight forward as far as the function syntax is concerned. Note that rules in Aurora MySQL regarding functions are much more lenient than SQL Server.

A function in Aurora MySQL may modify data and schema. Function determinism must be explicitly stated, unlike SQL Server that infers it from the code. Additional properties can be stated for a function, but most are advisory only and have no functional impact.

Also note that the AS keyword, which is mandatory in SQL Server before the function’s code body, is not valid Aurora MySQL syntax and must be removed.

Table-valued functions will be harder to migrate. For most in-line table valued functions, a simple path may consist of migrating to using views, and letting the calling code handle parameters.

Complex multi-statement table valued functions will require rewrite as a stored procedure, which may in turn write the data to a temporary or standard table for further processing.

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.udf.mysql.examples"></a>

Create a scalar function to change the first character of string to upper case.

```
CREATE FUNCTION UpperCaseFirstChar (String VARCHAR(20))
RETURNS VARCHAR(20)
BEGIN
RETURN CONCAT(UPPER(LEFT(String, 1)) , LOWER(SUBSTRING(String, 2, 19)));
END
```

```
SELECT UpperCaseFirstChar ('mIxEdCasE');
```

```
Mixedcase
```

## Summary
<a name="chap-sql-server-aurora-mysql.tsql.udf.summary"></a>

The following table identifies similarities, differences, and key migration considerations.


| SQL Server user-defined function feature | Migrate to Aurora MySQL  | Comment | 
| --- | --- | --- | 
|  Scalar UDF  |  Scalar UDF  |  Use `CREATE FUNCTION` with similar syntax, remove the `AS` keyword.  | 
|  Inline table-valued UDF  |  N/A  |  Use views and replace parameters with `WHERE` filter predicates.  | 
|  Multi-statement table-valued UDF  |  N/A  |  Use stored procedures to populate tables and read from the table directly.  | 
|  UDF determinism implicit  |  Explicit declaration  |  Use the `DETERMINISTIC` characteristic explicitly to denote a deterministic function, which enables engine optimizations.  | 
|  UDF boundaries local only  |  Can change data and schema  |  UDF rules are more lenient, avoid unexpected changes from function invocation.  | 

For more information, see [CREATE PROCEDURE and CREATE FUNCTION Statements](https://dev.mysql.com/doc/refman/5.7/en/create-procedure.html) and [CREATE FUNCTION Statement for Loadable Functions](https://dev.mysql.com/doc/refman/5.7/en/create-function-loadable.html) in the *MySQL documentation*.

# User-defined types for T-SQL
<a name="chap-sql-server-aurora-mysql.tsql.udt"></a>

This topic provides reference information about user-defined types and table-valued parameters in Microsoft SQL Server and their compatibility with Amazon Aurora MySQL. It explains the differences in feature support between SQL Server and Aurora MySQL, highlighting that Aurora MySQL does not currently support user-defined types or table-valued parameters.


| Feature compatibility |  AWS SCT / AWS DMS automation level |  AWS SCT action code index | Key differences | 
| --- | --- | --- | --- | 
|   ![\[Three star feature compatibility\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-compatibility-3.png)   |   ![\[Three star automation level\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-automation-3.png)   |   [User-Defined Types](chap-sql-server-aurora-mysql.tools.actioncode.md#chap-sql-server-aurora-mysql.tools.actioncode.udt)   |  Replace scalar UDT with base types. Rewrite stored procedures that use table-type input parameters to use strings with CSV, XML, or JSON, or to process row-by-row. For more information, see [Stored Procedures](chap-sql-server-aurora-mysql.tsql.storedprocedures.md).  | 

## SQL Server Usage
<a name="chap-sql-server-aurora-mysql.tsql.udt.sqlserver"></a>

SQL Server user-defined types provide a mechanism for encapsulating custom data types and for adding NULL constraints.

SQL Server also supports table-valued user-defined types, which you can use to pass a set of values to a stored procedure.

User defined types can also be associated to CLR code assemblies. Beginning with SQL Server 2014, memory-optimized types support memory optimized tables and code.

**Note**  
If your code uses custom rules bound to data types, Microsoft recommends discontinuing use of this deprecated feature.

All user-defined types are based on an existing system data types. They allow developers to reuse the definition, making the code and schema more readable.

### Syntax
<a name="chap-sql-server-aurora-mysql.tsql.udt.sqlserver.syntax"></a>

The simplified syntax for the `CREATE TYPE` statement.

```
CREATE TYPE <type name> {
FROM <base type> [ NULL | NOT NULL ] | AS TABLE (<Table Definition>)}
```

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.udt.sqlserver.examples"></a>

 **User-defined types** 

Create a `ZipCodeScalar` user-defined type.

```
CREATE TYPE ZipCode
FROM CHAR(5)
NOT NULL
```

Use the `ZipCodetype` in a table.

```
CREATE TABLE UserLocations
(UserID INT NOT NULL PRIMARY KEY, ZipCode ZipCode);
```

```
INSERT INTO [UserLocations] ([UserID],[ZipCode]) VALUES (1, '94324');
INSERT INTO [UserLocations] ([UserID],[ZipCode]) VALUES (2, NULL);
```

For the preceding example, the following error message appears. It indicates that NULL values for `ZipCodeare` aren’t allowed.

```
Msg 515, Level 16, State 2, Line 78
Cannot insert the value NULL into column 'ZipCode', table 'tempdb.dbo.UserLocations';
column doesn't allow nulls. INSERT fails.
The statement has been terminated.
```

 **Table-valued types** 

The following example demonstrates how to create and use a table valued types to pass a set of values to a stored procedure.

Create the `OrderItems` table.

```
CREATE TABLE OrderItems
(
    OrderID INT NOT NULL,
    Item VARCHAR(20) NOT NULL,
    Quantity SMALLINT NOT NULL,
    PRIMARY KEY(OrderID, Item)
);
```

Create a table valued type for the `OrderItems` table.

```
CREATE TYPE OrderItems
AS TABLE
(
    OrderID INT NOT NULL,
    Item VARCHAR(20) NOT NULL,
    Quantity SMALLINT NOT NULL,
    PRIMARY KEY(OrderID, Item)
);
```

Create the `InsertOrderItems` procedure. Note that the entire set of rows from the table valued parameter is handled with one statement.

```
CREATE PROCEDURE InsertOrderItems
@OrderItems AS OrderItems READONLY
AS
BEGIN
    INSERT INTO OrderItems(OrderID, Item, Quantity)
    SELECT OrderID,
        Item,
        Quantity
    FROM @OrderItems;
END
```

Instantiate the `OrderItems` type, insert the values, and pass it to a stored procedure.

```
DECLARE @OrderItems AS OrderItems;
```

```
INSERT INTO @OrderItems ([OrderID], [Item], [Quantity])
VALUES
(1, 'M8 Bolt', 100),
(1, 'M8 Nut', 100),
(1, M8 Washer, 200);

EXECUTE [InsertOrderItems] @OrderItems = @OrderItems;

(3 rows affected)
```

Select all rows from the `OrderItems` table.

```
SELECT * FROM OrderItems;

OrderID  Item       Quantity
1        M8 Bolt    100
1        M8 Nut     100
1        M8 Washer  200
```

For more information, see [CREATE TYPE (Transact-SQL)](https://docs.microsoft.com/en-us/sql/t-sql/statements/create-type-transact-sql?view=sql-server-ver15) in the *SQL Server documentation*.

## MySQL Usage
<a name="chap-sql-server-aurora-mysql.tsql.udt.mysql"></a>

 Amazon Aurora MySQL-Compatible Edition (Aurora MySQL) 5.7 doesn’t support user defined types and user defined table valued parameters.

The current documentation doesn’t indicate these features will be supported in Aurora MySQL version 8.

### Migration Considerations
<a name="chap-sql-server-aurora-mysql.tsql.udt.mysql.considerations"></a>

For scalar user-defined types, replace the type name with base type and optional NULL constraints.

For table-valued user-defined types used as stored procedure parameters, the workaround is more complicated.

Common solutions include using either temporary tables to hold the data or passing large string parameters containing the data in CSV, XML, JSON (or any other convenient format) and then writing code to parse these values in a stored procedure. Alternatively, if the logic doesn’t require access to the entire set of changes, and for small data sets, it is easier to call the stored procedure in a loop and pass the columns as standard parameters, row by row.

Memory-optimized engines aren’t yet supported in Aurora MySQL. You must convert memory optimized tables to disk based tables.

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.udt.mysql.examples"></a>

 **Replacing a user-defined type** 

Replace the `ZipCode` user-defined type with a base type.

```
CREATE TABLE UserLocations
(
    UserID INT NOT NULL
    PRIMARY KEY,
    /*ZipCode*/ CHAR(5) NOT NULL
);
```

 **Replacing a table-valued stored procedure parameter** 

The following steps describe how to replace a table-valued parameter with a source table and a `LOOP` cursor.

Create an `OrderItems` table.

```
CREATE TABLE OrderItems
(
    OrderID INT NOT NULL,
    Item VARCHAR(20) NOT NULL,
    Quantity SMALLINT NOT NULL,
    PRIMARY KEY(OrderID, Item)
);
```

Create and populate the `SourceTable`.

```
CREATE TABLE SourceTable
(
    OrderID INT,
    Item VARCHAR(20),
    Quantity SMALLINT,
    PRIMARY KEY (OrderID, Item)
);
```

```
INSERT INTO SourceTable (OrderID, Item, Quantity)
VALUES
(1, 'M8 Bolt', 100),
(2, 'M8 Nut', 100),
(3, 'M8 Washer', 200);
```

Create a procedure to loop through the `SourceTable` and insert rows.

**Note**  
There are syntax differences from T-SQL for both the `CREATE PROCEDURE` and the `CURSOR` declaration and use. For more information, see [Stored Procedures](chap-sql-server-aurora-mysql.tsql.storedprocedures.md) and [Cursors](chap-sql-server-aurora-mysql.tsql.cursors.md).

```
CREATE PROCEDURE LoopItems()
BEGIN
    DECLARE done INT DEFAULT FALSE;
    DECLARE var_OrderID INT;
    DECLARE var_Item VARCHAR(20);
    DECLARE var_Quantity SMALLINT;
    DECLARE ItemCursor CURSOR
        FOR SELECT OrderID,
            Item,
            Quantity
        FROM SourceTable;
    DECLARE CONTINUE HANDLER
        FOR NOT FOUND
        SET done = TRUE;
    OPEN ItemCursor;
    CursorStart: LOOP
    FETCH NEXT FROM ItemCursor
        INTO var_OrderID, var_Item, var_Quantity;
    IF Done
        THEN LEAVE CursorStart;
    END IF;
        INSERT INTO OrderItems (OrderID, Item, Quantity)
        VALUES (var_OrderID, var_Item, var_Quantity);
    END LOOP;
    CLOSE ItemCursor;
END;
```

Call the stored procedure.

```
CALL LoopItems();
```

Select all rows from the `OrderItems` table.

```
SELECT * FROM OrderItems;

OrderID  Item       Quantity
1        M8 Bolt    100
2        M8 Nut     100
3        M8 Washer  200
```

## Summary
<a name="chap-sql-server-aurora-mysql.tsql.udt.summary"></a>


| SQL Server |  Aurora MySQL  | Comments | 
| --- | --- | --- | 
|  Table-valued parameters  |  Not supported  |  Use either temporary tables, or CSV, XML, JSON string parameters and parse the data. Alternatively, rewrite the stored procedure to accept the data one row at a time and process the data in a loop.  | 
|  Memory-optimized table-valued user-defined types  |  Not supported  |  Not supported.  | 

For more information, see [Cursors](https://dev.mysql.com/doc/refman/5.7/en/cursors.html) in the *MySQL documentation*.

# Identity and sequences for T-SQL
<a name="chap-sql-server-aurora-mysql.tsql.identitysequences"></a>

This topic provides reference content comparing identity and sequence features between Microsoft SQL Server 2019 and Amazon Aurora MySQL. You can understand the key differences and similarities in how these database systems handle automatic enumeration functions and columns, which are commonly used for generating surrogate keys.


| Feature compatibility |  AWS SCT / AWS DMS automation level |  AWS SCT action code index | Key differences | 
| --- | --- | --- | --- | 
|   ![\[Two star feature compatibility\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-compatibility-2.png)   |   ![\[Three star automation level\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-automation-3.png)   |   [Identity and Sequences](chap-sql-server-aurora-mysql.tools.actioncode.md#chap-sql-server-aurora-mysql.tools.actioncode.identitysequences)   |  MySQL doesn’t support `SEQUENCE` objects. Rewrite `IDENTITY` to `AUTO_INCREMENT`. Last value is evaluated as `MAX(Existing Value) + 1` on every restart.  | 

## SQL Server Usage
<a name="chap-sql-server-aurora-mysql.tsql.identitysequences.sqlserver"></a>

Automatic enumeration functions and columns are common with relational database management systems and are often used for generating surrogate keys.

SQL Server provides several features that support automatic generation of monotonously increasing value generators:
+ The `IDENTITY` property of a table column.
+ The `SEQUENCE` objects framework.
+ The numeric functions such as `IDENTITY` and `NEWSEQUENTIALID`.

### Identity
<a name="chap-sql-server-aurora-mysql.tsql.identitysequences.sqlserver.identity"></a>

The `IDENTITY` property is probably the most widely used means of generating surrogate primary keys in SQL Server applications. Each table may have a single numeric column assigned as an `IDENTITY` using the `CREATE TABLE` or `ALTER TABLE` DDL statements. You can explicitly specify a starting value and increment.

**Note**  
The identity property doesn’t enforce uniqueness of column values, indexing, or any other property. Additional constraints such as primary or unique keys, explicit index specifications, or other properties must be specified in addition to the `IDENTITY` property.

The `IDENTITY` value is generated as part of the transaction that inserts table rows. Applications can obtain `IDENTITY` values using the `@@IDENTITY`, `SCOPE_IDENTITY`, and `IDENT_CURRENT` functions.

 `IDENTITY` columns may be used as primary keys by themselves, as part of a compound key, or as non-key columns.

You can manage `IDENTITY` columns using the `DBCC CHECKIDENT` command, which provides functionality for reseeding and altering properties.

#### Syntax
<a name="chap-sql-server-aurora-mysql.tsql.identitysequences.sqlserver.identity.syntax"></a>

```
IDENTITY [(<Seed Value>, <Increment Value>)]
```

View the original seed value of an `IDENTITY` column with the `IDENT_SEED` system function.

```
SELECT IDENT_SEED (<Table>)
```

Reseed an `IDENTITY` column.

```
DBCC CHECKIDENT (<Table>, RESEED, <Seed Value>)
```

#### Examples
<a name="chap-sql-server-aurora-mysql.tsql.identitysequences.sqlserver.identity.examples"></a>

Create a table with an `IDENTITY` primary key column.

```
CREATE TABLE MyTABLE
(
    Col1 INT NOT NULL
    PRIMARY KEY NONCLUSTERED IDENTITY(1,1),
    Col2 VARCHAR(20) NOT NULL
);
```

Insert a row and retrieve the generated `IDENTITY` value.

```
DECLARE @LastIdent INT;
INSERT INTO MyTable(Col2)
VALUES('SomeString');
SET @LastIdent = SCOPE_IDENTITY()
```

Create a table with a non-key `IDENTITY` column and an increment of 10.

```
CREATE TABLE MyTABLE
(
    Col1 VARCHAR(20) NOT NULL
        PRIMARY KEY,
    Col2 INT NOT NULL
        IDENTITY(1,10),
);
```

Create a table with a compound PK including an `IDENTITY` column.

```
CREATE TABLE MyTABLE
(
    Col1 VARCHAR(20) NOT NULL,
    Col2 INT NOT NULL
        IDENTITY(1,10),
    PRIMARY KEY (Col1, Col2)
);
```

### SEQUENCE
<a name="chap-sql-server-aurora-mysql.tsql.identitysequences.sqlserver.sequence"></a>

Sequences are objects that are independent of a particular table or column and are defined using the `CREATE SEQUENCE` DDL statement. You can manage sequences using the `ALTER SEQUENCE` statement. Multiple tables and multiple columns from the same table may use the values from one or more `SEQUENCE` objects.

You can retrieve a value from a `SEQUENCE` object using the `NEXT VALUE FOR` function. For example, a `SEQUENCE` value can be used as a default value for a surrogate key column.

 `SEQUENCE` objects provide several advantages over `IDENTITY` columns:
+ Can be used to obtain a value before the actual `INSERT` takes place.
+ Value series can be shared among columns and tables.
+ Easier management, restart, and modification of sequence properties.
+ Allow assignment of value ranges using `sp_sequence_get_range` and not just per-row values.

#### Syntax
<a name="chap-sql-server-aurora-mysql.tsql.identitysequences.sqlserver.sequence.syntax"></a>

```
CREATE SEQUENCE <Sequence Name> [AS <Integer Data Type> ]
START WITH <Seed Value>
INCREMENT BY <Increment Value>;
```

```
ALTER SEQUENCE <Sequence Name>
RESTART [WITH <Reseed Value>]
INCREMENT BY <New Increment Value>;
```

#### Examples
<a name="chap-sql-server-aurora-mysql.tsql.identitysequences.sqlserver.sequence.examples"></a>

Create a sequence for use as a primary key default.

```
CREATE SEQUENCE MySequence AS INT START WITH 1 INCREMENT BY 1;
CREATE TABLE MyTable
(
    Col1 INT NOT NULL
        PRIMARY KEY NONCLUSTERED DEFAULT (NEXT VALUE FOR MySequence),
    Col2 VARCHAR(20) NULL
);
```

```
INSERT MyTable (Col1, Col2) VALUES (DEFAULT, 'cde'), (DEFAULT, 'xyz');
```

```
SELECT * FROM MyTable;
```

```
Col1  Col2
1     cde
2     xyz
```

### Sequential Enumeration Functions
<a name="chap-sql-server-aurora-mysql.tsql.identitysequences.sqlserver.sequential"></a>

SQL Server provides two sequential generation functions: `IDENTITY` and `NEWSEQUENTIALID`.

**Note**  
The `IDENTITY` function shouldn’t be confused with the `IDENTITY` property of a column.

You can use the `IDENTITY` function only in a `SELECT …​ INTO` statement to insert `IDENTITY` column values into a new table.

The `NEWSEQUNTIALID` function generates a hexadecimal GUID, which is an integer. While the `NEWID` function generates a random GUID, the `NEWSEQUENTIALID` function guarantees that every GUID created is greater in numeric value than any other GUID previously generated by the same function on the same server since the operating system restart.

**Note**  
You can use `NEWSEQUENTIALID` only with `DEFAULT` constraints associated with columns having a `UNIQUEIDENTIFIER` data type.

#### Syntax
<a name="chap-sql-server-aurora-mysql.tsql.identitysequences.sqlserver.sequential.syntax"></a>

```
IDENTITY (<Data Type> [, <Seed Value>, <Increment Value>]) [AS <Alias>]
```

```
NEWSEQUENTIALID()
```

#### Examples
<a name="chap-sql-server-aurora-mysql.tsql.identitysequences.sqlserver.sequential.examples"></a>

Use the `IDENTITY` function as surrogate key for a new table based on an existing table.

```
CREATE TABLE MySourceTable
(
    Col1 INT NOT NULL PRIMARY KEY,
    Col2 VARCHAR(10) NOT NULL,
    Col3 VARCHAR(10) NOT NULL
);
```

```
INSERT INTO MySourceTable
VALUES
(12, 'String12', 'String12'),
(25, 'String25', 'String25'),
(95, 'String95', 'String95');
```

```
SELECT IDENTITY(INT, 100, 1) AS SurrogateKey,
    Col1,
    Col2,
    Col3
INTO MyNewTable
FROM MySourceTable
ORDER BY Col1 DESC;
```

```
SELECT *
FROM MyNewTable;
```

For the preceding example, the result looks as shown following.

```
SurrogateKey  Col1  Col2      Col3
100           95    String95  String95
101           25    String25  String25
102           12    String12  String12
```

Use `NEWSEQUENTIALID` as a surrogate key for a new table.

```
CREATE TABLE MyTable
(
    Col1 UNIQUEIDENTIFIER NOT NULL
    PRIMARY KEY NONCLUSTERED DEFAULT NEWSEQUENTIALID()
);
```

```
INSERT INTO MyTable
DEFAULT VALUES;
```

```
SELECT *
FROM MyTable;
```

For the preceding example, the result looks as shown following.

```
Col1

9CC01320-C5AA-E811-8440-305B3A017068
```

For more information, see [Sequence Numbers](https://docs.microsoft.com/en-us/sql/relational-databases/sequence-numbers/sequence-numbers?view=sql-server-ver15) and [CREATE TABLE (Transact-SQL) IDENTITY (Property)](https://docs.microsoft.com/en-us/sql/t-sql/statements/create-table-transact-sql-identity-property?view=sql-server-ver15) in the *SQL Server documentation*.

## MySQL Usage
<a name="chap-sql-server-aurora-mysql.tsql.identitysequences.mysql"></a>

 Amazon Aurora MySQL-Compatible Edition (Aurora MySQL) supports automatic sequence generation using the `AUTO_INCREMENT` column property, similar to the `IDENTITY` column property in SQL Server.

 Aurora MySQL doesn’t support table-independent sequence objects.

Any numeric column may be assigned the `AUTO_INCREMENT` property. To make the system generate the next sequence value, the application must not mention the relevant column’s name in the insert command, in case the column was created with the NOT NULL definition then also inserting a NULL value into an `AUTO_INCREMENT` column will increment it. In most cases, the seed value is 1 and the increment is 1.

Client applications use the `LAST_INSERT_ID` function to obtain the last generated value.

Each table can have only one `AUTO_INCREMENT` column. The column must be explicitly indexed or be a primary key, which is indexed by default.

The `AUTO_INCREMENT` mechanism is designed to be used with positive numbers only. Do not use negative values because they will be misinterpreted as a complementary positive value. This limitation is due to precision issues with sequences crossing a zero boundary.

There are two server parameters used to alter the default values for new `AUTO_INCREMENT` columns:
+  `auto_increment_increment` — Controls the sequence interval.
+  `auto_increment_offset` — Determines the starting point for the sequence.

To reseed the `AUTO_INCREMENT` value, use `ALTER TABLE <Table Name> AUTO_INCREMENT = <New Seed Value>`.

### Syntax
<a name="chap-sql-server-aurora-mysql.tsql.identitysequences.mysql.syntax"></a>

```
CREATE [TEMPORARY] TABLE [IF NOT EXISTS] <Table Name>
(<Column Name> <Data Type> [NOT NULL | NULL]
AUTO_INCREMENT [UNIQUE [KEY]] [[PRIMARY] KEY]...
```

### Migration Considerations
<a name="chap-sql-server-aurora-mysql.tsql.identitysequences.mysql.considerations"></a>

Since Aurora MySQL doesn’t support table-independent `SEQUENCE` objects, applications that rely on its properties must use a custom solution to meet their requirements.

In Aurora MySQL, you can use `AUTO_INCREMENT` instead of `IDENTITY` in SQL Server for most cases. For `AUTO_INCREMENT` columns, the application must explicitly `INSERT` a NULL or a 0.

**Note**  
Omitting the `AUTO_INCREMENT` column from the `INSERT` column list has the same effect as inserting a NULL value.

Make sure that your `AUTO_INCREMENT` columns are indexed and don’t have default constraints assigned to the same column. There is a critical difference between `IDENTITY` and `AUTO_INCREMENT` in the way the sequence values are maintained upon service restart. Application developers must be aware of this difference.

### Sequence Value Initialization
<a name="chap-sql-server-aurora-mysql.tsql.identitysequences.mysql.initialization"></a>

SQL Server stores the `IDENTITY` metadata in system tables on disk. Although some values may be cached and lost when the service is restarted, the next time the server restarts, the sequence value continues after the last block of values that was assigned to cache. If you run out of values, you can explicitly set the sequence value to start the cycle over. As long as there are no key conflicts, it can be reused after the range has been exhausted.

In Aurora MySQL, an `AUTO_INCREMENT` column for a table uses a special counter called the auto-increment counter to assign new values for the column. This counter is stored in cache memory only and isn’t persisted to disk. After a service restart, and when Aurora MySQL encounters an `INSERT` to a table containing an `AUTO_INCREMENT` column, it issues an equivalent of the following statement:

```
SELECT MAX(<Auto Increment Column>) FROM <Table Name> FOR UPDATE;
```

**Note**  
The `FOR UPDATE CLAUSE` is required to maintain locks on the column until the read completes.

 Aurora MySQL then increments the value retrieved by the preceding statement and assigns it to the in-memory autoincrement counter for the table. By default, the value is incremented by one. You can change the default using the `auto_increment_increment` configuration setting. If the table has no values, Aurora MySQL uses the value 1. You can change the default using the `auto_increment_offset` configuration setting.

Every server restart effectively cancels any `AUTO_INCREMENT = <Value>` table option in `CREATE TABLE` and `ALTER TABLE` statements.

Unlike `IDENTITY` columns in SQL Server, which by default don’t allow inserting explicit values, Aurora MySQL allows explicit values to be set. If a row has an explicitly specified `AUTO_INCREMENT` column value and the value is greater than the current counter value, the counter is set to the specified column value.

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.identitysequences.mysql.examples"></a>

Create a table with an `AUTO_INCREMENT` column.

```
CREATE TABLE MyTable
(
    Col1 INT NOT NULL
    AUTO_INCREMENT PRIMARY KEY,
    Col2 VARCHAR(20) NOT NULL
);
```

Insert `AUTO_INCREMENT` values.

```
INSERT INTO MyTable (Col2)
VALUES ('AI column omitted');
```

```
INSERT INTO MyTable (Col1, Col2)
VALUES (NULL, 'Explicit NULL');
```

```
INSERT INTO MyTable (Col1, Col2)
VALUES (10, 'Explicit value');
```

```
INSERT INTO MyTable (Col2)
VALUES ('Post explicit value');
```

```
SELECT *
FROM MyTable;
```

For the preceding example, the result looks as shown following.

```
Col1  Col2
1     AI column omitted
2     Explicit NULL
10    Explicit value
11    Post explicit value
```

Reseed `AUTO_INCREMENT`.

```
ALTER TABLE MyTable AUTO_INCREMENT = 30;
```

```
INSERT INTO MyTable (Col2)
VALUES ('Post ALTER TABLE');
```

```
SELECT *
FROM MyTable;
```

For the preceding example, the result looks as shown following.

```
1     AI column omitted
2     Explicit NULL
10    Explicit value
11    Post explicit value
30    Post ALTER TABLE
```

Change the increment value to 10.

**Note**  
Changing the `@@auto_increment_increment` value to 10 impacts all `AUTO_INCREMENT` enumerators in the database.

```
SET @@auto_increment_increment=10;
```

Verify variable change.

```
SHOW VARIABLES LIKE 'auto_inc%';
```

For the preceding example, the result looks as shown following.

```
Variable_name             Value
auto_increment_increment  10
auto_increment_offset     1
```

Insert several rows and then read.

```
INSERT INTO MyTable (Col1, Col2)
VALUES (NULL, 'Row1'), (NULL, 'Row2'), (NULL, 'Row3'), (NULL, 'Row4');
```

```
SELECT Col1, Col2
FROM MyTable;
```

For the preceding example, the result looks as shown following.

```
1     AI column omitted
2     Explicit NULL
10    Explicit value
11    Post explicit value
30    Post ALTER TABLE
40    Row1
50    Row2
60    Row3
70    Row4
```

## Summary
<a name="chap-sql-server-aurora-mysql.tsql.identitysequences.summary"></a>

The following table identifies similarities, differences, and key migration considerations.


| Feature | SQL Server |  Aurora MySQL  | Comments | 
| --- | --- | --- | --- | 
|  Independent `SEQUENCE` object  |   `CREATE SEQUENCE`   |  Not supported  |  | 
|  Automatic enumerator column property  |   `IDENTITY`   |   `AUTO_INCREMENT`   |  | 
|  Reseed sequence value  |   `DBCC CHECKIDENT`   |   `ALTER TABLE <Table Name> AUTO_INCREMENT = <New Seed Value>`   |  | 
|  Column restrictions  |  Numeric  |  Numeric, indexed, and no `DEFAULT`   |  | 
|  Controlling seed and interval values  |   `CREATE/ALTER TABLE`   |   `auto_increment_increment`   `auto_increment_offset`   |   Aurora MySQL settings are global and can’t be customized for each column as in SQL Server.  | 
|  Sequence setting initialization  |  Maintained through service restarts  |  Re-initialized every service restart  |  For more information, see [Sequence Value Initialization](#chap-sql-server-aurora-mysql.tsql.identitysequences.mysql.initialization).  | 
|  Explicit values to column  |  Not allowed by default, `SET IDENTITY_INSERT ON` required  |  Supported  |   Aurora MySQL requires explicit NULL or 0 to trigger sequence value assignment. Inserting an explicit value larger than all others will reinitialize the sequence.  | 
|  Non PK auto enumerator column  |  Supported  |  Not Supported  |  Implement an application enumerator.  | 
|  Compound PK with auto enumerator column  |  Supported  |  Not Supported  |  Implement an application enumerator.  | 

For more information, see [Using AUTO\$1INCREMENT](https://dev.mysql.com/doc/refman/5.7/en/example-auto-increment.html), [CREATE TABLE Statement](https://dev.mysql.com/doc/refman/5.7/en/create-table.html), and [AUTO\$1INCREMENT Handling in InnoDB](https://dev.mysql.com/doc/refman/5.7/en/innodb-auto-increment-handling.html#innodb-auto-increment-initialization.html) in the *MySQL documentation*.

# Managing statistics for T-SQL
<a name="chap-sql-server-aurora-mysql.tsql.managingstatistics"></a>

This topic provides reference information about statistics management in Microsoft SQL Server and Amazon Aurora MySQL, which is crucial for database performance optimization. You can understand the differences and similarities in how these two database systems handle statistics creation, storage, and maintenance.


| Feature compatibility |  AWS SCT / AWS DMS automation level |  AWS SCT action code index | Key differences | 
| --- | --- | --- | --- | 
|   ![\[Three star feature compatibility\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-compatibility-3.png)   |   ![\[No automation\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-automation-0.png)   |  N/A  |  Statistics contain only density information, and only for index key columns.  | 

## SQL Server Usage
<a name="chap-sql-server-aurora-mysql.tsql.managingstatistics.sqlserver"></a>

Statistics objects in SQL Server are designed to support cost-based query optimizer. It uses statistics to evaluate the various plan options and choose an optimal plan for optimal query performance.

Statistics are stored as BLOBs in system tables and contain histograms and other statistical information about the distribution of values in one or more columns. A histogram is created for the first column only and samples the occurrence frequency of distinct values. Statistics and histograms are collected by either scanning the entire table or by sampling only a percentage of the rows.

You can view Statistics manually using the `DBCC SHOW_STATISTICS` statement or the more recent `sys.dm_db_stats_properties` and `sys.dm_db_stats_histogram` system views.

SQL Server provides the capability to create filtered statistics containing a `WHERE` predicate. Filtered statistics are useful for optimizing histogram granularity by eliminating rows whose values are of less interest, for example NULLs.

SQL Server can manage the collection and refresh of statistics automatically, which is the default. Use the `AUTO_CREATE_STATISTICS` and `AUTO_UPDATE_STATISTICS` database options to change the defaults.

When a query is submitted with `AUTO_CREATE_STATISTICS` on, and the query optimizer may benefit from a statistics that doesn’t yet exist, SQL Server creates the statistics automatically. You can use the `AUTO_UPDATE_STATISTICS_ASYNC` database property to set new statistics creation to occur immediately and causing queries to wait or to run asynchronously. When run asynchronously, the triggering run can’t benefit from optimizations the optimizer may derive from it.

After creation of a new statistics object, either automatically or explicitly using the `CREATE STATISTICS` statement, the refresh of the statistics is controlled by the `AUTO_UPDATE_STATISTICS` database option. When set to `ON`, statistics are recalculated when they are stale, which happens when significant data modifications have occurred since the last refresh.

### Syntax
<a name="chap-sql-server-aurora-mysql.tsql.managingstatistics.sqlserver.syntax"></a>

```
CREATE STATISTICS <Statistics Name>
ON <Table Name> (<Column> [,...])
[WHERE <Filter Predicate>]
[WITH <Statistics Options>;
```

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.managingstatistics.sqlserver.examples"></a>

Create new statistics on multiple columns. Set to use a full scan and to not refresh.

```
CREATE STATISTICS MyStatistics
ON MyTable (Col1, Col2)
WITH FULLSCAN, NORECOMPUTE;
```

Update statistics with a 50% sampling rate.

```
UPDATE STATISTICS MyTable(MyStatistics)
WITH SAMPLE 50 PERCENT;
```

View the statistics histogram and data.

```
DBCC SHOW_STATISTICS ('MyTable','MyStatistics');
```

Turn off automatic statistics creation for a database.

```
ALTER DATABASE MyDB SET AUTO_CREATE_STATS OFF;
```

For more information, see [Statistics](https://docs.microsoft.com/en-us/sql/relational-databases/statistics/statistics?view=sql-server-ver15), [CREATE STATISTICS (Transact-SQL)](https://docs.microsoft.com/en-us/sql/t-sql/statements/create-statistics-transact-sql?view=sql-server-ver15), and [DBCC SHOW\$1STATISTICS (Transact-SQL)](https://docs.microsoft.com/en-us/sql/t-sql/database-console-commands/dbcc-show-statistics-transact-sql?view=sql-server-ver15) in the *SQL Server documentation*.

## MySQL Usage
<a name="chap-sql-server-aurora-mysql.tsql.managingstatistics.mysql"></a>

 Amazon Aurora MySQL-Compatible Edition (Aurora MySQL) supports two modes of statistics management: persistent optimizer statistics and non-persistent optimizer statistics. As the name suggests, persistent statistics are written to disk and survive service restart. Non-persistent statistics are kept in memory only and need to be recreated after service restart. It is recommended to use persistent optimizer statistics (the default for Aurora MySQL) for improved plan stability.

Statistics in Aurora MySQL are created for indexes only. Aurora MySQL doesn’t support independent statistics objects on columns that aren’t part of an index.

Typically, administrators change the statistics management mode by setting the global parameter `innodb_stats_persistent = ON`. This option isn’t supported for Aurora MySQL because it requires server `SUPER` privileges. Therefore, control the statistics management mode by changing the behavior for individual tables using the table option `STATS_PERSISTENT = 1`. There are no column-level or statistics-level options for setting parameter values.

To view statistics metadata, use the `INFORMATION_SCHEMA.STATISTICS` standard view. To view detailed persistent optimizer statistics, use the `innodb_table_stats` and `innodb_index_stats` tables.

The following image shows an example of `mysql.innodb_table_stats` content.

![\[Example of mysql innodb table stats\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-sql-server-aurora-mysql-managing-statistics.png)


The following image shows an example of `mysql.innodb_index_stats` content.

![\[Example of mysql statistics\]](http://docs.aws.amazon.com/dms/latest/sql-server-to-aurora-mysql-migration-playbook/images/pb-sql-server-aurora-mysql-index-statistics.png)


Automatic refresh of statistics is controlled by the global parameter `innodb_stats_auto_recalc`, which is set to `ON` in Aurora MySQL. You can set it individually for each table using the `STATS_AUTO_RECALC=1` option.

To explicitly force refresh of table statistics, use the `ANALYZE TABLE` statement. It is not possible to refresh individual statistics or columns.

Use the `NO_WRITE_TO_BINLOG` or its clearer alias `LOCAL` to avoid replication to replication replicas.

Use `ALTER TABLE …​ ANALYZE PARTITION` to analyze one or more individual partitions. For more information, see [Storage](chap-sql-server-aurora-mysql.storage.md).

**Note**  
 Amazon Relational Database Service (Amazon RDS) for MySQL 8 adds new `INFORMATION_SCHEMA.INNODB_CACHED_INDEXES` table which reports the number of index pages cached in the InnoDB buffer pool for each index.

### Syntax
<a name="chap-sql-server-aurora-mysql.tsql.managingstatistics.mysql.syntax"></a>

```
ANALYZE [NO_WRITE_TO_BINLOG | LOCAL] TABLE <Table Name> [,...];
```

```
CREATE TABLE ( <Table Definition> ) | ALTER TABLE <Table Name>
STATS_PERSISTENT = <1|0>,
STATS_AUTO_RECALC = <1|0>,
STATS_SAMPLE_PAGES = <Statistics Sampling Size>;
```

### Migration Considerations
<a name="chap-sql-server-aurora-mysql.tsql.managingstatistics.mysql.considerations"></a>

Unlike SQL Server, Aurora MySQL collects only density information. It doesn’t collect detailed key distribution histograms. This difference is critical for understanding run plans and troubleshooting performance issues, which aren’t affected by individual values used by query parameters.

Statistics collection is managed at the table level. You can’t manage individual statistics objects or individual columns. In most cases, that shouldn’t pose a challenge for successful migration.

### Examples
<a name="chap-sql-server-aurora-mysql.tsql.managingstatistics.mysql.examples"></a>

Create a table with explicitly set statistics options.

```
CREATE TABLE MyTable
(
    Col1 INT NOT NULL AUTO_INCREMENT,
    Col2 VARCHAR(255),
    DateCol DATETIME,
    PRIMARY KEY (Col1),
    INDEX IDX_DATE (DateCol)
) ENGINE=InnoDB,
STATS_PERSISTENT=1,
STATS_AUTO_RECALC=1,
STATS_SAMPLE_PAGES=25;
```

Refresh all statistics for `MyTable1` and `MyTable2`.

```
ANALYZE TABLE MyTable1, MyTable2;
```

Change `MyTable` to use non persistent statistics.

```
ALTER TABLE MyTable STATS_PERSISTENT=0;
```

## Summary
<a name="chap-sql-server-aurora-mysql.tsql.managingstatistics.summary"></a>

The following table identifies similarities, differences, and key migration considerations.


| Feature | SQL Server |  Aurora MySQL  | Comments | 
| --- | --- | --- | --- | 
|  Column statistics  |   `CREATE STATISTICS`   |  N/A  |  | 
|  Index statistics  |  Implicit with every index  |  Implicit with every index  |  Statistics are maintained automatically for every table index.  | 
|  Refresh / update statistics  |   `UPDATE STATISTICS`   `EXECUTE sp_updatestats`   |   `ANALYZE TABLE`   |  Minimal scope in Aurora MySQL is the entire table. No control over individual statistics.  | 
|  Auto create statistics  |   `AUTO_CREATE_STATISTICS` database option  |  N/A  |  | 
|  Auto update statistics  |   `AUTO_UPDATE_STATISTICS` database option  |   `STATS_AUTO_RECALC` table option  |  | 
|  Statistics sampling  |  Use the `SAMPLE` option of `CREATE` and `UPDATE STATISTICS`   |   `STATS_SAMPLE_PAGES` table option  |  Can only use page number, not percentage for `STATS_SAMPLE_PAGES`.  | 
|  Full scan refresh  |  Use the `FULLSCAN` option of `CREATE` and `UPDATE STATISTICS`   |  N/A  |  Using a very large `STATS_SAMPLE_PAGES` may serve the same purpose.  | 
|  Non-persistent statistics  |  N/A  |  Use `STATS_PERSISTENT=0` table option  |  | 

For more information, see [The INFORMATION\$1SCHEMA STATISTICS Table](https://dev.mysql.com/doc/refman/5.7/en/information-schema-statistics-table.html) [Configuring Persistent Optimizer Statistics Parameters](https://dev.mysql.com/doc/refman/5.7/en/innodb-persistent-stats.html), [Configuring Optimizer Statistics for InnoDB](https://dev.mysql.com/doc/refman/5.7/en/innodb-performance-optimizer-statistics.html), and [Configuring Optimizer Statistics for InnoDB](https://dev.mysql.com/doc/refman/5.7/en/innodb-performance-optimizer-statistics.html) in the *MySQL documentation*.