Navigating the Nuances: A Developer's Guide to SQL Dialects (SQLite, MySQL, PostgreSQL)¶
As developers, we frequently encounter diverse SQL engines. While core relational concepts are standardized, critical divergences emerge in schema definitions, data types, and procedural extensions like triggers.
This technical reference draws directly from real-world multi-database migrations implemented in the Examination Management System (EMS DB) repository.
Key Areas of Schema Divergence¶
graph LR
A["Relational Requirements"] --> B["PostgreSQL (Strict Types & Functions)"]
A --> C["MySQL (Backticks & Delimiters)"]
A --> D["SQLite (Type Affinity & CHECKs)"]
1. Dropping Objects (Tables, Views, Indexes)¶
The syntax for dropping database objects is broadly compatible, but identifier quoting rules differ.
2. Primary Keys, Auto-Increment, & Types¶
| Feature | SQLite | PostgreSQL | MySQL |
|---|---|---|---|
| Auto-Increment ID | INTEGER PRIMARY KEY (implicitly sequential) |
SERIAL PRIMARY KEY or IDENTITY |
INT AUTO_INCREMENT PRIMARY KEY |
| Text Fields | TEXT |
VARCHAR(n), TEXT |
VARCHAR(n), TEXT |
| Boolean | INTEGER CHECK ("is_correct" IN (0, 1)) |
Native BOOLEAN or SMALLINT |
TINYINT(1) |
| Date/Time | NUMERIC (DATETIME('now', 'localtime')) |
TIMESTAMP WITH TIME ZONE |
DATETIME, CURRENT_TIMESTAMP |
| ENUM Types | Simulated via CHECK ("status" IN (...)) |
Native CREATE TYPE ... AS ENUM |
Inline column ENUM('active', ...) |
Table ID Definition¶
ENUM & Constrained Types¶
3. Trigger Architecture & Execution¶
Triggers represent the most significant syntactical divide across the three engines.
Objective: Compute and set the end timestamp of a tests_sessions row upon creation based on test duration.
-- PostgreSQL mandates separating procedural function from trigger binding
CREATE OR REPLACE FUNCTION set_end_for_test_session_fn()
RETURNS TRIGGER AS $$
BEGIN
NEW.end := NEW.start + (
SELECT "duration" FROM "tests" WHERE "id" = NEW.test_id
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER "set_end_for_test_session"
BEFORE INSERT ON "tests_sessions"
FOR EACH ROW
EXECUTE FUNCTION set_end_for_test_session_fn();
-- MySQL requires custom statement DELIMITERs
DELIMITER $$
CREATE TRIGGER `set_end_for_test_session`
BEFORE INSERT ON `tests_sessions`
FOR EACH ROW
BEGIN
SET NEW.end = DATE_ADD(
IFNULL(NEW.start, NOW()),
INTERVAL (
SELECT TIME_TO_SEC(`duration`) / 60
FROM `tests`
WHERE `id` = NEW.`test_id`
) MINUTE
);
END$$
DELIMITER ;
-- SQLite embeds block logic directly in the trigger definition
CREATE TRIGGER "set_end_for_test_session"
AFTER INSERT ON "tests_sessions"
BEGIN
UPDATE "tests_sessions"
SET "end" = DATETIME(new.start, '+' || (
SELECT TIME(duration)
FROM "tests" AS t
WHERE t."id" = new."test_id"
))
WHERE "id" = new.id;
END;
4. Timestamp & Interval Arithmetic¶
How intervals and timestamps are computed across engines:
5. Conditional Expressions¶
6. Aggregate NULL Handling¶
When aggregating nullable scores (SUM), empty record sets return NULL unless coalesced:
Architectural Comparison Matrix¶
| Feature | PostgreSQL | MySQL | SQLite |
|---|---|---|---|
| Identifier Quoting | "identifier" |
`identifier` |
"identifier" / [identifier] |
| Auto-Increment Strategy | Sequence / IDENTITY |
Table attribute AUTO_INCREMENT |
Table attribute AUTOINCREMENT |
| Procedural Logic | PL/pgSQL (Separate function) |
DELIMITER blocks inside trigger |
BEGIN...END inside trigger |
| Interval Typing | Native INTERVAL |
INTERVAL val UNIT functions |
String modifier parsing |
| Strict Typing | Highly strict & extensible | Strict with mode flags | Type affinity (permissive) |
Next in the Series¶
Reference Repositories¶
- Examination Management System DB (EMS DB): Production multi-dialect repository with complete DDL schemas, seed scripts, and automated test benches.
- PostgreSQL Official Documentation
- MySQL 8.4 Reference Manual
- SQLite Documentation