SQL (ANSI SQL:2023)
This assessment applies the coupling account — coupling as access to a shared resource, and the coupling floor [1], [2] — and the knowledge-embodiment theorem [3]; the language is assessed against its official reference [4].
1 Assessment
This page assesses SQL in five parts: core coupling — what the language’s own constructs force; ecosystem coupling — what the surrounding platform, tooling, and community cost; engineering readiness — whether the artifact can be reproduced, verified, observed, worked in quickly, connected to other languages, and governed openly; the profile — the unscored facts a score cannot express; and the scoring detail — the mechanism marks and the arithmetic behind every score. A ✅ bullet marks a construct that helps. A ⚠️ or ❌ bullet marks a cost; the scoring model does not price every cost named here.
Core coupling 85/100 · Ecosystem coupling 80/100 · Engineering readiness 76/100
1.1 Core coupling
1.1.1 Type
- ✅ Static column typing: Every column, parameter, and result column has a declared type, checked when the statement is prepared or planned
- ✅ Schema is declared independently of queries: A table declares its structure once; queries consume it without redeclaring it
- ❌ No sum types: A column carries one scalar type, so alternatives are modelled with nullable columns or separate tables and the caller must know the encoding (T4)
- ❌ No generics: There is no type parameter, so reuse across types is done by generating DDL, not by the type system (T5)
- ❌ NULL is not type-safe absence: NULL is compatible with every domain, three-valued logic propagates through expressions, and
NULL = NULLis unknown rather than true (T10)
1.1.2 Construction
- ✅ Declarative query construction: A statement states the result set, and the engine chooses the access path, so construction does not couple to a storage layout or an algorithm
- ✅ Views separate consumers from the query behind them: A view exposes a stable relation whose defining query can change without changing its consumers
- ✅ Set-based composition: Joins, unions, and subqueries compose relations, so logic composes without imperative sequencing or shared cursors
- ✅ Schema and use are separated: Tables declare the data, and normalisation stores each fact once so a fact duplicated across columns cannot drift out of agreement
- ⚠️ Triggers fire implicitly: A trigger runs in response to a data change, so the writer of a statement cannot see from the statement which code will run
- ⚠️ Stored procedures couple construction to the engine: SQL/PSM standardises persistent stored modules (Part 4 of ISO/IEC 9075), but the dialects used in practice — PL/pgSQL, PL/SQL, and T-SQL — share little beyond the basics, so the code depends on one engine
1.1.3 Memory
- ✅ No manual memory management: The storage engine owns buffers, caches, and page allocation, and a statement never names an address or frees anything
- ✅ No explicit allocation in queries: The engine materialises or streams a result set, so query text does not couple to a memory layout
- ✅ Resource limits are declarative configuration: Work memory and temp-file behaviour are engine settings, not properties the query must manage
- ⚠️ Large intermediate results still cost memory: An unbounded sort, hash join, or
SELECT *spool can exhaust the engine’s memory even though the language hides allocation - ⚠️ Materialised views and temporary tables persist engine-side state: They pin storage and need a refresh policy, coupling the data to a refresh lifecycle
1.1.4 Error
- ✅ Standard SQLSTATE: An error carries a five-character SQLSTATE class and subclass, so a handler matches a portable code instead of a message string
- ✅ Structured handlers in SQL/PSM:
DECLARE ... HANDLERandEXCEPTIONblocks separate the normal path from the recovery path - ✅ Constraint violations are errors, not silent writes: A primary-key, foreign-key, UNIQUE, or CHECK violation aborts the statement
- ⚠️ Vendor error codes extend SQLSTATE: PostgreSQL
SQLSTATEcodes, OracleORA-numbers, and SQL Server error numbers differ outside the standard classes, so handlers are not portable - ⚠️ Some failures depend on the data: Name and type errors are caught at prepare time, but division by zero, overflow, and constraint violations are detected only at execution
1.1.5 Concurrency
- ✅ Transactions group statements atomically: BEGIN, COMMIT, and ROLLBACK make a unit of work commit or abort as one, so a statement does not coordinate with others by hand
- ✅ Isolation is selectable: READ COMMITTED, REPEATABLE READ, and SERIALIZABLE let the application declare the visibility contract it needs
- ✅ MVCC in PostgreSQL and Oracle: Readers do not block writers and writers do not block readers, so read and write traffic decouple
- ✅ Set-based statements reduce explicit locking: One UPDATE moves many rows without a row-by-row lock protocol in application code
- ⚠️ Default isolation permits anomalies: READ COMMITTED is the default in PostgreSQL, so non-repeatable reads and write skew are possible unless the application raises the level
- ⚠️ Explicit locking couples code to the engine:
SELECT ... FOR UPDATE,LOCK TABLE, and advisory locks are vendor-shaped and must agree with the lock ordering of the whole application
1.1.6 Platform
- ✅ A standard core is portable: DDL, DML, and joins written to core SQL run on PostgreSQL, MySQL, Oracle, SQL Server, Db2, and SQLite with little change
- ✅ Standard call interfaces: ODBC and JDBC let one client reach many engines, so an application does not bind to one engine’s binary protocol
- ✅ Multiple independent implementations: No single vendor owns the language, so the standard cannot be changed unilaterally
- ⚠️ Dialects diverge outside the core: String functions, date arithmetic,
LIMITversusFETCH FIRST, and window-function details differ by vendor - ⚠️ Vendor extensions bind queries to one engine: JSON operators, array types, regex syntax, and identity or sequence clauses are engine-specific
1.1.7 Trade-offs (not coupling)
- Denormalisation is a modelling choice: Storing a value in two places trades redundancy for fewer joins; the language permits it but does not force it
- NULL versus NOT NULL is a modelling decision: Making a column nullable widens its value set, and the three-valued-logic cost follows from that decision, not from a forced dependency
- The optimiser owns the execution plan: A declarative statement gives up control of access paths, so plan hints are a choice about how far to override the engine
- Schema migration is a process, not a mechanism: Changing a column couples deploy order to schema version, but the coupling follows from sharing one schema, which is the purpose of a relational store
- Vendor-specific SQL may be worth the portability cost: Writing to one dialect buys features and performance; the loss of portability is a deliberate trade, not a property of the language
- SQL is not a general-purpose language: The assumption of a database engine and a set-based workload is a scope decision, not a coupling defect
1.2 Ecosystem coupling
1.2.1 Performance
- ✅ Decades of query-optimisation research: Cost-based planners, index selection, join reordering, and predicate pushdown produce good plans without hand-tuning in the common case
- ✅ Set-based execution: One statement moves many rows, so per-row interpreter overhead is amortised by the engine
- ✅ Indexes and partitions are declarative: An index is declared once and then used by any matching query
- ✅ Engines are written in C or C++ with mature storage layers: Throughput and buffer management are the engine’s responsibility, not the query’s
- ⚠️ Plan quality is an engine property: The same query can be fast on PostgreSQL and slow on another engine, and the language guarantees no plan
- ⚠️ Plans can regress without a query change: New statistics, a version upgrade, or changed data volume can select a worse plan
1.2.2 Ecosystem
- ❌ Tooling is fragmented by dialect: ORMs, migration tools, linters, and formatters commonly target one engine’s dialect, and there is no single SQL toolchain (Ec2)
- ✅ The standard interfaces are universal: ODBC, JDBC, and ADO.NET drivers exist for nearly every engine and host language
- ✅ Mature, independently maintained engines: PostgreSQL, MySQL, Oracle, SQL Server, Db2, and SQLite are all long-lived and actively developed
- ✅ A very large body of published knowledge: Standards documents, engine manuals, and books are abundant and stable over decades
1.2.3 Evolution
- ❌ The standard evolves slowly: SQL:2023 appeared six years after SQL:2016, so a needed feature can wait years for standardisation (V4)
- ✅ The language is broadly backward compatible: New editions add features and rarely remove them, so decades-old queries keep running
- ✅ Vendor implementations lead the standard: PostgreSQL, Oracle, and others ship features before standardisation, which gives users early access
- ⚠️ Adoption lags publication: An engine may take years to implement a newly standardised feature, so code written to the standard may not run everywhere yet
1.2.4 Domain
- ✅ Excellent fit for relational data: The model of relations, tuples, and set operations matches tabular data directly
- ✅ One language spans several data domains: Analytics with window functions, transactional writes with DML, and semi-structured data with SQL/JSON all live in the same language
- ✅ SQL/PGQ in SQL:2023 adds property-graph queries: Graph patterns are expressible without leaving SQL
- ⚠️ Procedural logic is awkward: Row-by-row algorithms and complex control flow read poorly in SQL/PSM and are usually pushed to a host language
- ⚠️ It is not a runtime for arbitrary programs: SQL assumes a database engine and a set-based workload, which limits use outside data management
1.2.5 Learning
- ✅ Basic queries are gentle:
SELECT ... FROM ... WHEREis readable and learnable within days - ✅ The declarative surface hides the hard part: A beginner writes a correct query without knowing the plan the engine will choose
- ⚠️ Advanced work is steep: Query optimisation, join ordering, and window semantics take years to master
- ⚠️ NULL semantics surprise newcomers: Three-valued logic makes an intuitive predicate such as
a <> 5fail to match rows whereais NULL - ⚠️ Dialect knowledge is required in practice: Beyond the standard core, a developer must learn one engine’s functions, types, and plan output
1.2.6 Syntax
- ✅ Keyword-based and readable: Statements read close to English and do not lean on punctuation or symbols
- ✅ Compact for the result produced: A join and filter over several tables fits in a few lines
- ⚠️ DDL is verbose:
CREATE TABLE, constraints, indexes, andALTER TABLEstatements are long and repetitive - ⚠️ Identifier quoting differs by engine: Double quotes, backticks, and square brackets each mark an identifier in a different dialect
1.2.7 Testing
- ✅ Plan inspection is standard practice: PostgreSQL and MySQL report the chosen plan and actual timings through
EXPLAINandEXPLAIN ANALYZE, and Oracle and SQL Server expose equivalent plan and runtime statistics - ✅ Constraints are executable invariants: PRIMARY KEY, FOREIGN KEY, UNIQUE, CHECK, and NOT NULL reject bad data at write time, so tests need not re-check them
- ✅ Transactions isolate a test: A test can run inside a transaction and roll back, leaving the database unchanged
- ✅ Standard schema introspection:
INFORMATION_SCHEMAand catalog views let tooling verify structure - ⚠️ No in-language test framework: Test harnesses live in the host language, so a suite couples to the driver and to a fixture loader
1.2.8 Security
- ❌ Injection is the canonical failure: Because a statement is text, building it by concatenating untrusted input lets that input become code (Q3)
- ✅ Parameterised statements remove the injection surface: Bind variables and prepared statements carry data out of band, so a value cannot change the parse
- ✅ GRANT and REVOKE express least privilege: Access is declared per object and per role
- ✅ Views and row-level security restrict visibility: A view, or PostgreSQL row-level security, limits what a statement can see
- ⚠️ Dynamic SQL re-opens the risk:
EXECUTE IMMEDIATE, dynamic PL/pgSQL, and string-built queries defeat parameterisation where they are unavoidable - ⚠️ Definer’s-rights procedures can escalate: A procedure that runs with its definer’s rights (
SECURITY DEFINER) can grant more access than intended unless it is audited
1.3 Engineering readiness
1.3.1 Reproducibility
- ✅ No charged mechanism in this dimension.
1.3.2 Supply chain
- ⚠️ Published packages carry no signed provenance by default.
1.3.3 Observability
- ⚠️ No stable tracing and metrics standard.
- ⚠️ The standard library has no structured logging.
1.3.4 Feedback
- ✅ No charged mechanism in this dimension.
1.3.5 Interop
- ⚠️ No maintained portable target.
1.3.6 Governance
- ✅ No charged mechanism in this dimension.
1.4 Profile
Unscored, and kept separate from every score on purpose. The profile records what a mechanism price cannot express: what a running program costs, how well a coding model and an agent work in the language, and whether a safety-critical path exists.
1.4.1 Energy and cost
What this axis asks: what does a running program cost in energy and time?
- ⚠️ Runs inside a database engine: energy and time are the engine’s, not the program’s.
1.4.2 AI and agent readiness
What this axis asks: how well does a coding model and an agent work in this language?
1.4.3 Safety certification
What this axis asks: is there a certified or standards-qualified path to a safety-critical system?
- ⚠️ No certified or standards-qualified path for safety-critical systems.
1.5 Scoring detail
1.5.1 Core coupling Dimensions
| Dimension | Mechanism | Description | Present | Cost |
|---|---|---|---|---|
| Type | T1 |
dynamic no static check | — | 0 |
T2 |
weak static | — | 0 | |
T3 |
dynamic/duck dispatch | — | 0 | |
T4 |
no sum types | ✅ | −4 | |
T5 |
no generics | ✅ | −4 | |
T6 |
no namespaces | — | 0 | |
T7 |
type erasure/reflection | — | 0 | |
T8 |
operator overloading | — | 0 | |
T9 |
inheritance required for polymorphism | — | 0 | |
T10 |
no null-safety | ✅ | −8 | |
T11 |
no module boundary | — | 0 | |
T12 |
no effect tracking | ✅ | −8 | |
T13 |
async/sync colouring | — | 0 | |
T14 |
unchecked foreign boundary | ✅ | −4 | |
| Type subtotal | −15 (−28) | |||
| Construction | C1 |
global mutable state by default | — | 0 |
C2 |
no immutability by default | — | 0 | |
C3 |
constructors coupled to allocation | — | 0 | |
C4 |
no constructor/validation | — | 0 | |
| Construction subtotal | 0 | |||
| Memory | M1 |
manual deallocation | — | 0 |
M2 |
GC non-deterministic pauses | — | 0 | |
M3 |
no RAII | — | 0 | |
M4 |
no ownership model | — | 0 | |
| Memory subtotal | 0 | |||
| Error | Er1 |
no structured path | — | 0 |
Er2 |
unchecked exceptions only | — | 0 | |
Er3 |
errors ignored/non-exhaustive | — | 0 | |
Er4 |
no composable result type | — | 0 | |
| Error subtotal | 0 | |||
| Concurrency | K1 |
GIL/no parallel | — | 0 |
K2 |
shared mutable state default | — | 0 | |
K3 |
manual sync no race detection | — | 0 | |
K4 |
no structured concurrency | — | 0 | |
K5 |
no primitives | — | 0 | |
| Concurrency subtotal | 0 | |||
| Platform | P1 |
cannot function outside one vendor/domain | — | 0 |
P2 |
single-vendor but other uses | — | 0 | |
P3 |
legacy/discontinued platform | — | 0 | |
| Platform subtotal | 0 |
Core Dimensions score: 85/100 — total cost = −15 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).
1.5.2 Ecosystem coupling Dimensions
| Dimension | Mechanism | Description | Present | Cost |
|---|---|---|---|---|
| Performance | R1 |
interpreted no AOT/JIT | — | 0 |
R2 |
GC pauses | — | 0 | |
R3 |
JIT warmup | — | 0 | |
| Performance subtotal | 0 | |||
| Ecosystem | Ec1 |
no package manager | — | 0 |
Ec2 |
fragmented tooling | ✅ | −8 | |
Ec3 |
small/niche/discontinued | — | 0 | |
Ec4 |
proprietary | — | 0 | |
Ec5 |
platform-locked tooling | — | 0 | |
Ec6 |
monoculture | — | 0 | |
Ec7 |
legacy burden | — | 0 | |
| Ecosystem subtotal | −8 | |||
| Evolution | V1 |
breaking changes | — | 0 |
V2 |
frozen | — | 0 | |
V3 |
discontinued | — | 0 | |
V4 |
glacial standards | ✅ | −4 | |
| Evolution subtotal | −4 | |||
| Domain | D1 |
domain/vendor lock-in | — | 0 |
D2 |
poor domain fit | — | 0 | |
D3 |
framework forces architecture | — | 0 | |
| Domain subtotal | 0 | |||
| Learning | L1 |
steep | — | 0 |
L2 |
large surface/multi-paradigm | — | 0 | |
L3 |
sparse docs | — | 0 | |
| Learning subtotal | 0 | |||
| Syntax | S1 |
verbose | — | 0 |
S2 |
inconsistent | — | 0 | |
S3 |
punctuation-heavy | — | 0 | |
| Syntax subtotal | 0 | |||
| Testing | G1 |
weak tooling | — | 0 |
G2 |
poor debugging | — | 0 | |
G3 |
runtime-only errors | — | 0 | |
| Testing subtotal | 0 | |||
| Security | Q1 |
memory-unsafe | — | 0 |
Q2 |
dynamic/weak typing risk | — | 0 | |
Q3 |
known vulnerability/injection | ✅ | −8 | |
| Security subtotal | −8 |
Meta Dimensions score: 80/100 — total cost = −20 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).
1.5.3 Engineering readiness Dimensions
| Dimension | Mechanism | Description | Present | Cost |
|---|---|---|---|---|
| Reproducibility | Y1 |
build not reproducible | — | 0 |
Y2 |
dependencies not pinned | — | 0 | |
| Reproducibility subtotal | 0 | |||
| Supply chain | Y3 |
no signed provenance | ✅ | −8 |
Y4 |
registry without namespacing | — | 0 | |
| Supply chain subtotal | −8 | |||
| Observability | Y5 |
no stable tracing standard | ✅ | −8 |
Y6 |
no structured logging | ✅ | −4 | |
| Observability subtotal | −12 | |||
| Feedback | Y7 |
slow build | — | 0 |
Y8 |
no fast in-editor analysis | — | 0 | |
| Feedback subtotal | 0 | |||
| Interop | Y9 |
no foreign-function interface | — | 0 |
Y10 |
no portable target | ✅ | −4 | |
| Interop subtotal | −4 | |||
| Governance | Y11 |
single-vendor governance | — | 0 |
Y12 |
closed specification | — | 0 | |
| Governance subtotal | 0 |
Readiness Dimensions score: 76/100 — total cost = −24 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).
Verdict: SQL’s design yields low language-imposed coupling for the work it is built for. The declarative model separates what a result is from how it is computed, and the schema separates data structure from the queries that consume it. Views decouple consumers from the query behind them, and transactions decouple statements from each other’s timing. Main strengths: the standard core is portable across many independent engines, types come from the schema, and a mature optimiser takes on execution plans. Main weaknesses: NULL is not type-safe, no sum types or generics exist, the standard moves slowly, tooling fragments by dialect, and a string-built statement is a standing injection hazard unless parameters are used.
The formal development. The definitions, theorems, and proofs behind this page are published in:
- On the Nature of Coupling: All Coupling as Shared Resource Access — Zenodo, 2026 — doi:10.5281/zenodo.21925122
- Coupling as a Consequence of Cohesion: Decomposition, the Coupling Floor, and Domain-Imposed Coupling — Zenodo, 2026 — doi:10.5281/zenodo.21925201
- On the Nature of Cohesion: Cohesion as Correctness Relative to a Partitioning Rule — Zenodo, 2026 — doi:10.5281/zenodo.21703193