SQL Practice Logo

SQLPractice Online

ANSI SQL Standards & Database Differences: Real-World

Module: Foundational Concepts

Stripe supports 10+ database engines in their data pipeline. The team uses a strict SQL-92 core layer for 80% of queries, isolating vendor-specific code in thin adapter modules — enabling zero-downtime database migrations.

Stripe-Style Multi-Database SaaS

Stripe's data infrastructure supports multiple database backends (PostgreSQL for transactional data, MySQL for legacy systems, Redshift/Snowflake for analytics). Their approach:

The application layer uses a repository pattern where all SQL is encapsulated in service modules. 80% of queries use SQL-92 core features and work unchanged across databases. The remaining 20% use a thin dialect adapter.

Key architectural decisions:

- All pagination uses LIMIT (MySQL/PostgreSQL) vs FETCH NEXT (SQL Server/Redshift) via a paginate() helper

- All boolean columns stored as SMALLINT with values 0/1 (compatible with BIT, TINYINT, BOOLEAN)

- All upserts routed through an upsert() service method that picks ON CONFLICT vs MERGE

- Schema migrations use Flyway with database-specific scripts in /migrations/postgresql/ and /migrations/mysql/

This architecture enabled Stripe to migrate their core transactional database from MySQL to PostgreSQL over 18 months with zero downtime. 70% of application code required no changes. The remaining 30% only needed adapter updates.

Portable payment query (SQL-92 core)

SELECT

p.payment_id,

p.amount,

p.currency,

COALESCE(p.description, 'No description') AS description,

CURRENT_TIMESTAMP AS queried_at

FROM payments p

INNER JOIN customers c ON p.customer_id = c.customer_id

WHERE p.status = 'succeeded'

AND p.created_at >= CURRENT_TIMESTAMP - INTERVAL '7' DAY

ORDER BY p.created_at DESC

FETCH FIRST 100 ROWS ONLY;

Upsert customer profile (PostgreSQL-specific adapter)

-- PostgreSQL adapter:

INSERT INTO customers (email, name, updated_at)

VALUES ($1, $2, CURRENT_TIMESTAMP)

ON CONFLICT (email) DO UPDATE SET

name = EXCLUDED.name,

updated_at = CURRENT_TIMESTAMP;

-- MySQL adapter (same method, different SQL):

INSERT INTO customers (email, name, updated_at)

VALUES (?, ?, NOW())

ON DUPLICATE KEY UPDATE

name = VALUES(name),

updated_at = NOW();

All

MySQL 5.7 → PostgreSQL Migration with Window Function Gap

A fintech company ran MySQL 5.7 for 8 years. Their analytics team wanted PostgreSQL's window functions, CTEs, and JSON support. During migration, they discovered 47 queries using correlated subquery workarounds that existed because MySQL 5.7 had no window functions.

The pattern they found most often: calculating row rank within a group.