Transact-SQL (SQL Server)
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 Transact-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 58/100 · Ecosystem coupling 69/100 · Engineering readiness 61/100
1.1 Core coupling
1.1.1 Type
- ✅ Static typing over SQL data types: Every variable, parameter, and column has a declared type, and a block that violates it fails at parse or bind time
- ✅ Table variables and table-valued parameters carry a declared row type:
DECLARE @t TABLE (...)and a user-defined table type pass a typed row set between units - ✅ A procedure parameter declares its type and direction:
@p int OUTPUTfixes the type and the direction, so a caller binds to a signature - ⚠️
DECLARE @namedefaults to NULL (T10): A declared variable starts as NULL, so an unassigned variable is valid and propagates NULL into the expression - ⚠️ NULL comparison needs three-valued logic: With
ANSI_NULLS ON(the default, and the only supported setting since SQL Server 2017),@x = NULLis unknown, so a check needsIS NULL - ⚠️ There is no type beyond a SQL data type: No user-defined sum type or generic exists, so a variant is modelled with nullable columns or a discriminator
1.1.2 Construction
- ✅ A stored procedure provides modularity with a declared interface:
CREATE PROCEDUREnames the unit, its parameters, and its result, and can return a status value and OUTPUT parameters - ✅ A view or an inline table-valued function separates consumers from the query: The exposed relation is stable while its defining query can change
- ✅ A table-valued parameter passes a row set without a shared temporary table: A user-defined table type plus a TVP parameter removes the need for a global temporary table
- ✅ Set-based statements compose without a cursor:
INSERT ... SELECT,UPDATE ... FROM, andMERGEexpress the whole transformation in one statement - ⚠️ Triggers fire implicitly: An
AFTERorINSTEAD OFtrigger runs in response to a data change, so the writer of a statement cannot see from the statement which code will run - ⚠️ Session
SEToptions change the meaning of a statement:ANSI_NULLSandQUOTED_IDENTIFIERare captured when a stored procedure is created, whileDATEFORMATapplies when the statement runs, so the same text can parse or convert differently elsewhere
1.1.3 Memory
- ✅ No manual memory management: The SQL Server buffer pool and the query workspace own the memory, and a batch never names an address
- ✅ The server grants memory to a query: The query’s memory grant comes from the server’s calculator or the Resource Governor, so the batch does not request bytes
- ✅ Buffer pool and
tempdbbehaviour are server configuration:max server memory, thetempdbfiles, and a Resource Governor pool are settings, not batch properties - ⚠️ A sort or hash can spill to
tempdb: An operation whose grant is too small writes totempdb, which costs I/O even though the language hides allocation - ⚠️ A cursor allocates per fetch: A row-by-row cursor uses workspace per fetch, so the memory pattern follows the loop
1.1.4 Error
- ✅
TRY ... CATCHgives a structured path: TheCATCHblock runs on error and exposesERROR_NUMBER,ERROR_MESSAGE,ERROR_SEVERITY, andERROR_LINE - ✅
THROWandRAISERRORraise with a number and a message:THROW 50001, 'msg', 1carries a number, so a handler can branch on it - ✅
XACT_STATE()and@@TRANCOUNTtell the handler whether the transaction is committable: A doomed transaction is detectable insideCATCH - ⚠️ Errors ignored or non-exhaustive (Er3): A
CATCHblock that swallows the error, or a skipped@@ERRORcheck, lets the batch continue past the failure - ⚠️ Errors do not abort the batch by default: Without
SET XACT_ABORT ON, a statement error rolls back that statement and the batch continues - ⚠️ Severity decides whether the caller sees the error: A low-severity error is informational, so the same statement text can pass a caller by
1.1.5 Concurrency
- ✅ Transactions group statements atomically:
BEGIN TRANSACTION,COMMIT, andROLLBACKmake a unit of work commit or abort as one - ✅ Isolation levels are selectable and versioned:
READ UNCOMMITTED,READ COMMITTED(default),REPEATABLE READ,SNAPSHOT, andSERIALIZABLE, while the database optionsREAD_COMMITTED_SNAPSHOTandALLOW_SNAPSHOT_ISOLATIONswitchREAD COMMITTEDto row versioning and enableSNAPSHOT - ✅ Snapshot isolation removes reader–writer blocking: With
READ_COMMITTED_SNAPSHOT ON, a reader uses row versions and does not block a writer - ✅ Set-based statements reduce explicit locking: One
UPDATEmoves many rows without a row-by-row lock protocol in application code - ❌ Shared mutable state by default (K2):
SEToptions,CONTEXT_INFO, and a global temporary table##tare visible to code the caller did not write - ⚠️ A locking hint couples the query to the engine’s concurrency model:
WITH (NOLOCK),UPDLOCK, andREADPASTare engine-shaped and must agree with the application’s lock ordering
1.1.6 Platform
- ✅ Client interfaces are standard: ODBC, JDBC, OLE DB, and ADO.NET reach the engine, so a client is not bound to one binary protocol
- ✅ Bulk interfaces are documented:
bcpandBULK INSERTuse a documented format, so data exchange is not tied to an application API - ⚠️ Two implementations exist under separate vendors: Microsoft SQL Server and SAP ASE share a lineage and a core syntax, but the two diverge, and Microsoft and SAP document them separately
- ⚠️ Azure SQL is the same vendor with a different deployment: The managed service changes availability, administration, and some features, so code targets a specific surface
- ❌ Cannot function outside one vendor (P1): The language needs SQL Server, Azure SQL, or SAP ASE
- ❌ The T-SQL surface is engine-specific:
TOP,IDENTITY,GO, thesyscatalog views, and thesp_procedures are SQL Server features, so a port to another engine is a rewrite
1.1.7 Trade-offs (not coupling)
- Logic beside the data is deliberate: Keeping a rule in the database guarantees it for every writer
- A cursor is a deliberate trade for row-by-row work: It makes a per-row algorithm expressible, at the cost of the set-based model
- Dynamic SQL is deliberate for DDL and for a flexible query:
sp_executesqlandEXECreach constructs a static statement cannot name NOLOCKtrades consistency for concurrency: A reader that accepts dirty reads avoids blocking- The engine is proprietary by design: The licence buys one integrated stack of engine, tooling, and support
- T-SQL is not a general-purpose language: The assumption of a SQL Server database is a scope decision
1.2 Ecosystem coupling
1.2.1 Performance
- ✅ A cost-based optimiser with a plan cache executes a statement: A procedure’s plan can be reused, so the caller does not choose an access path
- ✅ A columnstore index and batch mode scan analytically:
CREATE COLUMNSTORE INDEXplus batch mode processes a large scan in vectorised batches - ✅ Query Store records plans and regressions: An operator can see a plan change and force a previous plan
- ✅ In-memory OLTP tables are lock-free and memory-optimised:
MEMORY_OPTIMIZED = ONtables and natively compiled procedures remove latch contention - ⚠️ Parameter sniffing can choose a plan for the wrong input: A cached plan built for one parameter value can be poor for another
- ⚠️ A plan can regress without a query change: New statistics, a version upgrade, or a changed data volume can select a worse plan
1.2.2 Ecosystem
- ✅ Tooling is mature and first-party: SQL Server Management Studio, Azure Data Studio, and
sqlcmdcover administration and query work - ✅ The market and the hiring pool are large: SQL Server is widely deployed in Microsoft environments, so documentation, training, and consultants are abundant
- ✅ Cloud deployment is a first-party service: Azure SQL Database and Managed Instance run the same engine with a managed administration model
- ❌ Proprietary (Ec4): The engine, the tooling, and the support come from Microsoft (and, for ASE, from SAP) under licence
- ⚠️ The ASE toolchain is separate from the SQL Server toolchain: SAP maintains its own documentation, tools, and support, so knowledge does not transfer freely
1.2.3 Evolution
- ✅ The release cadence is regular and active: SQL Server ships a major release periodically, and Azure SQL receives continuous updates
- ✅ A compatibility level keeps old behaviour: A database at an older compatibility level keeps the older optimiser and syntax behaviour while the engine is upgraded
- ✅ The engine leads the standard:
TRY ... CATCH,MERGE, window functions, and JSON support arrived as engine features, so users get them without waiting for the standard - ⚠️ A deprecated feature is warned and then removed: The documentation lists deprecated features, and an upgrade can require edits
- ⚠️ Feature availability differs by edition and deployment: An Enterprise feature or an Azure-only feature may not exist on a local Standard edition
1.2.4 Domain
- ✅ Excellent fit for transactional and analytical logic in the database: A stored procedure runs beside the data with a plan and a transaction
- ✅ One language spans OLTP and analytics: Set-based DML, window functions, and a columnstore scan all live in the same language
- ✅ The surrounding pipeline is first-party: Integration Services, the Agent scheduler, and Reporting Services cover the rest of a data workflow
- ❌ Domain and vendor lock-in (D1): The language exists inside SQL Server or SAP ASE, so both the domain and the vendor are fixed
- ⚠️ Procedural row logic reads poorly: A row-by-row algorithm in a cursor is verbose and slow beside its set-based equivalent
- ⚠️ It is not a runtime for arbitrary programs: The language assumes a SQL Server database and a set-based workload
1.2.5 Learning
- ✅ Gentle for a SQL developer: A procedure with
BEGIN ... END,TRY ... CATCH, andSELECT ... INTOis readable to anyone who knows SQL - ✅ Documentation and examples are complete: The Microsoft T-SQL reference documents every statement with examples
- ⚠️ The procedural and the set-based model coexist: A developer must choose between a cursor and a set-based statement, and the wrong choice shows up as a performance problem
- ⚠️ NULL handling must be learned: With
ANSI_NULLS ON,= NULLnever matches, and a comparison with NULL yields unknown - ⚠️ Plan and locking behaviour is a separate body of knowledge: Performance work means reading plans and lock waits rather than syntax
- ⚠️ The SAP ASE dialect diverges in details: Differences in
GO,IDENTITY, and built-in functions mean knowledge transfers only in part
1.2.6 Syntax
- ✅ Keyword-based and readable:
SELECT ... FROM ... WHEREandBEGIN ... ENDread close to English - ✅ A procedure is a readable unit: The parameter list, the body, and the
RETURNvalue are declared in oneCREATE PROCEDUREstatement - ✅
GOmarks a batch boundary in a script: The client tool sends the batch, so the boundary is visible in the source - ⚠️
SEToptions prefix every script:SET NOCOUNT ON,ANSI_NULLS ON, andQUOTED_IDENTIFIER ONare usually written at the top, so the boilerplate grows - ⚠️
@and@@distinguish kinds of variable by convention: A local variable, a parameter, and a global function share the sigil, so a reader distinguishes them by spelling - ⚠️ Identifier quoting depends on a setting:
[name]and"name"both quote an identifier, and the second form needsQUOTED_IDENTIFIER ON
1.2.7 Testing
- ✅
tSQLtis the established framework: An open-source unit-test framework runs test classes with mocked tables and assertions - ✅
TRY ... CATCHmakes a unit test expressible in the language: A test procedure asserts andTHROWs on a mismatch - ✅ A transaction wraps a test and rolls it back: The database is unchanged after the test
- ✅ Error metadata supports an assertion on a failure path:
ERROR_NUMBERandERROR_MESSAGEinsideCATCHlet a test check the rejection - ⚠️ The base product ships no test framework:
tSQLtis a third-party install, so a suite depends on deployment and permissions - ⚠️ A server-side suite needs a server: The harness needs a SQL Server instance, so the tests couple to infrastructure
1.2.8 Security
- ✅
sp_executesqlwith parameters removes the injection surface:EXEC sp_executesql N'SELECT ... WHERE x = @p', N'@p int', @p = 1passes the value out of band - ✅
GRANT,DENY, andREVOKEwith roles express least privilege: Access is declared per object, per schema, and per role - ✅ Row-level security limits what a statement can see: A
SECURITY POLICYwith a predicate function filters rows by the caller’s identity - ✅
EXECUTE ASmakes the effective identity explicit: A procedure can run as its owner or as a named user - ❌ Known vulnerability/injection (Q3): A statement built with
EXEC (@sql)from concatenated input lets the input become code - ⚠️ Dynamic SQL and owner-rights execution re-open the risk:
EXECon a built string,sp_OA*procedures, andxp_cmdshellwhere enabled defeat parameterisation
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
- ⚠️ One vendor decides the language’s direction.
- ⚠️ The specification is not public.
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?
- ❌ Sparse training data: models write it unreliably [5].
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 | — | 0 | |
T5 |
no generics | — | 0 | |
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 (−20) | |||
| 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 | ✅ | −4 | |
Er4 |
no composable result type | — | 0 | |
| Error subtotal | −4 | |||
| Concurrency | K1 |
GIL/no parallel | — | 0 |
K2 |
shared mutable state default | ✅ | −8 | |
K3 |
manual sync no race detection | — | 0 | |
K4 |
no structured concurrency | — | 0 | |
K5 |
no primitives | — | 0 | |
| Concurrency subtotal | −8 | |||
| Platform | P1 |
cannot function outside one vendor/domain | ✅ | −15 |
P2 |
single-vendor but other uses | — | 0 | |
P3 |
legacy/discontinued platform | — | 0 | |
| Platform subtotal | −15 |
Core Dimensions score: 58/100 — total cost = −42 (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 | — | 0 | |
Ec3 |
small/niche/discontinued | — | 0 | |
Ec4 |
proprietary | ✅ | −8 | |
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 | — | 0 | |
| Evolution subtotal | 0 | |||
| Domain | D1 |
domain/vendor lock-in | ✅ | −15 |
D2 |
poor domain fit | — | 0 | |
D3 |
framework forces architecture | — | 0 | |
| Domain subtotal | −15 | |||
| 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: 69/100 — total cost = −31 (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 | ✅ | −8 |
Y12 |
closed specification | ✅ | −8 | |
| Governance subtotal | −15 (−16) |
Readiness Dimensions score: 61/100 — total cost = −39 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).
Verdict: Transact-SQL’s coupling follows its host engine. Stored procedures, views, and table-valued parameters give real modularity, TRY ... CATCH gives a structured failure path, and the optimiser, Query Store, columnstore, and in-memory OLTP carry performance work. Main strengths: a mature first-party toolchain, a regular release cadence, and standard client interfaces. Main weaknesses: the language only runs on SQL Server or SAP ASE, the useful surface is engine-specific, NULL starts in every declared variable, an unhandled CATCH continues the batch, cursor code paths are slow, and a string-built statement is a standing injection hazard.
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