Beginner9 min read

How to Delete Multiple Items in DynamoDB

There is no DELETE FROM table WHERE … in DynamoDB. Every delete targets one item by its full primary key: the API has no bulk-delete operation and no truncate, and 's DELETE removes exactly one item per statement.

So "delete multiple items" is always a two-step job: find the keys, then issue one delete per key, batched for throughput.

How do I delete multiple items in DynamoDB?

Collect the primary keys of the items you want gone (via Query or Scan), then delete them in batches: BatchWriteItem takes up to 25 DeleteRequests per call, TransactWriteItems deletes up to 100 items atomically, and PartiQL DELETE statements can be batched 25 at a time with BatchExecuteStatement.

  • Bulk, best-effort: BatchWriteItem, 25 deletes per call; you retry the leftovers.
  • All-or-nothing: TransactWriteItems, up to 100 actions that succeed or fail together, at twice the write cost.
  • SQL-ish syntax: PartiQL DELETE + BatchExecuteStatement, the same per-key rule in a familiar spelling.
  • Everything in the table: truncate strategies. Dropping and recreating the table is usually cheapest.
  • In a GUI: DynoTable deletes a selection or every item a query matches, staged for review or written directly.

Step 0: collect the keys

A delete needs the entire primary key. On a table that's the partition key and the sort key (AWS: "For each primary key, you must provide all of the key attributes"). You can't delete by an arbitrary attribute, so "delete every item where status = 'archived'" starts with a read:

  • If the condition lives on a key (or a GSI key), a targeted Query collects the keys cheaply.
  • Otherwise it's a Scan with a filter, which reads (and bills) the whole table, filter or not; see query vs scan.

Use a ProjectionExpression that returns only the key attributes, and follow LastEvaluatedKey to the end (pagination). Stop early and you delete only the first 1 MB page's worth.

Method 1: BatchWriteItem (up to 25 deletes per call)

BatchWriteItem bundles up to 25 put/delete requests or 16 MB per call (AWS API reference: "A single call to BatchWriteItem can transmit up to 16MB of data over the network, consisting of up to 25 item put or delete operations").

aws dynamodb batch-write-item --return-consumed-capacity TOTAL --request-items '{
  "Tickets": [
    {"DeleteRequest": {"Key": {"PK": {"S": "TICKET#1001"}, "SK": {"S": "META"}}}},
    {"DeleteRequest": {"Key": {"PK": {"S": "TICKET#1002"}, "SK": {"S": "META"}}}},
    {"DeleteRequest": {"Key": {"PK": {"S": "TICKET#1003"}, "SK": {"S": "META"}}}}
  ]
}'

Four properties of the batch trip people up, all from the API reference:

  • Not atomic: "The individual PutItem and DeleteItem operations specified in BatchWriteItem are atomic; however BatchWriteItem as a whole is not." Some deletes can land while others don't.
  • You must retry UnprocessedItems. Throttled requests come back in the UnprocessedItems response map, in the same shape as the request. "Typically, you would call BatchWriteItem in a loop", and AWS "strongly recommend[s] that you use an exponential backoff algorithm" between retries.
  • No conditions. "You cannot specify conditions on individual put and delete requests". A conditional delete goes through DeleteItem or a transaction instead.
  • No duplicate keys. Two operations on the same key in one call fail the whole call with a ValidationException. De-duplicate your key list (a Set of PK + SK strings) before you chunk it.

The loop shape (chunk by 25, send, re-queue the leftovers with backoff) is written out in batch operations.

Method 2: TransactWriteItems (atomic deletes)

When deletes must succeed or fail together (a parent item and its children, say), use TransactWriteItems: up to 100 actions (deletes, puts, updates, condition checks) applied atomically, with per-item allowed.

The trade: a transactional write costs double the write capacity of a plain write. The full mechanics (idempotency tokens, TransactionCanceledException, cost math) are in DynamoDB transactions.

Method 3: PartiQL DELETE, batched

PartiQL keeps the per-key rule and only changes the spelling.

The developer guide is explicit (PartiQL DELETE): "You can only delete one item at a time. You cannot issue a single DynamoDB PartiQL statement that deletes multiple items", and the WHERE condition "must resolve to a single primary key value".

To delete several items you batch single-item statements. BatchExecuteStatement runs up to 25 statements per batch (all reads or all writes, never mixed):

[
  {"Statement": "DELETE FROM \"Tickets\" WHERE PK = 'TICKET#1001' AND SK = 'META'"},
  {"Statement": "DELETE FROM \"Tickets\" WHERE PK = 'TICKET#1002' AND SK = 'META'"}
]
aws dynamodb batch-execute-statement --statements file://statements.json

A WHERE that doesn't pin the full key fails, so there is no DELETE FROM Tickets WHERE status = 'archived'. More PartiQL patterns (and the limits behind them) in PartiQL examples and PartiQL vs SQL.

Deleting ALL items (truncate)

DynamoDB has no truncate. Your options, in order of preference:

  1. Delete and recreate the table. DeleteTable + CreateTable skips the per-item write bill. The settings don't come back on their own (see below).
  2. Expire items with . For "delete everything older than X", if your items already carry a Number expiry in epoch seconds, TTL deletes them without consuming write throughput, "within a few days" of expiry. Filter expired items out of reads meanwhile.
  3. Scan + batch-delete (Method 1) when the table must stay live and only a subset goes. You pay for every byte the Scan reads plus a write per item deleted.

Recreating, step by step: turn deletion protection off first, or DeleteTable fails. Re-declare keys, indexes, streams, capacity mode and tags in CreateTable.

Once the new table is ACTIVE, re-enable TTL (UpdateTimeToLive; it isn't a CreateTable field), point-in-time recovery and auto scaling.

The new table has a new stream ARN, so rewire Lambda triggers and other stream consumers. Infrastructure as code makes all of this one deploy.

TTL is free only when the attribute is already there. Stamping an expiry onto existing items is one write each, the same bill as deleting them. On a global table, each replica Region also pays for the replicated TTL delete.

The Scan cost is per 4 KB read, not per item: a million 200-byte items is about 195 MB, or roughly 24,400 eventually consistent read units at half a unit per 4 KB.

Delete multiple items in DynoTable

In DynoTable the find-the-keys, chunk and retry loop is a grid selection. Two cases:

  1. Rows you selected. Run or filter a query, select the rows, and press ⌘⌫. Each row becomes a staged delete: it tints red in the grid and appears as a card in Pending changes. Nothing has touched DynamoDB yet.
  2. Every item the query matches. Press ⌘A (or tick the header checkbox) on a visual query or scan with unloaded pages. Delete then offers Stage delete or Commit delete for up to 10,000 items, or Delete directly for the whole match.

A Commit goes out as TransactWriteItems chunks of up to 100 items, each delete conditional on the item still existing. If a chunk fails, the commit stops and earlier chunks stay applied. You can revert them from History.

Delete directly writes as it reads, with no stage and no History, so it cannot be reverted. You type the table name to confirm.

Safe (the default) re-checks the filter on each delete; Fast batch-deletes without re-checking. Max writes/s caps the pace on a production table.

⌘⇧⌫ on selected rows skips the review: it deletes them directly as unconditional BatchWriteItem calls of 25, retrying unprocessed items for you.

Staged deletes in DynoTable: selected rows tinted red in the grid, each one a reviewable card in Pending changes before commit.
Staged deletes in DynoTable: selected rows tinted red in the grid, each one a reviewable card in Pending changes before commit.

To wipe a whole table, export its definition as code first (Terraform, CDK or CloudFormation; it leaves out PITR, tags and auto scaling), then delete and recreate the table. The bulk delete docs cover the modes in detail.

If a delete needs a hand-written condition (attribute_exists, a version check), build the exact ConditionExpression + placeholder maps in the DynamoDB Expression Builder and attach it to a DeleteItem or transaction instead.

FAQ

Can I delete multiple items in one DynamoDB call? Yes. Up to 25 with BatchWriteItem (best-effort, retry UnprocessedItems) or up to 100 atomically with TransactWriteItems. Both take a full primary key per item; neither accepts a "where" condition to select the items.

Is there a DELETE WHERE in DynamoDB? No. Neither the low-level API nor PartiQL can select the items to delete by a condition: PartiQL's DELETE requires a WHERE that "resolve[s] to a single primary key value". You query or scan for the keys first, then delete each one.

How do I delete all items in a DynamoDB table? For a full wipe, delete and recreate the table. It avoids paying one write per item. For age-based cleanup, use TTL, which deletes expired items without consuming write throughput, within a few days of expiry. Scan + batch-delete is the fallback when the table must stay live.

Why are some items still there after my batch delete? Almost always unretried UnprocessedItems: a throttled BatchWriteItem returns the failed deletes in the response, and code that doesn't loop over them "finishes" with items still in the table. Retry with exponential backoff until the map comes back empty.

How much does deleting items cost? Each delete is a write sized by the deleted item (one write unit per 1 KB, plus a write to each GSI that holds it, doubled inside a transaction), and finding the keys costs reads too. Estimate the cost with the pricing calculator.

Prefer to see what you're deleting first? Download DynoTable: select the rows, stage the deletes with ⌘⌫, review the diffs, and commit when you're sure.

By Dmitriy · Updated