DynamoDB and PostgreSQL solve different problems. DynamoDB is a serverless
NoSQL database optimized for scale and predictable latency; PostgreSQL is a
mature relational database optimized for flexible querying and data integrity.
This page compares them factually so you can choose by workload, not by hype.
What is the difference between DynamoDB and PostgreSQL?
DynamoDB is a fully managed, serverless key-value and document NoSQL
database that delivers single-digit-millisecond performance at any scale but
has no JOIN operator, so you model data around known access patterns.
PostgreSQL is an open-source object-relational SQL database with full
joins, a rich type system and ACID transactions, designed for ad-hoc relational
queries. Choose by access pattern, scale and query flexibility.
DynamoDB vs PostgreSQL at a glance
| Characteristic | DynamoDB | PostgreSQL |
|---|---|---|
| Data model | NoSQL — supports both key-value and document models; flexible, per-item schema | Object-relational — tables, rows and columns with a defined, enforced schema |
| Query language | Purpose-built API (Query/Scan/GetItem) plus PartiQL, a SQL-compatible language for SELECT/INSERT/UPDATE/DELETE |
Full SQL; PostgreSQL 18 conforms to at least 170 of the 177 mandatory SQL:2023 Core features |
| Joins & relations | No JOIN operator; AWS recommends denormalizing your data model | Full relational joins, foreign keys and constraints across tables |
| Indexes | Global and local secondary indexes let you query on an alternate key | B-tree, hash, GiST, SP-GiST, GIN, BRIN, plus partial, expression and covering indexes |
| Consistency | Eventually consistent by default; strongly consistent reads optional; native ACID transactions | ACID-compliant since 2001; strong consistency by default via MVCC |
| Scaling | Horizontal — partitions data automatically; serverless on-demand mode scales up and down (to zero) | Vertical scaling plus read replicas for read scale-out; write scale-out needs partitioning or external tooling |
| Transactions | Server-side ACID transactions across one or more items and tables, subject to per-request limits | Full multi-statement transactions, savepoints (nested transactions) and MVCC concurrency |
| Hosting / managed | Serverless, AWS-only; on-demand or provisioned capacity billing | Open source, self-host anywhere; managed options include Amazon RDS for PostgreSQL and Aurora PostgreSQL-Compatible Edition |
| Best-fit workloads | High-scale operational apps with known access patterns (carts, sessions, leaderboards, event data) | Relational and analytical workloads needing joins, ad-hoc queries and strong data integrity |
When DynamoDB is the better choice
Pick DynamoDB when your access patterns are known and your priority is
predictable latency at scale rather than ad-hoc querying:
- You need consistent single-digit-millisecond reads and writes whether you have hundreds or hundreds of millions of users.
- You want a serverless database with no servers to patch or capacity to plan, and on-demand billing that scales to zero when idle.
- Your workload is key- or item-oriented (user profiles, sessions, shopping carts, leaderboards, event streams) and fits DynamoDB's partition/sort-key model.
- You want multi-Region, multi-active replication and a high availability SLA without building your own replication.
The trade-off: no joins, and you must design your keys around the queries you
will run. Rework of access patterns after the fact is harder than in SQL.
When PostgreSQL is the better choice
Pick PostgreSQL when query flexibility and relational integrity matter more
than horizontal scale:
- You need joins, aggregations and ad-hoc queries across normalized tables, and your access patterns will keep evolving.
- You want full SQL, a rich type system (JSON/JSONB, arrays, geospatial via PostGIS), and enforced constraints for data integrity.
- Your dataset and traffic fit a vertically scaled primary with read replicas, or you are comfortable adding partitioning/sharding tooling for write scale-out.
- You want to self-host, avoid single-vendor lock-in, or run a managed Postgres on the cloud of your choice.
The trade-off: you own more of the scaling and operational story than with a
serverless NoSQL database, especially for very high write throughput.
What a PostgreSQL join becomes in DynamoDB
The tables above say DynamoDB has no joins. What they leave out is that you also
give up the planner, and that turns out to be the more expensive half. Take a
weekly support report over three normalized tables.
SELECT t.name AS team, count(*) AS closed
FROM tickets tk
JOIN agents a ON a.id = tk.agent_id
JOIN teams t ON t.id = a.team_id
WHERE tk.closed_at >= now() - interval '7 days'
GROUP BY t.name
Nothing in that statement says how to run it. PostgreSQL's planner picks the scan
and join methods from table statistics and re-picks them when the data shifts or
when you add an index. The text stays the same and the plan improves underneath
it.
DynamoDB has no planner. Query takes an IndexName and you supply it, so the
request is the plan. You also cannot revise the base plan later. UpdateTable
accepts GlobalSecondaryIndexUpdates but has no parameter for the base table's
KeySchema, so the partition and sort key you pick at CreateTable are the ones
you keep.
That means the report needs a key designed for it. Give tickets a GSI
partitioned by teamId and sorted by closedAt, and one team's week is a single
Query:
{
"TableName": "tickets",
"IndexName": "team-closed-index",
"KeyConditionExpression": "teamId = :team AND closedAt BETWEEN :from AND :to",
"ExpressionAttributeValues": {
":team": {"S": "TEAM#billing"},
":from": {"S": "2026-07-21T00:00:00Z"},
":to": {"S": "2026-07-28T00:00:00Z"}
}
}
Three pieces of the statement moved into your application. A key condition has to
perform an equality test on the partition key, so "every team" is one Query per
team rather than one request. The count(*) per team becomes a client-side tally
over paginated results.
The third is the one that costs real time. teamId is not on a ticket in the
relational schema at all; it comes from agents. Copying it onto the ticket moves
the join to write time, onto the code path that creates the ticket.
That move has a tail worth knowing before you commit to it. A global
secondary index only holds items that carry both of its key attributes, so every
ticket written before you started stamping teamId is missing from the index and
invisible to the report until you backfill it.
DynoTable's SQL Workbench does that planning for you instead of making you write
it out. The same report is one statement:
SELECT t.name AS team, COUNT(*) AS closed
FROM tickets tk
JOIN teams t ON tk.teamId = t.teamId
GROUP BY t.name
It compiles to DynamoDB's own Query and Scan operations, planned against your
real keys and indexes, and does the join and the grouping on the client. It
supports INNER JOIN, LEFT JOIN, GROUP BY, COUNT and SUM. PartiQL has
none of those.
It does not give DynamoDB a server-side join, because nothing can. It removes the
fan-out and rollup code you would otherwise own.
Working with DynamoDB
Once you have chosen DynamoDB, DynoTable is a desktop client
built specifically for it. It gives you a native GUI for browsing and editing
items, a SQL Workbench that expresses relational-shaped queries — joins,
GROUP BY, aggregates — within DynamoDB's access-pattern rules by compiling
them down to DynamoDB's own Query/Scan operations, and an AI agent that
runs on your own AWS Bedrock credentials so your schema and data stay in your
account. If you are coming from Postgres and miss writing SQL, the
SQL for DynamoDB guide and
PartiQL vs SQL guide explain what maps across and what
does not.
For quick one-off queries, the free
DynamoDB Expression Builder generates
correct key conditions, filter expressions and update expressions with the
right reserved-word and attribute-name handling — no install required.
FAQ
Can DynamoDB replace PostgreSQL?
Not in general — they target different workloads. DynamoDB can replace
PostgreSQL for high-scale operational applications with well-defined access
patterns, where its serverless scaling and predictable latency are the priority.
It is a poor fit where you rely on joins, ad-hoc relational queries or a
constantly evolving query surface, which are PostgreSQL's strengths. Many teams
run both: PostgreSQL for relational and analytical work, DynamoDB for the
high-throughput key-access paths.
Does DynamoDB support SQL or joins?
DynamoDB supports PartiQL, a SQL-compatible query language for SELECT, INSERT,
UPDATE and DELETE against your tables, but it does not support the JOIN
operator — AWS recommends denormalizing your data instead. PostgreSQL supports
full SQL including joins across tables. See the
DynamoDB JOIN guide for how to model relationships
without a native join.
Is DynamoDB cheaper than PostgreSQL?
It depends on the workload, so treat cost as something to model rather than
assume. DynamoDB bills per read/write request and stored data (on-demand or
provisioned capacity) and scales to zero when idle, which can be cheaper for
spiky or low-baseline traffic. A managed PostgreSQL such as Amazon RDS or Aurora
generally bills for provisioned compute and storage that runs continuously,
which can be more cost-effective for steady, query-heavy workloads. Estimate
both against your real access patterns before deciding.
Related
- Learn: SQL for DynamoDB · PartiQL vs SQL · Joins in DynamoDB
- FAQ: Does DynamoDB support SQL?
- Build queries fast with the free DynamoDB Expression Builder.
- Download DynoTable for macOS, Windows or Linux.
References
- What is Amazon DynamoDB? — AWS DynamoDB Developer Guide
- DynamoDB read consistency
- PartiQL — a SQL-compatible query language for DynamoDB
- Amazon DynamoDB transactions
- About PostgreSQL
- PostgreSQL 18: SQL conformance (SQL:2023 Core features)
- PostgreSQL index types
- PostgreSQL concurrency control (MVCC)
- Amazon RDS for PostgreSQL
Last verified 2026-07-13 against the AWS DynamoDB Developer Guide and the
official PostgreSQL documentation. PostgreSQL is a trademark of the PostgreSQL
Community Association; referenced here for identification only.
Top comments (0)