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.