A comprehensive technical analysis of table creation syntax, data types, constraints, and advanced features between two leading database management systems
The CREATE TABLE statement is a foundational SQL command that defines the structure of a database table, including column definitions, data types, constraints, indexes, and storage parameters. While the core purpose remains consistent across database systems, MySQL and SQL Server implement this command with significant syntactical and functional differences.
These differences reflect each system's architectural design, optimization strategies, and historical development. Understanding these nuances is essential for database administrators, developers working in multi-database environments, and organizations planning database migrations. For comprehensive documentation on SQL CREATE TABLE syntax across all major database systems, the SQL CREATE TABLE reference guide provides detailed specifications and examples.
MySQL's CREATE TABLE implementation is characterized by its flexibility with storage engines, character sets, and collations. The syntax allows for detailed configuration of how data is stored, indexed, and managed at both the table and column levels.
CREATE TABLE [IF NOT EXISTS] [database_name.]table_name ( column_name1 data_type [NOT NULL | NULL] [DEFAULT default_value] [AUTO_INCREMENT] [UNIQUE [KEY]] [PRIMARY KEY] [COMMENT 'string'], column_name2 data_type [column_attributes], ..., [CONSTRAINT [constraint_name]] PRIMARY KEY (column_name, ...), [CONSTRAINT [constraint_name]] UNIQUE [INDEX|KEY] [index_name] (column_name, ...), [CONSTRAINT [constraint_name]] FOREIGN KEY [index_name] (column_name, ...) REFERENCES table_name (column_name, ...) [ON DELETE reference_option] [ON UPDATE reference_option], [CONSTRAINT [constraint_name]] CHECK (expr) ) [table_options]; [table_options]: [ENGINE = engine_name] [AUTO_INCREMENT = value] [AVG_ROW_LENGTH = value] [CHARACTER SET = charset_name] [COLLATE = collation_name] [COMMENT = 'string'] [COMPRESSION = {'ZLIB'|'LZ4'|'NONE'}] [CONNECTION = 'connect_string'] [DATA DIRECTORY = 'absolute path to directory'] [INDEX DIRECTORY = 'absolute path to directory'] [DELAY_KEY_WRITE = {0 | 1}] [ENCRYPTION = {'Y' | 'N'}] [INSERT_METHOD = { NO | FIRST | LAST }] [KEY_BLOCK_SIZE = value] [MAX_ROWS = value] [MIN_ROWS = value] [PACK_KEYS = {0 | 1 | DEFAULT}] [PASSWORD = 'string'] [ROW_FORMAT = {DEFAULT|DYNAMIC|FIXED|COMPRESSED|REDUNDANT|COMPACT}] [STATS_AUTO_RECALC = {DEFAULT|0|1}] [STATS_PERSISTENT = {DEFAULT|0|1}] [STATS_SAMPLE_PAGES = value] [TABLESPACE tablespace_name [STORAGE {DISK|MEMORY}]] [UNION = (table_name[,table_name]...)]
CREATE TABLE IF NOT EXISTS products ( product_id INT UNSIGNED NOT NULL AUTO_INCREMENT, product_name VARCHAR(255) NOT NULL, product_code VARCHAR(50) NOT NULL, category_id INT UNSIGNED NOT NULL, supplier_id INT UNSIGNED, price DECIMAL(10,2) NOT NULL DEFAULT 0.00, cost DECIMAL(10,2), stock_quantity INT NOT NULL DEFAULT 0, reorder_level INT DEFAULT 5, weight FLOAT(8,2), dimensions VARCHAR(50), short_description VARCHAR(255), long_description TEXT, is_featured BOOLEAN DEFAULT FALSE, is_active BOOLEAN DEFAULT TRUE, date_added TIMESTAMP DEFAULT CURRENT_TIMESTAMP, last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (product_id), UNIQUE KEY idx_product_code (product_code), INDEX idx_category (category_id), INDEX idx_supplier (supplier_id), INDEX idx_product_name (product_name(50)), FULLTEXT INDEX idx_product_search (product_name, short_description), CONSTRAINT fk_product_category FOREIGN KEY (category_id) REFERENCES categories (category_id) ON UPDATE CASCADE ON DELETE RESTRICT, CONSTRAINT fk_product_supplier FOREIGN KEY (supplier_id) REFERENCES suppliers (supplier_id) ON UPDATE CASCADE ON DELETE SET NULL, CONSTRAINT chk_price CHECK (price >= 0), CONSTRAINT chk_stock CHECK (stock_quantity >= 0) ) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='Product catalog with inventory information' MAX_ROWS=1000000;
Type | Storage | Range | Usage |
---|---|---|---|
TINYINT | 1 byte | -128 to 127 or 0 to 255 (unsigned) | Small integer values, flags |
INT | 4 bytes | -2^31 to 2^31-1 or 0 to 2^32-1 (unsigned) | Standard integer values, IDs |
DECIMAL(p,s) | Variable | Depends on precision (p) and scale (s) | Exact numeric values (money) |
FLOAT | 4 bytes | ±1.175494351E-38 to ±3.402823466E+38 | Approximate numeric values |
Type | Storage | Max Size | Usage |
---|---|---|---|
CHAR(n) | n bytes | 255 characters | Fixed-length strings |
VARCHAR(n) | Length + 1-2 bytes | 65,535 bytes | Variable-length strings |
TEXT | Length + 2 bytes | 65,535 bytes | Long text strings |
LONGTEXT | Length + 4 bytes | 4GB | Very long text strings |
Type | Storage | Range | Format |
---|---|---|---|
DATE | 3 bytes | 1000-01-01 to 9999-12-31 | YYYY-MM-DD |
TIME | 3 bytes | -838:59:59 to 838:59:59 | HH:MM:SS |
DATETIME | 8 bytes | 1000-01-01 00:00:00 to 9999-12-31 23:59:59 | YYYY-MM-DD HH:MM:SS |
TIMESTAMP | 4 bytes | 1970-01-01 00:00:01 to 2038-01-19 03:14:07 | YYYY-MM-DD HH:MM:SS |
MySQL's AUTO_INCREMENT attribute creates automatically incrementing column values, typically for primary keys. Unlike SQL Server's IDENTITY, AUTO_INCREMENT can be modified at the table level.
-- Setting the initial AUTO_INCREMENT value
CREATE TABLE products (
product_id INT AUTO_INCREMENT PRIMARY KEY,
product_name VARCHAR(100)
) AUTO_INCREMENT = 1000;
-- Modifying the AUTO_INCREMENT value
ALTER TABLE products AUTO_INCREMENT = 2000;
MySQL allows specifying different storage engines for tables, each with unique characteristics for performance, transaction support, and data integrity.
Engine | Transactions | XA | Savepoints | Foreign Keys |
---|---|---|---|---|
InnoDB | Yes | Yes | Yes | Yes |
MyISAM | No | No | No | No |
MEMORY | No | No | No | No |
ARCHIVE | No | No | No | No |
MySQL provides extensive support for character sets and collations at the database, table, and column levels.
-- Table with specific character set and collation
CREATE TABLE multilingual_content (
content_id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
content_en TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci,
content_fr TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_french_ci,
content_de TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
MySQL supports various index types including B-tree, hash, fulltext, and spatial indexes, with options for partial indexing of string columns.
CREATE TABLE articles (
article_id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
author VARCHAR(100),
published_date DATE,
-- Regular index
INDEX idx_author (author),
-- Composite index
INDEX idx_author_date (author, published_date),
-- Partial index (only indexes first 50 characters)
INDEX idx_title (title(50)),
-- Fulltext index for search
FULLTEXT INDEX idx_content (title, content)
);
MySQL provides automatic timestamp updating for tracking row modifications, a feature not directly available in SQL Server.
CREATE TABLE audit_records (
record_id INT AUTO_INCREMENT PRIMARY KEY,
record_data VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
MySQL's CREATE TABLE syntax offers extensive configuration options that make it highly adaptable to different application requirements. For a comprehensive reference on all MySQL CREATE TABLE options, syntax variations, and best practices, visit the MySQL CREATE TABLE documentation.
Recent MySQL versions have introduced several important enhancements to the CREATE TABLE syntax:
SQL Server's CREATE TABLE syntax reflects its enterprise-oriented architecture with features for filegroups, partitioning, and advanced constraint management. The syntax emphasizes explicit schema qualification and named constraints.
CREATE TABLE [ database_name . [ schema_name ] . | schema_name . ] table_name ( column_name1 data_type [ NULL | NOT NULL ] [ CONSTRAINT constraint_name DEFAULT constant_expression ] [ IDENTITY [ ( seed , increment ) ] ] [ ROWGUIDCOL ] [ CONSTRAINT constraint_name { PRIMARY KEY | UNIQUE } [ CLUSTERED | NONCLUSTERED ] ] [ CONSTRAINT constraint_name CHECK ( logical_expression ) ] [ CONSTRAINT constraint_name FOREIGN KEY REFERENCES referenced_table_name ( referenced_column_name ) [ ON DELETE { NO ACTION | CASCADE | SET NULL | SET DEFAULT } ] [ ON UPDATE { NO ACTION | CASCADE | SET NULL | SET DEFAULT } ] ] [ COLLATE collation_name ] [ SPARSE ] [ MASKED WITH ( FUNCTION = 'mask_function' ) ] [ ENCRYPTED WITH ( COLUMN_ENCRYPTION_KEY = key_name, ENCRYPTION_TYPE = { DETERMINISTIC | RANDOMIZED }, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256' ) ] [ GENERATED ALWAYS AS { ROW | TRANSACTION_ID | SEQUENCE_NUMBER } [ START | END ] [ HIDDEN ] ] [ GENERATED ALWAYS AS ( computed_column_expression ) [ PERSISTED [ NOT NULL ] ] ] [ ,...n ] [ CONSTRAINT constraint_name PRIMARY KEY [ CLUSTERED | NONCLUSTERED ] ( column [ ASC | DESC ] [ ,...n ] ) [ WITH ( <index_option> [ ,...n ] ) ] ] [ CONSTRAINT constraint_name UNIQUE [ CLUSTERED | NONCLUSTERED ] ( column [ ASC | DESC ] [ ,...n ] ) [ WITH ( <index_option> [ ,...n ] ) ] ] [ CONSTRAINT constraint_name FOREIGN KEY ( column [ ,...n ] ) REFERENCES referenced_table_name ( referenced_column_name [ ,...n ] ) [ ON DELETE { NO ACTION | CASCADE | SET NULL | SET DEFAULT } ] [ ON UPDATE { NO ACTION | CASCADE | SET NULL | SET DEFAULT } ] ] [ CONSTRAINT constraint_name CHECK ( logical_expression ) ] [ PERIOD FOR SYSTEM_TIME ( system_start_time_column_name, system_end_time_column_name ) ] ) [ WITH ( <table_option> [ ,...n ] ) ] [ ON { partition_scheme_name ( partition_column_name ) | filegroup | "default" } ] [ TEXTIMAGE_ON { filegroup | "default" } ] [ FILESTREAM_ON { partition_scheme_name | filegroup | "default" } ] [ PARTITION ( column_name RANGE [ LEFT | RIGHT ] FOR VALUES ( [ boundary_value [ ,...n ] ] ) ) ];
CREATE TABLE Production.Products ( ProductID INT NOT NULL IDENTITY(1001, 1), ProductName NVARCHAR(255) NOT NULL, ProductCode NVARCHAR(50) NOT NULL, CategoryID INT NOT NULL, SupplierID INT NULL, Price DECIMAL(10, 2) NOT NULL CONSTRAINT DF_Products_Price DEFAULT 0.00, Cost DECIMAL(10, 2) NULL, StockQuantity INT NOT NULL CONSTRAINT DF_Products_StockQuantity DEFAULT 0, ReorderLevel INT NULL CONSTRAINT DF_Products_ReorderLevel DEFAULT 5, Weight FLOAT NULL, Dimensions NVARCHAR(50) NULL, ShortDescription NVARCHAR(255) NULL, LongDescription NVARCHAR(MAX) NULL, IsFeatured BIT CONSTRAINT DF_Products_IsFeatured DEFAULT 0, IsActive BIT CONSTRAINT DF_Products_IsActive DEFAULT 1, DateAdded DATETIME2 CONSTRAINT DF_Products_DateAdded DEFAULT GETDATE(), LastUpdated DATETIME2 NULL, CONSTRAINT PK_Products PRIMARY KEY CLUSTERED (ProductID), CONSTRAINT UQ_Products_ProductCode UNIQUE NONCLUSTERED (ProductCode), CONSTRAINT FK_Products_Categories FOREIGN KEY (CategoryID) REFERENCES Production.Categories (CategoryID) ON UPDATE CASCADE ON DELETE RESTRICT, CONSTRAINT FK_Products_Suppliers FOREIGN KEY (SupplierID) REFERENCES Production.Suppliers (SupplierID) ON UPDATE CASCADE ON DELETE SET NULL, CONSTRAINT CK_Products_Price CHECK (Price >= 0), CONSTRAINT CK_Products_StockQuantity CHECK (StockQuantity >= 0) ) WITH ( DATA_COMPRESSION = PAGE, DURABILITY = SCHEMA_AND_DATA ) ON ProductsFileGroup TEXTIMAGE_ON LargeDataFileGroup; -- Create indexes CREATE NONCLUSTERED INDEX IX_Products_CategoryID ON Production.Products (CategoryID); CREATE NONCLUSTERED INDEX IX_Products_SupplierID ON Production.Products (SupplierID) WHERE SupplierID IS NOT NULL; CREATE NONCLUSTERED INDEX IX_Products_ProductName ON Production.Products (ProductName); -- Create a full-text index CREATE FULLTEXT INDEX ON Production.Products (ProductName, ShortDescription) KEY INDEX PK_Products ON ProductCatalogFullTextIndex WITH CHANGE_TRACKING AUTO;
Type | Storage | Range | Usage |
---|---|---|---|
TINYINT | 1 byte | 0 to 255 | Small integer values |
INT | 4 bytes | -2^31 to 2^31-1 | Standard integer values, IDs |
DECIMAL(p,s) | 5-17 bytes | -10^38+1 to 10^38-1 | Exact numeric values (money) |
FLOAT | 4 or 8 bytes | -1.79E+308 to 1.79E+308 | Approximate numeric values |
Type | Storage | Max Size | Usage |
---|---|---|---|
CHAR(n) | n bytes | 8,000 characters | Fixed-length non-Unicode strings |
VARCHAR(n) | Actual length + 2 bytes | 8,000 characters or MAX (2GB) | Variable-length non-Unicode strings |
NCHAR(n) | 2×n bytes | 4,000 characters | Fixed-length Unicode strings |
NVARCHAR(n) | 2×actual length + 2 bytes | 4,000 characters or MAX (2GB) | Variable-length Unicode strings |
Type | Storage | Range | Precision |
---|---|---|---|
DATE | 3 bytes | 0001-01-01 to 9999-12-31 | 1 day |
TIME | 3-5 bytes | 00:00:00.0000000 to 23:59:59.9999999 | 100ns |
DATETIME2 | 6-8 bytes | 0001-01-01 to 9999-12-31 | 100ns |
DATETIMEOFFSET | 8-10 bytes | 0001-01-01 to 9999-12-31 (with time zone) | 100ns |
SQL Server uses the IDENTITY property for auto-incrementing columns, with explicit seed and increment values.
-- Creating a table with IDENTITY column
CREATE TABLE Products (
ProductID INT IDENTITY(1000, 1) PRIMARY KEY,
ProductName NVARCHAR(100) NOT NULL
);
-- You cannot directly set IDENTITY values in INSERT statements
-- To insert explicit values, you must use:
SET IDENTITY_INSERT Products ON;
INSERT INTO Products (ProductID, ProductName) VALUES (1500, 'Custom Product');
SET IDENTITY_INSERT Products OFF;
SQL Server organizes objects within schemas, providing an additional namespace level for better organization and security management.
-- Creating a schema
CREATE SCHEMA Sales;
GO
-- Creating a table in the schema
CREATE TABLE Sales.Customers (
CustomerID INT PRIMARY KEY,
CustomerName NVARCHAR(100)
);
-- Granting permissions at the schema level
GRANT SELECT ON SCHEMA::Sales TO SalesRole;
SQL Server allows tables and indexes to be placed on specific filegroups for performance optimization and storage management.
-- Creating filegroups
ALTER DATABASE SalesDB ADD FILEGROUP FG_Current;
ALTER DATABASE SalesDB ADD FILEGROUP FG_Archive;
-- Adding files to filegroups
ALTER DATABASE SalesDB ADD FILE (
NAME = 'SalesData_Current',
FILENAME = 'C:\Data\SalesData_Current.ndf',
SIZE = 100MB
) TO FILEGROUP FG_Current;
-- Creating a partitioned table
CREATE PARTITION FUNCTION SalesByDatePF (DATETIME2)
AS RANGE RIGHT FOR VALUES ('2023-01-01', '2024-01-01');
CREATE PARTITION SCHEME SalesByDatePS
AS PARTITION SalesByDatePF TO (FG_Archive, FG_Current, FG_Current);
CREATE TABLE Sales.Orders (
OrderID INT IDENTITY(1,1) PRIMARY KEY,
OrderDate DATETIME2 NOT NULL,
CustomerID INT NOT NULL,
Amount DECIMAL(10,2) NOT NULL
) ON SalesByDatePS(OrderDate);
SQL Server supports computed columns that can be either virtual (calculated on-the-fly) or persisted (stored physically in the table).
CREATE TABLE Products (
ProductID INT IDENTITY(1,1) PRIMARY KEY,
Price DECIMAL(10,2) NOT NULL,
DiscountRate DECIMAL(5,2) NOT NULL DEFAULT 0,
-- Virtual computed column (calculated when queried)
DiscountAmount AS (Price * (DiscountRate / 100)),
-- Persisted computed column (stored in the table)
FinalPrice AS (Price - (Price * (DiscountRate / 100))) PERSISTED
);
-- You can index persisted computed columns
CREATE INDEX IX_Products_FinalPrice ON Products(FinalPrice);
SQL Server supports system-versioned temporal tables that automatically track historical data changes.
CREATE TABLE Products (
ProductID INT IDENTITY(1,1) PRIMARY KEY,
ProductName NVARCHAR(100) NOT NULL,
Price DECIMAL(10,2) NOT NULL,
ValidFrom DATETIME2 GENERATED ALWAYS AS ROW START NOT NULL,
ValidTo DATETIME2 GENERATED ALWAYS AS ROW END NOT NULL,
PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.ProductsHistory));
-- Query data at a specific point in time
SELECT * FROM Products
FOR SYSTEM_TIME AS OF '2023-01-15 12:00:00';
SQL Server's CREATE TABLE syntax provides enterprise-grade features for performance, security, and data management. For comprehensive documentation on SQL Server's CREATE TABLE syntax and all available options, refer to the SQL Server CREATE TABLE syntax guide.
Recent SQL Server versions have introduced several important enhancements to table creation:
Beyond basic table creation, MySQL and SQL Server offer advanced features that address specific database requirements. Understanding these differences is crucial for optimizing database design and performance.
Feature | MySQL | SQL Server |
---|---|---|
Named Constraints | Optional | Common practice |
CHECK Constraints | Supported since MySQL 8.0.16 | Fully supported |
Foreign Key Actions | CASCADE, SET NULL, RESTRICT, NO ACTION | CASCADE, SET NULL, SET DEFAULT, NO ACTION |
Default Constraints | Inline with column definition | Can be named and added separately |
Computed Columns | Generated columns (virtual or stored) | Computed columns (virtual or persisted) |
CREATE TABLE orders (
order_id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT NOT NULL,
order_date DATE NOT NULL,
total_amount DECIMAL(10,2) NOT NULL,
CONSTRAINT fk_customer
FOREIGN KEY (customer_id)
REFERENCES customers(customer_id)
ON DELETE RESTRICT
ON UPDATE CASCADE,
CONSTRAINT chk_total
CHECK (total_amount > 0)
);
CREATE TABLE Orders (
OrderID INT IDENTITY(1,1) PRIMARY KEY,
CustomerID INT NOT NULL,
OrderDate DATE NOT NULL
CONSTRAINT DF_Orders_OrderDate DEFAULT GETDATE(),
TotalAmount DECIMAL(10,2) NOT NULL,
CONSTRAINT FK_Orders_Customers
FOREIGN KEY (CustomerID)
REFERENCES Customers(CustomerID)
ON DELETE NO ACTION
ON UPDATE CASCADE,
CONSTRAINT CK_Orders_TotalAmount
CHECK (TotalAmount > 0)
);
Feature | MySQL | SQL Server |
---|---|---|
Index Types | B-tree, Hash, Fulltext, Spatial | B-tree, Columnstore, XML, Spatial, Hash, Memory-optimized |
Partitioning | RANGE, LIST, HASH, KEY partitioning | RANGE partitioning with filegroups |
Compression | Row and page compression (InnoDB) | Row, page, and columnstore compression |
Clustered Indexes | Primary key is always clustered | Can specify CLUSTERED or NONCLUSTERED |
In-Memory Tables | MEMORY storage engine (limited features) | Memory-optimized tables with durability options |
CREATE TABLE sales (
id INT NOT NULL,
created_at DATE NOT NULL,
customer_id INT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
PRIMARY KEY(id, created_at)
)
PARTITION BY RANGE (YEAR(created_at)) (
PARTITION p0 VALUES LESS THAN (2020),
PARTITION p1 VALUES LESS THAN (2021),
PARTITION p2 VALUES LESS THAN (2022),
PARTITION p3 VALUES LESS THAN (2023),
PARTITION p4 VALUES LESS THAN (2024),
PARTITION p5 VALUES LESS THAN MAXVALUE
);
-- Create partition function
CREATE PARTITION FUNCTION SalesDateRangePF (DATE)
AS RANGE RIGHT FOR VALUES (
'2020-01-01', '2021-01-01', '2022-01-01',
'2023-01-01', '2024-01-01'
);
-- Create partition scheme
CREATE PARTITION SCHEME SalesDateRangePS
AS PARTITION SalesDateRangePF
TO (
FG_Archive, FG_2020, FG_2021,
FG_2022, FG_2023, FG_Current
);
-- Create partitioned table
CREATE TABLE Sales (
ID INT NOT NULL,
CreatedAt DATE NOT NULL,
CustomerID INT NOT NULL,
Amount DECIMAL(10,2) NOT NULL,
CONSTRAINT PK_Sales PRIMARY KEY (ID, CreatedAt)
) ON SalesDateRangePS(CreatedAt);
Feature | MySQL | SQL Server |
---|---|---|
Storage Engines | Multiple engines (InnoDB, MyISAM, etc.) | Single engine with different options |
Schema Organization | Databases as schema containers | Schemas within databases |
File Management | Tablespaces with limited control | Filegroups with detailed control |
LOB Storage | Inline with table data | Can be stored in separate filegroups |
Temporary Tables | CREATE TEMPORARY TABLE | CREATE TABLE #temp or @table_variable |
-- Transaction-safe table with foreign keys
CREATE TABLE customers (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE
) ENGINE=InnoDB;
-- Fast read-only table without transactions
CREATE TABLE log_entries (
id INT AUTO_INCREMENT PRIMARY KEY,
log_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
message TEXT NOT NULL
) ENGINE=MyISAM;
-- In-memory table for temporary data
CREATE TABLE cache_items (
cache_key VARCHAR(100) PRIMARY KEY,
cache_value TEXT,
expiry TIMESTAMP
) ENGINE=MEMORY;
-- Create filegroups
ALTER DATABASE SalesDB ADD FILEGROUP FG_Data;
ALTER DATABASE SalesDB ADD FILEGROUP FG_Index;
ALTER DATABASE SalesDB ADD FILEGROUP FG_Archive;
ALTER DATABASE SalesDB ADD FILEGROUP FG_LOB;
-- Add files to filegroups
ALTER DATABASE SalesDB ADD FILE (
NAME = 'SalesData',
FILENAME = 'C:\Data\SalesData.ndf',
SIZE = 100MB
) TO FILEGROUP FG_Data;
-- Create table with different storage locations
CREATE TABLE Sales.Orders (
OrderID INT IDENTITY(1,1),
OrderDate DATETIME2 NOT NULL,
CustomerID INT NOT NULL,
OrderDetails NVARCHAR(MAX),
CONSTRAINT PK_Orders PRIMARY KEY (OrderID)
) ON FG_Data
TEXTIMAGE_ON FG_LOB;
Feature | MySQL | SQL Server |
---|---|---|
JSON Support | JSON data type with functions | NVARCHAR with JSON functions |
Spatial Data | Spatial data types and functions | GEOMETRY and GEOGRAPHY types |
XML Support | Limited XML functions | XML data type with XQuery |
Temporal Data | No built-in temporal tables | System-versioned temporal tables |
Hierarchical Data | No specialized type | HIERARCHYID data type |
CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
attributes JSON,
INDEX idx_attributes ((CAST(attributes->>'$.color' AS CHAR(20))))
);
-- Insert with JSON
INSERT INTO products (name, attributes) VALUES
('Smartphone', '{"color": "black", "storage": 128, "features": ["5G", "waterproof"]}');
-- Query JSON data
SELECT id, name,
JSON_EXTRACT(attributes, '$.color') AS color,
JSON_EXTRACT(attributes, '$.storage') AS storage
FROM products
WHERE JSON_CONTAINS(attributes, '"waterproof"', '$.features');
-- Create a system-versioned temporal table
CREATE TABLE Products (
ProductID INT IDENTITY(1,1) PRIMARY KEY,
ProductName NVARCHAR(100) NOT NULL,
Price DECIMAL(10,2) NOT NULL,
ValidFrom DATETIME2 GENERATED ALWAYS AS ROW START NOT NULL,
ValidTo DATETIME2 GENERATED ALWAYS AS ROW END NOT NULL,
PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo)
)
WITH (
SYSTEM_VERSIONING = ON (
HISTORY_TABLE = dbo.ProductsHistory
)
);
-- Query data as of a point in time
SELECT ProductID, ProductName, Price
FROM Products
FOR SYSTEM_TIME AS OF '2023-01-15 12:00:00';
-- Query data changes over time
SELECT ProductID, ProductName, Price, ValidFrom, ValidTo
FROM Products
FOR SYSTEM_TIME BETWEEN '2023-01-01' AND '2023-12-31';
Migrating database schemas between MySQL and SQL Server requires careful planning and understanding of the syntax differences. Here are key considerations when converting CREATE TABLE statements between these systems.
MySQL Type | SQL Server Equivalent | Notes |
---|---|---|
TINYINT(1) | BIT | MySQL often uses TINYINT(1) for boolean values |
VARCHAR | VARCHAR or NVARCHAR | Use NVARCHAR for Unicode support |
TEXT | VARCHAR(MAX) or NVARCHAR(MAX) | SQL Server doesn't have TEXT type in modern usage |
DATETIME | DATETIME2 | DATETIME2 has better precision and range |
TIMESTAMP | DATETIME2 | Behavior differences need special handling |
UNSIGNED INT | BIGINT | SQL Server doesn't support unsigned integers |
CREATE TABLE products (
product_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL
) AUTO_INCREMENT=1001;
CREATE TABLE Products (
ProductID INT IDENTITY(1001, 1) PRIMARY KEY,
Name NVARCHAR(100) NOT NULL
);
Note: SQL Server's IDENTITY requires both seed and increment values, while MySQL's AUTO_INCREMENT only specifies the starting value.
CREATE TABLE orders (
order_id INT AUTO_INCREMENT PRIMARY KEY,
order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP,
status VARCHAR(20) DEFAULT 'pending'
);
CREATE TABLE Orders (
OrderID INT IDENTITY(1,1) PRIMARY KEY,
OrderDate DATETIME2 DEFAULT GETDATE(),
LastUpdated DATETIME2 NULL,
Status NVARCHAR(20) DEFAULT 'pending'
);
-- Need trigger for auto-update
CREATE TRIGGER trg_Orders_Update
ON Orders AFTER UPDATE AS
BEGIN
UPDATE Orders
SET LastUpdated = GETDATE()
FROM Orders o
INNER JOIN inserted i ON o.OrderID = i.OrderID;
END;
CREATE TABLE orders (
order_id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
INDEX idx_customer (customer_id),
CONSTRAINT fk_customer
FOREIGN KEY (customer_id)
REFERENCES customers(id)
ON DELETE RESTRICT
);
CREATE TABLE Orders (
OrderID INT IDENTITY(1,1) PRIMARY KEY,
CustomerID INT NOT NULL,
Amount DECIMAL(10,2) NOT NULL,
CONSTRAINT FK_Orders_Customers
FOREIGN KEY (CustomerID)
REFERENCES Customers(CustomerID)
ON DELETE NO ACTION
);
CREATE INDEX IX_Orders_CustomerID
ON Orders(CustomerID);
Note: SQL Server typically uses explicit constraint names and often separates index creation from table creation, while MySQL commonly defines indexes inline with the table definition.
When migrating between MySQL and SQL Server, consider these approaches:
For comprehensive migration guidance, syntax conversion tools, and best practices, refer to the SQL database migration guide which provides detailed step-by-step instructions for converting between different database systems.
Regardless of which database system you're using, following these best practices will help you create well-structured, efficient, and maintainable tables that perform well and adapt to changing requirements.
Follow normalization principles to reduce data redundancy and improve data integrity. Typically aim for Third Normal Form (3NF) for most operational databases, but know when to denormalize for performance.
-- Instead of one denormalized table
CREATE TABLE orders_denormalized (
order_id INT PRIMARY KEY,
customer_name VARCHAR(100),
customer_email VARCHAR(100),
customer_address TEXT,
product_name VARCHAR(100),
product_price DECIMAL(10,2),
quantity INT,
total_price DECIMAL(10,2)
);
-- Use normalized tables
CREATE TABLE customers (
customer_id INT PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(100),
address TEXT
);
CREATE TABLE products (
product_id INT PRIMARY KEY,
name VARCHAR(100),
price DECIMAL(10,2)
);
CREATE TABLE orders (
order_id INT PRIMARY KEY,
customer_id INT,
order_date DATE,
FOREIGN KEY (customer_id)
REFERENCES customers(customer_id)
);
CREATE TABLE order_items (
order_id INT,
product_id INT,
quantity INT,
price DECIMAL(10,2),
PRIMARY KEY (order_id, product_id),
FOREIGN KEY (order_id)
REFERENCES orders(order_id),
FOREIGN KEY (product_id)
REFERENCES products(product_id)
);
Select the most appropriate data type for each column to optimize storage, ensure data integrity, and improve query performance.
Data Category | Best Practice | MySQL Example | SQL Server Example |
---|---|---|---|
Integers | Use the smallest integer type that can hold your maximum value | TINYINT for 0-255, SMALLINT for larger ranges | TINYINT, SMALLINT based on range |
Decimal Values | Use DECIMAL for exact values (money), FLOAT for approximate | DECIMAL(10,2) for currency | DECIMAL(10,2) or MONEY |
Strings | Use VARCHAR for variable length, CHAR for fixed | VARCHAR(50) with utf8mb4 charset | NVARCHAR(50) for Unicode |
Dates | Use date-specific types, not strings | DATE, DATETIME, TIMESTAMP | DATE, DATETIME2, DATETIMEOFFSET |
Boolean | Use boolean or bit types, not integers | BOOLEAN or TINYINT(1) | BIT |
Use constraints to enforce data integrity at the database level rather than relying solely on application logic.
CREATE TABLE employees (
employee_id INT PRIMARY KEY,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
hire_date DATE NOT NULL,
department_id INT NOT NULL,
salary DECIMAL(10,2) NOT NULL,
manager_id INT,
-- Check constraint
CONSTRAINT chk_salary
CHECK (salary > 0),
-- Default constraint
CONSTRAINT df_hire_date
DEFAULT CURRENT_DATE FOR hire_date,
-- Foreign key to departments
CONSTRAINT fk_department
FOREIGN KEY (department_id)
REFERENCES departments(department_id),
-- Self-referencing foreign key
CONSTRAINT fk_manager
FOREIGN KEY (manager_id)
REFERENCES employees(employee_id)
);
Following these best practices will help you create optimized database schemas regardless of which database system you're using.
Whether you're working with MySQL, SQL Server, or migrating between database systems, understanding the nuances of CREATE TABLE syntax is essential for building robust, efficient, and maintainable database applications.