
The SQL versus NoSQL question is usually framed as a fork in the road: pick the relational database and its rigid schema, or pick the flexible NoSQL store and give up transactional guarantees. That framing is dated. Modern stacks routinely run both, matching each store to the shape of the data it holds, so the real question is not which one wins but which one fits a given workload. This post breaks down what each family is, where they differ on schema, scaling, consistency, and query language, and when to reach for each, closing with a case the either/or framing misses: running graph queries over relational data without moving it anywhere.
A SQL database is a relational database: it stores data in tables of rows and columns, enforces a schema defined ahead of time, and is queried with SQL (Structured Query Language). Tables relate through keys, and the engine guarantees ACID transactions, so a set of changes either commits as a unit or not at all. The model traces to Edgar Codd’s 1970 relational proposal and is codified in the ISO/IEC 9075 SQL standard.
The defining strengths follow from that structure. A fixed schema means every row has the same columns and types, enforced on write, so bad data is rejected at the door. Foreign keys and joins resolve relationships at query time, so one normalized copy of each fact serves many queries. And ACID transactions (atomicity, consistency, isolation, durability) make relational databases the default wherever correctness under concurrency is non-negotiable, such as payments, inventory, and ledgers. The cost is rigidity: changing the data’s shape means changing the schema, and scaling writes past a single machine takes real effort. Good relational schema design is what makes the model pay off.
The widely used relational engines are PostgreSQL, MySQL, Microsoft SQL Server, and Oracle Database, with SQLite covering the embedded case. PostgreSQL has been the most-used database among developers for two years running in the 2024 Stack Overflow Developer Survey. All speak SQL, though each adds its own extensions on top of the standard.
NoSQL (“not only SQL”) is an umbrella for databases that do not use the relational table-and-join model. They emerged in the late 2000s for workloads relational systems struggled with: very high write throughput, horizontal scale across commodity machines, and data whose shape changes often. Most NoSQL stores are schema-flexible rather than fixed on write, and many trade strict ACID consistency for the looser BASE model (basically available, soft state, eventual consistency) in exchange for availability and partition tolerance.
That trade-off is not arbitrary. The CAP theorem, formalized by Gilbert and Lynch, states that a distributed system facing a network partition must choose between consistency and availability. NoSQL systems built to stay available across many nodes therefore relax consistency to eventual, while relational systems hold consistency and accept reduced availability during a partition. Which side a store falls on matters more than the SQL/NoSQL label itself.
NoSQL is not one thing. It covers four broad families, each optimized for a different access pattern:
So “NoSQL” tells you little on its own: a key-value cache and a graph database share almost nothing beyond not being relational. Choosing well means choosing a type, following how the data is accessed.
The families diverge on a handful of axes that decide most design questions.

Read the table by its last row. The other dimensions are what every comparison lists, but the failure modes are what an engineer lives with. Relational databases fail loudly and predictably: a migration locks a table, a write-heavy workload saturates the primary. NoSQL stores fail quietly: eventual consistency means a read can return stale data with no error, and because the engine does not enforce constraints, correctness becomes the application’s job. Neither is worse in the abstract; the question is which one your team is equipped to reason about.
The modeling difference is tangible in code. Fetching a user’s orders in a relational database joins two tables:
SELECT o.id, o.total, o.created_at
FROM orders o
JOIN users u ON u.id = o.user_id
WHERE u.email = 'ada@example.com'
ORDER BY o.created_at DESC;In a document store, the orders are often embedded in the user document, so the same read is a single lookup with no join:
db.users.findOne(
{ email: "ada@example.com" },
{ orders: 1 }
)The document model wins when you always want the whole object together; the relational model wins the moment a second query needs the same data sliced a different way, because the normalized table serves every angle without duplication.
No database is faster in general, only faster at a particular access pattern. The honest answer depends on the query, the data volume, and how the store is scaled.
Scaling model is the biggest divide. Relational databases scale vertically by default: a bigger machine, plus read replicas. Scaling writes past one primary means sharding, which adds real operational weight because joins and transactions across shards are hard. Most NoSQL stores are built to scale horizontally, sharding across nodes so write throughput grows as you add machines. For a workload too large or write-heavy for one machine, that difference, not raw per-query speed, is usually decisive.
Joins versus denormalization. A relational database answers a multi-table question with joins the planner optimizes over indexes. A NoSQL store typically denormalizes instead, storing related data together so common reads hit one document or key, buying fast reads with more expensive, error-prone writes when the duplicated facts change.
Indexing cuts across both. The largest performance lever in either family is whether a query is served by an index or forces a full scan. More “SQL is slow” and “NoSQL is slow” conclusions trace to a missing index than to the data model. Performance is a property of fit between workload and store, not of the label.
The decision comes down to the shape of the data and the guarantees the workload needs, not to which technology is newer.
The data is structured and its shape is stable, relationships are queried from many angles, and correctness under concurrency is required. Financial systems, order and inventory management, reporting over well-defined entities, and any application where a partial write is unacceptable are relational by default. Wanting joins, transactions, or ad hoc queries you did not anticipate is the model earning its place.
The workload pushes past what a single relational primary handles, or the data does not fit the table model well. Reach for a document store when records are self-contained and their shape varies; a key-value store for high-throughput lookups by key; a wide-column store for massive write volumes like telemetry; a graph database when the questions are about relationships and paths. The common thread is scale, velocity, or flexibility outweighing the need for a single enforced schema.
Most systems past a certain size do not choose once. They use polyglot persistence: a relational database as the system of record, a key-value store for caching, a document store for a catalog, each holding the data it serves best. The question shifts from “SQL or NoSQL” to “which store owns which data, and how do they stay consistent.” Graph is a common addition, and adding one no longer means standing up another database: when a relationship-heavy question spans data already in a relational store, it is worth asking when a graph database makes sense as a separate layer rather than a separate migration.
Graph is one of the four NoSQL types, and its value is easy to state: some questions are about connections, and connections are what graphs make cheap. “Which accounts are within four hops of this flagged account” or “what is the blast radius if this service fails” are traversals. In SQL they become self-joins or recursive CTEs that grow unwieldy as the hop count rises; in a graph they are short pattern matches:
MATCH path = (a:Account {id: $account_id})-[:TRANSFERRED_TO*1..4]->(b:Account)
WHERE b.flagged = true
RETURN b.id, length(path)Historically, getting this expressiveness meant adopting a dedicated graph database, and that meant ETL: a pipeline to copy data out of the relational store into the graph database and keep the two in sync. For data already maintained in Postgres, MySQL, Snowflake, or an Iceberg lakehouse, that is a second copy and a second system to operate, often enough reason to skip graph entirely. It is the same either/or tax, applied to graph.
PuppyGraph is a graph query engine that removes that step. You define a graph schema (a mapping of existing tables to nodes and edges) over the relational or lakehouse data you already have, and query it in openCypher or Gremlin directly, with no data movement and no separate database to maintain. The tables stay where they are; PuppyGraph is the compute layer that runs the traversal over them, and where that compute happens is worth checking in any zero-ETL graph tool. Some systems deliver the same shape as a translation layer, sometimes called a virtual graph: the graph query is rewritten into one large SQL statement and pushed down, so the traversal actually runs in the source’s relational planner and is bounded by what that planner does with generated SQL. PuppyGraph is a query engine, not a translator. It compiles the query into a plan of node and edge operators that execute in its own distributed engine, sending the sources only simple scans and filters. Traversal performance follows from that placement: the engine plans and optimizes for graph workloads, multi-hop expansion and pattern matching, instead of inheriting whatever a relational planner does with one large generated query. The gap compounds with depth, because each added hop is one more operator in a graph plan but one more self-join in the generated SQL.
It reads from SQL databases, warehouses, and open table formats; customers include Coinbase, eBay, and AMD, which builds a graph layer over Apache Iceberg spanning tickets, code, logs, and telemetry. That dissolves one instance of the choice: you keep the relational system of record and its ACID guarantees, and add graph querying as a layer over the same data. If you are weighing graph against other specialized models, our graph vs vector database comparison covers where each fits.

What is the main difference between SQL and NoSQL databases? SQL databases store data in tables with a fixed schema and enforce ACID transactions, making them strong for structured data and correctness. NoSQL databases use flexible non-tabular models (document, key-value, wide-column, or graph) and favor horizontal scale and schema flexibility over strict consistency. The core difference is structure and guarantees versus flexibility and scale.
Is SQL still relevant? Yes. Relational databases remain the default for transactional and analytical workloads, and PostgreSQL has topped the Stack Overflow Developer Survey’s most-used database ranking for two consecutive years. NoSQL has grown alongside SQL rather than replacing it, and most large systems run both.
What are the four types of SQL? This usually refers to the four sublanguages within SQL, grouped by what they do: DDL (Data Definition Language, for schema), DML (Data Manipulation Language, for reading and writing rows), DCL (Data Control Language, for permissions), and TCL (Transaction Control Language, for commit and rollback). They are categories of SQL commands, not separate databases.
Is MongoDB a SQL or NoSQL database? MongoDB is a NoSQL database, specifically a document store. It keeps data as flexible JSON-like (BSON) documents rather than fixed relational tables, and scales horizontally through sharding. It is often chosen when records are self-contained and their shape varies.
SQL and NoSQL are different tools for different data shapes, not competitors. Relational databases remain the default for structured, correctness-critical work; NoSQL trades some guarantees for flexibility and horizontal scale across its four families. The mature approach is polyglot: let each store own the data it serves best, and choose per workload rather than by allegiance.
Try the forever-free PuppyGraph Developer Edition and book a demo with the team to see how openCypher and Gremlin queries run over warehouse and lakehouse tables, with no graph-specific ETL, add graph traversals to the relational data you already have.
Get started with PuppyGraph!
Developer Edition
Enterprise Edition