DuplicateItemException: duplicate primary key

TL;DR — Um INSERT do PartiQL é uma criação estrita: se um item com a mesma chave primária já existe, o DynamoDB lança DuplicateItemException em vez de sobrescrevê-lo. Para modificar o item existente use o UPDATE do PartiQL; para obter a semântica de substituir-se-existir do PutItem, use o próprio PutItem — o INSERT do PartiQL deliberadamente nunca substitui.

O que significa

DuplicateItemException: There was an attempt to insert an item with the
same primary key as an item that already exists in the DynamoDB table.

O data-plane do PartiQL (ExecuteStatement / ExecuteTransaction / BatchExecuteStatement) mapeia INSERT para uma criação condicional — ele só é bem-sucedido quando nenhum item com aquela chave primária existe. Esse é o padrão oposto do PutItem nativo, que substitui um item existente silenciosamente. Se você veio do SQL esperando que INSERT falhe em uma chave duplicada, este é exatamente esse comportamento; se você esperava semântica de upsert, este erro é a surpresa.

Por que isso acontece

  • O item genuinamente já existe — um retry, um replay, ou dois processos de escrita competindo na mesma chave ambos emitindo INSERT.
  • Você queria uma atualização, não uma criação — portar código no estilo PutItem para o PartiQL e supor que INSERT substitui.
  • Um loop de retry não idempotente — a primeira tentativa foi bem-sucedida mas a resposta foi perdida (timeout), e o retry reinsere a mesma chave.
  • Uma chave sintética que não é única — a chave de partição/ordenação que você compõe colide mais frequentemente do que você imagina (por exemplo, um timestamp com precisão de segundos).

Como corrigir

  1. Atualizando um item existente? Use UPDATE:

    UPDATE "orders" SET status = 'shipped' WHERE pk = 'ORDER#123' AND sk = 'META'
  2. Quer substituir-se-existir (semântica do PutItem)? Chame PutItem — o PartiQL não tem instrução de upsert, e o padrão da API nativa é exatamente a sobrescrita que você busca:

    await client.send(new PutItemCommand({TableName: 'orders', Item: item}));
  3. Trate como sucesso quando a criação é idempotente — se um retry encontra DuplicateItemException para um item que sua primeira tentativa já escreveu, capturá-lo e ignorá-lo é frequentemente o tratamento correto.

  4. Mantenha o INSERT quando você depende da unicidade — a exceção é seu guard de somente-criação, o equivalente no PartiQL de attribute_not_exists() em uma condição de PutItem.

Testar instruções contra dados reais é a forma mais rápida de internalizar a divisão INSERT/UPDATE — o editor PartiQL do DynoTable roda instruções contra suas tabelas ao vivo com diagnósticos inline e correções rápidas, e o DynamoDB Expression Builder emite as requisições equivalentes da API nativa quando você precisa da semântica de PutItem em vez disso.

Reproduza

Um INSERT do PartiQL para uma chave primária que já existe:

await client.send(
  new PutItemCommand({
    TableName: 'orders',
    Item: {pk: {S: 'ORDER#1'}, sk: {S: 'META'}}
  })
);
await client.send(
  new ExecuteStatementCommand({
    Statement: `INSERT INTO "orders" VALUE {'pk':'ORDER#1','sk':'META'}`
  })
);

Saída real:

DuplicateItem: Duplicate primary key exists in table
HTTP 400

Vale saber se você testa localmente: o DynamoDB Local expõe isso como DuplicateItem com a mensagem curta acima, enquanto a referência da API do serviço documenta como DuplicateItemException com uma frase mais longa. Case pelo HTTP 400 mais a operação, não pelo nome ou pelo texto exatos, senão seu handler vai se comportar de um jeito contra o Local e de outro contra a tabela real.

Erros relacionados

Referências

Verificado pela última vez em 2026-07-13 contra a documentação oficial da AWS vinculada acima.

Reproduzido em 2026-07-26 no DynamoDB Local 2.x com o AWS SDK for JavaScript v3.1095.0 — a saída acima é literal.

Trabalhe com o DynamoDB sem o Console

Um cliente desktop rápido para DynamoDB que roda o SQL de verdade que o DynamoDB não consegue — JOINs, GROUP BY, agregações — com edição visual e um agente de IA com suas próprias chaves do Bedrock.

Teste grátis de 30 dias, sem cartão de crédito — depois o plano Grátis sem limite de tempo.