✨ Strapi MCP is now Generally Available - let your agents manage your Strapi content ✨

Ecosystem●14 min read

GraphQL vs SQL: How Specialized Query Languages Compare to Standard SQL

●September 24, 2026
GraphQL vs SQL: How Specialized Query Languages Compare to Standard SQL

You pick PostgreSQL for storage, so your persistence layer speaks SQL. You add a graph database for recommendations, and someone on the team has to learn Cypher. The frontend team asks for GraphQL, and now your stack runs three query languages, only one of which you chose on purpose. GraphQL vs SQL is the comparison that comes up first, but SQL's System R roots go back to the 1970s, and GraphQL, Cypher, MongoDB's aggregation pipeline, PromQL, and Elasticsearch's ES|QL (Elasticsearch Query Language) each handle workloads SQL was never designed around.

The two get compared as if they compete for the same job. They don't. This article covers what each language does at its own layer, where SQL stops fitting the workload, what the other languages buy you, and how to combine them. It closes with how the open-source headless CMS Strapi 5 runs GraphQL and REST over a SQL database, so you can see the layered pattern in a real codebase. It's written for developers and architects who already know SQL and are weighing a second query language.

In brief:

  • SQL is a standardized, portable language for relational data. GraphQL is a client-driven API query language. They operate at different layers of the stack.
  • The GraphQL vs SQL question is about where each sits in your architecture. One does not replace the other.
  • Specialized query languages trade portability for domain-specific power: graph traversal, document filtering, time-series aggregation, relevance ranking.
  • Your choice affects hiring, vendor lock-in, tooling, and long-term maintainability.

Those distinctions make more sense once you separate the storage and API layers.

What SQL Actually Gives You (And What It Doesn't)

SQL predates most of the stack you run it on. ANSI standardized SQL in 1986, and ISO adopted it in 1987. The current SQL edition, ISO/IEC 9075:2023, arrived in June 2023. SQL:2023 added a JSON data type, while the SQL/PGQ specification added Part 16, which lets you query tables as if they were a property graph.

SQL's Strengths as a Standard

Portability is the headline benefit, with an asterisk. Since SQL:1999, SQL Core conformance has required every conforming engine to supply a Core feature set, while the rest is optional and vendors diverge. SQL:2008 standardized FETCH FIRST n ROWS ONLY, but MySQL and SQLite implement only LIMIT, PostgreSQL supports both, and SQL Server uses TOP or, from SQL Server 2012, FETCH. MySQL's own documentation warns that if you use extensions like GROUP_CONCAT() and REGEXP, your code is "most likely not portable." Core SQL travels well. Dialect features don't, and most teams find out which is which during a migration.

The declarative model is the other durable advantage. You describe the result, and a cost-based optimizer picks the access path. Mature SQL tooling has grown around it for nearly five decades: PostgreSQL 18 ships EXPLAIN ANALYZE, pg_stat_statements, and auto_explain built in, as the PostgreSQL EXPLAIN docs describe.

SQL is also the more widely used skill. The 2025 Developer Survey, with 49,009 respondents, found 59% of developers use SQL, and PostgreSQL alone reached 55.6%, up from 48.7% in 2024.

Where SQL Starts Breaking Down

Over-fetching is the first problem you hit once a REST API sits in front of SQL. REST endpoints return fixed shapes; AWS's GraphQL and REST comparison puts it plainly: "you would get all of this data even if you only needed a phone number." Under-fetching is the mirror image, one request per related resource. GitHub made a similar point in its GraphQL API rationale, noting that hypermedia navigation required clients to repeatedly communicate with the server and that GraphQL could let one call replace multiple REST requests.

PostgreSQL jsonb softens schema rigidity for semi-structured data, with GIN indexing and a decomposed binary format.

Nested document data needs joins to reconstruct. Strapi's REST API docs reflect this: responses "only include top-level fields" unless you ask for relations with populate, and the filter documentation warns that deep filters "may cause performance issues."

Graph traversal is where SQL gets awkward. Recursive CTE traversal works for small graphs, but each recursion step joins the working set back to the base table. A PostgreSQL mailing-list discussion described the SEARCH and CYCLE clauses added in version 14 as "just convenience syntax for what you can already do manually." In Cypher, the same traversal of unknown depth uses MATCH variable-length paths, such as MATCH (a)-[*1..10]->(b).

Real-time API subscription support is not part of standard SQL. PostgreSQL provides LISTEN/NOTIFY, but its LISTEN/NOTIFY limits require payloads shorter than 8,000 bytes, deliver only on commit, and cause the commit to fail when the queue fills.

What GraphQL Actually Is (And Isn't)

GraphQL is not a database language. The GraphQL FAQ has a dedicated entry, "Is GraphQL a database language like SQL?", and the answer opens with "No, but this is a common misconception." The September 2025 specification, the current release, states that GraphQL "does not mandate a particular programming language or storage system." Meta put it more bluntly in its 2015 GraphQL announcement: "Protocol, not storage."

GraphQL as an API Layer, Not a Database Layer

Every field in a GraphQL schema is backed by a resolver function you write. Executing a field calls its resolver, which produces the next value, as the GraphQL resolver documentation explains.

That resolver can run a SQL query, call a REST endpoint, hit another GraphQL service, or return static JSON. Apollo documents these resolver data sources. Lee Byron, GraphQL co-creator, told InfoQ: "GraphQL rarely interfaces directly to a data storage engine like MySQL or Redis, instead GraphQL interfaces with your existing application code."

The GraphQL schema system is the contract. Object types have named fields, fields can return other object types to arbitrary depth, and the type system is introspectable through __schema and __type. The GraphQL Foundation, formed in 2019 under The Linux Foundation, is the neutral home for the project.

What Developers Get From GraphQL

GraphQL selection sets let clients specify requests down to scalar fields, so the response shape matches the request. A single GraphQL endpoint replaces REST's route sprawl, and schema introspection documents the schema. Subscriptions sit in the same spec and close the push gap LISTEN/NOTIFY leaves open.

GraphQL vs SQL: A Direct Comparison

The two differ on almost every axis a schema designer cares about, starting with what a schema even is.

DimensionSQL (Relational)GraphQL (Type System)
Schema unitTable with typed columnsObject type with named fields
RelationshipsForeign keys, enforced by the engine; JOIN at query timeNested fields; no referential integrity
NestingFlat rows, reassembled by the clientFirst-class selection sets of arbitrary depth
PolymorphismNo standard support; single table inheritance, class table inheritance, polymorphic associationsNative interface and union
TypingStorage types (INT, TEXT, TIMESTAMPTZ) at the engineAPI types (Int, Float, String, Boolean, ID, custom scalars)
StandardizationISO/IEC 9075 with heavy dialect divergenceSingle spec, versioned editions
Stack layerStorage and transactionsAPI and middleware
PaginationLIMIT/OFFSET or keyset, no standard conceptRelay Connections specification (edges, node, cursors, pageInfo)

Data Model and Schema Design

SQL relational modeling uses tables whose foreign keys the engine enforces at write time, then reassembles the data at read time with joins. GraphQL response shape comes from types with nested fields, and the spec requires every operation to select down to scalar values so the response has one unambiguous shape. Nothing in GraphQL enforces referential integrity. That stays with your storage.

Polymorphism is the sharpest divergence. Martin Fowler's patterns catalog notes that "relational databases don't support inheritance," so you pick a workaround: single table inheritance, class table inheritance, or polymorphic associations. PostgreSQL's INHERITS exists, but its inheritance documentation flags that indexes, unique constraints, and foreign keys "only apply to single tables, not to their inheritance children." GraphQL polymorphism includes interface and union, queried with inline fragments and __typename.

Query Flexibility and Performance

Resolver behavior. SQL gives you joins and aggregations the planner optimizes as one unit. GraphQL gives the client control of nesting, executed one resolver at a time, and that produces the N+1 problem: an N+1 query example might fetch 100 posts in one query, then perform 100 author lookups, for 101 round trips. Independent field resolvers run separately, so nothing batches them for you.

Batching. DataLoader batching is the standard fix. It batches .load(key) calls made in one event loop tick into a single batchLoadFn(keys) call and caches for the life of a request, turning 11 queries (SELECT * FROM users plus ten SELECT * FROM posts WHERE userId = ?) into two. Create one instance per request, or you leak data between users.

Compilation. Compilation engines go further. Hasura and PostGraphile use GraphQL-to-SQL compilation to turn any GraphQL query, however nested, into one SQL statement and hand it to PostgreSQL's planner. Hasura's engineers point out that DataLoader cannot use single-source features like joins. A GraphQL performance study from the University of Washington measured 58–59% lower average round-trip time for Apollo GraphQL versus REST on a complex multi-join endpoint.

Cost controls. Because clients control query shape, you need cost limits: GitHub query limits allow 5,000 points per hour, and Shopify refills 50 points per second to a 1,000-point cap. Strapi's GraphQL plugin ships depthLimit: 10 and amountLimit: 100 by default.

Tooling, Ecosystem, and Developer Experience

SQL's toolchain is deep and stable. Prisma typed clients come from a schema file, TypeORM and Knex cover the ORM and query-builder ends, and database profiling tools ship inside the database engine itself.

GraphQL's client ecosystem is younger. Codegen is where the type system pays off: Apollo Client 4.0 enforces required query variables at TypeScript compile time, and GraphQL Code Generator's Client Preset v6 generates schema types only where they're used.

On the playground side, GraphiQL 5 added Monaco Editor support, so the IDE autocompletes against the introspected schema.

Beyond GraphQL: Other Specialized Query Languages Worth Knowing

Each of these languages is built around a data shape SQL models poorly.

Cypher for Graph Databases

Cypher draws relationships as ASCII art. Mutual friends of Jim:

MATCH (a:Person {name:'Jim'})-[:KNOWS]->(b)-[:KNOWS]->(c),
      (a)-[:KNOWS]->(c)
RETURN b, c

Equivalent SQL graph queries need self-joins or CTEs and get worse with each hop. An Applied Sciences study comparing Neo4j 3.4.6 and MySQL 5.7 found relational engines won on group-by, sort, and aggregation workloads, while graph engines won on "multiple table joins, pattern matching, path identification and their combinations." Index-free adjacency explains the split: traversal cost depends on the connectedness of the nodes you touch, not on total data size.

Cypher is also becoming less proprietary. ISO published the GQL standard, ISO/IEC 39075:2024, on April 12, 2024, and Neo4j's Cypher 25 aligns with it. In parallel, the SQL/PGQ graph operator puts a GRAPH_TABLE operator with a MATCH clause inside standard SQL.

MongoDB Query Language (MQL) for Document Stores

Pipeline mapping. The MQL-SQL comparison maps MongoDB's aggregation pipeline onto SQL concepts: $match is WHERE, $group is GROUP BY, $project is SELECT, and $lookup is JOIN.

Stage ordering. The difference is that you order the stages yourself, and order decides performance. The official $lookup performance guidance warns that "Excessive use of $lookup may slow down query performance" and recommends an embedded model. MongoDB's own engineers say $lookup "is useful at the end of an aggregation pipeline, not before the aggregation." One documented production query dropped from 68 seconds to 1.2 seconds on 100,000 records after moving $group ahead of $lookup.

Single-document writes are atomic without transactions, but multi-document transactions, supported since MongoDB 4.0, "incur a greater performance cost over single document writes."

PromQL (Prometheus Query Language) exists because Prometheus data has a shape SQL doesn't model. The PromQL rate function, used as rate(http_requests_total{job="api-server"}[5m]), computes a per-second rate over a range vector and corrects automatically for counter resets when a target restarts. SQL has no range vector type.

ES|QL relevance scoring pipes stages with | and exposes a query relevance score through METADATA _score (which is not BM25 in the documented contexts). Standard SQL relevance can filter text but has no native relevance score to rank by. Lucene nested-object limits prevent the original Elasticsearch language from searching nested objects. Kibana Query Language (KQL), by contrast, only filters data. It doesn't aggregate or sort.

InfluxDB 3 SQL moved the other way: InfluxDB 3 dropped Flux and made SQL the primary language. Its SQL gap-filling functions, including date_bin_gapfill() and interpolate(), handle the gap-filling that InfluxQL's GROUP BY time(5m) fill(linear) supported and standard SQL still doesn't.

When to Use GraphQL, SQL, or Both

The division of labour is the easier decision.

SQL as Your Source of Truth, GraphQL as Your API

Netflix, GitHub, and Shopify run the same hybrid: SQL, or REST and gRPC services over SQL, internally, with one GraphQL graph for external clients. Thoughtworks' Technology Radar, Vol. 22 from 2020, recommended server-side GraphQL aggregation only, with microservices continuing to expose REST while resolvers stitch them.

Resolver architecture guidance from graphql.org puts authorization and validation in a business logic layer that resolvers delegate to. Under that layer sits an ORM or query builder with batching. If your GraphQL server fronts a single PostgreSQL instance, a compilation engine is the stronger choice. Thoughtworks rated the Hasura GraphQL Engine "Trial" in April 2025 for aggregation while staying "cautious about its powerful federated query and unified schema management."

Choosing a Specialized Language for Specialized Workloads

Add a second language when the workload, not the demo, demands it:

  • Relationship traversal workloads are the product. Recursive CTE guidance covers small-to-medium graphs.
  • Ingest rate and cardinality exceed a tuned PostgreSQL.
  • Relevance ranking, rather than filtering, drives search.

SQL aggregation workloads over millions of rows stay in SQL.

These cases can justify another language, but they do not remove its operational cost.

Weigh those costs against the benefit. Polyglot persistence trade-offs include "increased administration overhead and more complex configuration (in particular in terms of security)," according to a CEUR-WS survey of polyglot architectures. PostgreSQL extensions (PostGIS, pgvector, TimescaleDB, and pg_trgm with full-text search) cover many of these needs from one engine. Thoughtworks' oldest GraphQL caution still holds: avoid using it as a server-to-server protocol, or you'll write boilerplate for every new model.

How Strapi Handles GraphQL and SQL Together

Strapi 5 is a working example of the layered pattern. Content lives in SQL, and the API configuration is REST by default with GraphQL as an official plugin.

Querying Content With the GraphQL Plugin and REST API

npm install @strapi/plugin-graphql

Shadow CRUD generates types, queries, mutations, and resolvers from your Content-Types, and the sandbox is available at /graphql.

Fetching one restaurant in French through the GraphQL API:

query Restaurant {
  restaurant(documentId: "a1b2c3d4e5d6f7g8h9i0jkl", locale: "fr") {
    documentId
    name
    description
    locale
  }
}

The same French document over REST:

GET /api/restaurants/a1b2c3d4e5d6f7g8h9i0jkl?locale=fr

Both hit the same backend, and both responses are flat in Strapi 5 (data.title, not data.attributes.title). GraphQL never exposes the numeric id, so the documentId identifier is what you query with, while REST returns id alongside it. For lists, restaurants_connection returns nodes plus a pageInfo block with page, pageSize, pageCount, and total.

SQL Under the Hood With Knex.js and the Document Service

The Content API architecture is a stack of four layers:

Document Service API (strapi.documents)
  └→ Query Engine API (strapi.db.query)
       └→ Knex.js
            └→ SQL driver (pg / mysql2 / better-sqlite3)

Strapi 5 database support includes PostgreSQL 14+ (17.0 recommended), MySQL 8.0+, MariaDB 10.3+, and SQLite 3, and the docs state it "does not support MongoDB (or any NoSQL databases)."

The Document Service API is the layer you write against. It's "aware of advanced Strapi 5 features like Draft and Publish, Strapi Internationalization, Content History," while the Query Engine beneath it "gives unrestricted internal access to the database layer" without knowing about them. Selecting specific fields looks like this:

// src/api/restaurant/controllers/restaurant.js
const documents = await strapi.documents("api::restaurant.restaurant").findMany({
  fields: ["name", "description"],
});
// Returns: [{ documentId, name, description }, ...]

The Document Service migration guide explains how to adopt this Strapi 5 API. When you need raw SQL, strapi.db.connection exposes the Knex instance, and database transactions hand you a trx object. You get SQL's constraints and transactions without writing SQL, and the GraphQL resolvers Shadow CRUD generates sit on top of this chain.

Making the Right Query Language Decision for Your Stack

GraphQL and SQL layers solve different problems. SQL constraints own storage, integrity, and transactions. GraphQL resolver execution owns the shape of what clients receive, and a resolver or compilation engine connects them. Cypher, MQL, PromQL, and ES|QL earn a place when relationship traversal, document nesting, counter-reset math, or relevance ranking is the workload.

These languages are converging rather than displacing each other. SQL:2023 features absorbed JSON and property graphs, InfluxDB 3 SQL replaced Flux with SQL plus time-series functions, the ISO GQL standard is now tracked by Cypher, and Thoughtworks is watching GraphQL for LLMs as a data access layer.

If you'd like to see the layered pattern without wiring it yourself, Strapi Cloud provisions a PostgreSQL database by default, and the quick-start guide gets a Strapi 5 project running locally with the GraphQL plugin a single install away.

Theodore Kelechukwu OnyejiakuDevRel and Community | Software Developer | Technical Writer

Theodore is a Technical Writer and a full-stack software developer. He loves writing technical articles, building solutions, and sharing his expertise.

Related Posts

Top 5 ORMs for Developers in 2025
Ecosystem·16 min read

Top 8 ORMs for Developers: TypeScript, Java, .NET, and Python Options Compared

Compare Prisma, Drizzle, TypeORM, MikroORM, Hibernate, EF Core & SQLAlchemy. Find the right ORM for your stack with honest production tradeoffs.

·December 30, 2024
Top Chart Libraries
Ecosystem·16 min read

Best Chart Libraries for Developers in 2026

Compare Chart.js, D3, ECharts, ApexCharts, Plotly, Highcharts, Nivo & Recharts on bundle size, licensing, and accessibility.

·March 31, 2025
Top 10 React Libraries to Use in 2025
Ecosystem·16 min read

Top React Libraries for Developers: Building a Modern Stack

Compare the best React libraries for UI, state, routing, forms, animation, and auth—with React 19 compatibility, bundle sizes, and Strapi 5 pairing notes.

·May 29, 2025