Was Embedding SQL in Programs a Mistake? Rethinking an Industry Norm

Was Embedding SQL in Programs a Mistake? Rethinking an Industry Norm
Photo by Christopher Burns / Unsplash

In the early days of software engineering, SQL wasn't born to be embedded in code. It was designed as a console-based, declarative language, meant for business analysts and data operators to pull reports from a relational database. Simple, focused, and readable—it was never meant to live inside the guts of C or Java.

Yet here we are, decades later, with millions of lines of application code tightly interwoven with raw SQL queries. So, the question arises: Was embedding SQL into programs one of the gravest mistakes of our industry?


The Original Intent of SQL

SQL (Structured Query Language) originated at IBM in the 1970s as part of the System R project. Its creators designed it to express what data is needed, not how to retrieve it. This declarative approach made it ideal for interactive querying, where business users could type:

SELECT name FROM employees WHERE department = 'Engineering';

…and get results—no loops, no variables, no procedural code. The magic was in its simplicity.

At its core, SQL was never meant to be embedded inside general-purpose programming languages. It was a report language, not a control flow tool.


So Why Did We Embed It?

As software systems evolved and grew in complexity, developers needed ways to connect application logic to persistent storage. And SQL was the default language for that storage. Instead of inventing a new safe bridge, developers started doing this:

$db->query("SELECT * FROM users WHERE id = $id");

What followed was a string-based, dynamic, fragile, and often unsafe way of interacting with databases. SQL became string soup, injected and manipulated at runtime, lacking compile-time checks, and vulnerable to attacks.


What Went Wrong?

  1. Paradigm Mismatch
    SQL is declarative. Your application is imperative. Mixing the two leads to conceptual friction, making the code harder to reason about.
  2. String-Based Hell
    Most SQL-in-code is built via string concatenation or interpolation. This leads to SQL injection vulnerabilities, poor readability, and runtime errors that compilers can’t catch.
  3. Tight Coupling
    When SQL is directly embedded, it binds your application logic tightly to the schema. Schema changes ripple through your codebase, and there's no abstraction layer to soften the blow.
  4. Difficult Testing and Refactoring
    Embedded SQL makes it harder to write unit tests, mock databases, or refactor data access without side effects.

But Did We Have a Choice?

To be fair, the ecosystem evolved. Libraries and frameworks tried to fix the pain:

  • ORMs (Object-Relational Mappers) like Hibernate, Sequelize, and Laravel Eloquent let developers express data interactions in code without raw SQL.
  • Query builders (like Knex.js, DrizzleORM, or Doctrine QueryBuilder) provided safer, composable alternatives.
  • LINQ in .NET introduced compile-time safe queries that feel natural inside C#.
  • Functional languages like Haskell and Scala introduced type-safe database access layers with strong guarantees.

These efforts validated the pain of embedded SQL. If SQL-in-code was perfect, none of these tools would exist.


So Was It Really a Mistake?

It depends on how you define "mistake":

  • If you believe in separation of concerns, safe abstractions, and composability, then yes—embedding SQL directly was a short-term hack that created long-term maintenance burdens.
  • But if you're driven by pragmatism, the reality is: SQL worked, and still works. It's fast, expressive, and battle-tested.

Sometimes we make trade-offs. And SQL-in-code was a trade-off born of necessity, not ignorance.


Modern Alternatives to Embedded SQL

If you're building new systems today, you don’t have to repeat the sins of the past. Some alternatives include:

  • DrizzleORM (TypeScript): type-safe, structured, SQL-like.
  • JOOQ (Java): fluent, type-safe SQL DSL.
  • EdgeDB: a post-relational database designed with modern APIs in mind.
  • GraphQL + Prisma: decouples your app from the storage layer with powerful querying.

Or you can go all in with event sourcing, document stores, or Datalog-style databases where SQL doesn’t exist at all.


Finally

The truth is: SQL is not the villain. The problem lies in how we abused its simplicity, embedding it recklessly into imperative code. Like duct-taping a steering wheel to a horse—it works, but that wasn’t the design.

As our industry matures, we must revisit old assumptions. Embedding SQL may have been necessary once, but we now have the tools and patterns to do better.

Let’s use them.

Support Us