SQL Differences by Database — Cheat Sheet
The SQL in this tutorial is written for SQLite. Each lesson covers DB-specific differences using MySQL/PostgreSQL tabs, so this page serves as a quick-reference summary table.
Rather than writing SQL that works across all databases, it's more efficient to leverage each DB's native syntax.
Lesson-by-Lesson DB Difference Guide
These are topics covered in detail with SQLite/MySQL/PostgreSQL tabs in each lesson. Refer to the respective lessons.
| Topic | Lesson | Key Difference |
|---|---|---|
| Paging | Lesson 03 | LIMIT vs FETCH FIRST (ANSI) |
| NULL Handling | Lesson 06 | COALESCE (standard) vs IFNULL vs ISNULL vs NVL |
| CASE Expressions | Lesson 07 | IIF (SQLite) vs IF (MySQL) vs CASE (PG) |
| JOIN | Lessons 08-09 | Syntax identical, FULL OUTER JOIN support varies |
| Subqueries | Lesson 10 | Syntax nearly identical |
| Date/Time | Lesson 11 | SUBSTR vs YEAR() vs EXTRACT(), julianday vs DATEDIFF |
| Strings | Lesson 12 | \|\| vs CONCAT(), INSTR vs LOCATE vs POSITION |
| Numeric/Conversion/Conditional | Lesson 13 | RANDOM vs RAND(), GREATEST/LEAST support varies |
| UNION/INTERSECT/EXCEPT | Lesson 14 | Syntax identical, EXCEPT vs MINUS (Oracle) |
| DML (UPSERT) | Lesson 15 | ON CONFLICT vs ON DUPLICATE KEY |
| DDL | Lesson 16 | AUTOINCREMENT vs AUTO_INCREMENT vs GENERATED |
| Transactions | Lesson 17 | BEGIN vs START TRANSACTION |
| Window Functions | Lesson 18 | Syntax identical, minimum version differences |
| CTE | Lesson 19 | WITH RECURSIVE (SQLite/MySQL/PG) vs WITH only (MSSQL/Oracle) |
| Views | Lesson 22 | CREATE OR REPLACE support varies |
| Indexes | Lesson 23 | EXPLAIN syntax, partial index support |
| Triggers | Lesson 24 | SQLite: BEGIN...END, PG: function + trigger separation |
| JSON | Lesson 25 | json_extract vs ->>, JSONB (PG) |
| Stored Procedures | Lesson 26 | SQLite unsupported, DELIMITER (MySQL), PL/pgSQL (PG) |
Data Type Comparison
| Purpose | SQLite | MySQL | PostgreSQL | SQL Server | Oracle |
|---|---|---|---|---|---|
| Integer | INTEGER | INT | INTEGER | INT | NUMBER(10) |
| Fixed-point | REAL | DECIMAL(12,2) | NUMERIC(12,2) | DECIMAL(12,2) | NUMBER(12,2) |
| Floating-point | REAL | DOUBLE | DOUBLE PRECISION | FLOAT | BINARY_DOUBLE |
| Short string | TEXT | VARCHAR(200) | VARCHAR(200) | NVARCHAR(200) | VARCHAR2(200) |
| Long text | TEXT | TEXT | TEXT | NVARCHAR(MAX) | CLOB |
| Date/Time | TEXT (ISO 8601) | DATETIME | TIMESTAMP | DATETIME2 | TIMESTAMP |
| Boolean | INTEGER (0/1) | TINYINT(1) | BOOLEAN | BIT | NUMBER(1) |
| JSON | TEXT + json functions | JSON | JSONB | NVARCHAR(MAX) | CLOB |
| Binary | BLOB | BLOB | BYTEA | VARBINARY(MAX) | BLOB |
| UUID | TEXT | CHAR(36) | UUID | UNIQUEIDENTIFIER | RAW(16) |
SQLite uses a dynamic type system. The type names above represent "type affinity" — in practice, any value can be stored.
Identifier Quoting
When wrapping identifiers that contain reserved words or spaces:
| DB | Syntax | Example |
|---|---|---|
| SQLite | Double quotes or backticks | "order" or `order` |
| MySQL | Backticks (default) | `order` |
| PostgreSQL | Double quotes | "order" (enables case sensitivity) |
| SQL Server | Square brackets | [order] |
| Oracle | Double quotes | "ORDER" (note case sensitivity) |
Recommendation
Avoid using reserved words as identifiers when possible. Using plural forms like orders instead of order, or users instead of user, is safe without quoting.
Auto-Increment Comparison
| DB | Syntax | Notes |
|---|---|---|
| SQLite | INTEGER PRIMARY KEY AUTOINCREMENT |
ROWID-based, AUTOINCREMENT is optional |
| MySQL | INT AUTO_INCREMENT PRIMARY KEY |
One per table, ENGINE=InnoDB |
| PostgreSQL | INTEGER GENERATED ALWAYS AS IDENTITY |
SQL standard. SERIAL is legacy |
| SQL Server | INT IDENTITY(1,1) PRIMARY KEY |
|
| Oracle | NUMBER GENERATED ALWAYS AS IDENTITY |
12c+. Previously: SEQUENCE + TRIGGER |
Details: Lesson 16 DDL
MERGE Statement
MERGE is an ANSI SQL standard that conditionally performs INSERT/UPDATE/DELETE in a single statement.
| DB | Support | Alternative Syntax |
|---|---|---|
| SQLite | Not supported | ON CONFLICT |
| MySQL | Not supported | ON DUPLICATE KEY UPDATE |
| PostgreSQL | 15+ (partial) | ON CONFLICT recommended |
| SQL Server | 2008+ | Full support |
| Oracle | 9i+ | Full support |
MERGE INTO products AS target
USING staging_products AS source
ON target.sku = source.sku
WHEN MATCHED AND source.is_active = 0 THEN
DELETE
WHEN MATCHED THEN
UPDATE SET name = source.name, price = source.price
WHEN NOT MATCHED THEN
INSERT (sku, name, price)
VALUES (source.sku, source.name, source.price);
UPSERT details: Lesson 15 DML
Feature Support Version Matrix
| Feature | SQLite | MySQL | PostgreSQL | SQL Server | Oracle |
|---|---|---|---|---|---|
| Window Functions | 3.25+ | 8.0+ | 8.4+ | 2005+ | 8i+ |
| CTE (WITH) | 3.8.3+ | 8.0+ | 8.4+ | 2005+ | 11gR2+ |
| Recursive CTE | 3.8.3+ | 8.0+ | 8.4+ | 2005+ | 11gR2+ |
| JSON Functions | 3.38+ | 5.7+ | 9.2+ | 2016+ | 12c+ |
| FULL OUTER JOIN | 3.39+ | Not supported | Supported | Supported | Supported |
| INTERSECT/EXCEPT | 3.34+ | 8.0.31+ | Supported | Supported | Supported (MINUS) |
| Partial Index | 3.8+ | Not supported | Supported | Supported (filtered) | Not supported |
| UPSERT | 3.24+ | Supported | 9.5+ | MERGE | MERGE |
| Stored Procedures | Not supported | Supported | Supported | Supported | Supported |
| Triggers | Supported | Supported | Supported | Supported | Supported |
| SEQUENCE | Not supported | Not supported | Supported | Supported | Supported |
Tutorial SQL Conversion Checklist
Items to check when running this tutorial's SQLite queries on other databases:
| # | Check Item | SQLite Original | MySQL | PostgreSQL | SQL Server |
|---|---|---|---|---|---|
| 1 | Row limit | LIMIT 10 |
Same | Same | FETCH NEXT 10 ROWS ONLY |
| 2 | Date extraction | SUBSTR(col, 1, 7) |
DATE_FORMAT(col, '%Y-%m') |
TO_CHAR(col, 'YYYY-MM') |
FORMAT(col, 'yyyy-MM') |
| 3 | Days elapsed | JULIANDAY(a) - JULIANDAY(b) |
DATEDIFF(a, b) |
a::date - b::date |
DATEDIFF(DAY, b, a) |
| 4 | Date addition | DATE('now', '+30 days') |
DATE_ADD(NOW(), INTERVAL 30 DAY) |
CURRENT_DATE + INTERVAL '30 days' |
DATEADD(DAY, 30, GETDATE()) |
| 5 | NULL replacement | IFNULL(x, y) |
IFNULL(x, y) |
COALESCE(x, y) |
ISNULL(x, y) |
| 6 | String concatenation | a \|\| b |
CONCAT(a, b) |
a \|\| b |
CONCAT(a, b) |
| 7 | Boolean | is_active = 1 |
Same | is_active = TRUE |
Same |
| 8 | Type casting | CAST(x AS INTEGER) |
CAST(x AS SIGNED) |
x::integer |
CAST(x AS INT) |
| 9 | Current time | datetime('now') |
NOW() |
NOW() |
GETDATE() |
| 10 | AUTOINCREMENT | INTEGER PRIMARY KEY |
INT AUTO_INCREMENT |
GENERATED ALWAYS AS IDENTITY |
INT IDENTITY(1,1) |
| 11 | Random | RANDOM() |
RAND() |
RANDOM() |
NEWID() |
| 12 | Regex | GLOB '*[0-9]*' |
REGEXP '[0-9]' |
~ '[0-9]' |
LIKE '%[0-9]%' |