SQL Practice Logo

SQLPractice Online

ANSI SQL Standards & Database Differences: Concept

Module: Foundational Concepts

ANSI SQL (officially ISO/IEC 9075) is the international standard that defines how SQL must behave across all database systems. Think of it as the constitution of relational databases — every vendor must implement the core, but each adds their own amendments.

The critical insight: SQL-92 is the practical portability baseline. Every major database (PostgreSQL, MySQL, SQL Server, Oracle, SQLite, Snowflake, BigQuery) supports SQL-92 core. Features added in SQL:1999+ have uneven support. Vendor extensions are completely non-portable.

**The SQL Standard Timeline**

SQL-86 (1986) — The first ANSI standard. Basic SELECT, INSERT, UPDATE, DELETE. Barely resembles modern SQL.

SQL-92 (1992) — The portability baseline. Introduced: JOIN syntax (INNER/LEFT/RIGHT), CASE expressions, CAST(), COALESCE(), string functions (UPPER, LOWER, SUBSTRING), date literals, transaction isolation levels, FETCH FIRST (sort of). Every production database supports this.

SQL:1999 (SQL3) — Introduced: recursive CTEs (WITH RECURSIVE), triggers, procedural language (PSM), user-defined types, BOOLEAN data type (finally). MySQL's support remained patchy for years.

SQL:2003 — The window functions revolution. Introduced: ROW_NUMBER(), RANK(), DENSE_RANK(), LEAD(), LAG(), FIRST_VALUE(), LAST_VALUE(). Also: GENERATED AS IDENTITY (replacing vendor auto-increment hacks), MERGE statement (upsert). MySQL didn't add window functions until 8.0 (2018).

SQL:2008 — Formalized FETCH FIRST n ROWS ONLY (pagination). TRUNCATE TABLE. INSTEAD OF triggers standard.

SQL:2011 — Temporal tables (system-versioned and application-time). Still poorly supported in MySQL.

SQL:2016 — JSON functions standard (JSON_VALUE, JSON_QUERY, JSON_TABLE). Each vendor had already shipped their own JSON API — now they're all different AND there's a standard nobody fully implements.

SQL:2023 — Property graph queries (MATCH clause), enhanced JSON, multi-dimensional arrays. Bleeding edge — essentially unsupported in production databases today.

---

**The Big Four Divergence Points**

These are the areas where databases diverge most aggressively from each other:

**1. Pagination**

- ANSI (SQL:2008): FETCH FIRST n ROWS ONLY / OFFSET n ROWS

- PostgreSQL/MySQL: LIMIT n OFFSET n ← simple but non-standard

- SQL Server (pre-2012): SELECT TOP n ← non-standard, no offset

- SQL Server (2012+): OFFSET/FETCH NEXT (matches ANSI)

- Oracle (pre-12c): WHERE ROWNUM <= n ← the old nightmare

- Oracle (12c+): FETCH FIRST / OFFSET (matches ANSI)

**2. String Concatenation**

- ANSI: || operator (pipe pipe)

- PostgreSQL: || ✓, CONCAT() ✓

- MySQL: CONCAT() only — || is logical OR by default!

- SQL Server: + operator, CONCAT() (2012+)

- Oracle: || ✓, CONCAT() for 2 args only

- Safe choice: CONCAT() works on all four

**3. Auto-Increment Identity**

- ANSI (SQL:2003): GENERATED ALWAYS AS IDENTITY / GENERATED BY DEFAULT AS IDENTITY

- PostgreSQL: SERIAL (legacy), BIGSERIAL, or GENERATED AS IDENTITY (preferred since PG 10)

- MySQL: AUTO_INCREMENT

- SQL Server: IDENTITY(seed, increment)

- Oracle: GENERATED AS IDENTITY (12c+), or legacy SEQUENCE + trigger

**4. Date/Time Functions**

- ANSI: CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP (no parentheses — they're keywords)

- PostgreSQL: NOW(), CURRENT_TIMESTAMP, LOCALTIMESTAMP — all work

- MySQL: NOW(), CURDATE(), CURTIME() — CURRENT_DATE works too

- SQL Server: GETDATE(), SYSDATETIME(), GETUTCDATE() — no CURRENT_DATE without cast

- Oracle: SYSDATE (includes time), TRUNC(SYSDATE) for date only — no CURRENT_DATE without FROM DUAL