Intermedio6 min de lectura

Expresiones de condición de clave en DynamoDB

Una expresión de condición de clave es el KeyConditionExpression que pasas a un Query — la única parte de la petición que DynamoDB usa para encontrar items. Todo lo demás (filtros, proyecciones) corre después de que la lectura ya está medida.

¿Qué es una expresión de condición de clave en DynamoDB?

Una expresión de condición de clave es el KeyConditionExpression de un Query que le dice a DynamoDB qué items leer. La debe ser una igualdad (PK = :v); la admite un operador de rango — =, <, <=, >, >=, BETWEEN, o begins_with. Decide qué se lee y se factura, a diferencia de un filtro.

  • La debe ser una igualdad. PK = :v y nada más — sin rangos, sin begins_with, sin IN. DynamoDB la hashea para localizar una partición.
  • La admite un operador de rango. =, <, <=, >, >=, BETWEEN, o begins_with — aquí es donde cortas una .
  • No es un filtro. Una condición de clave decide qué se lee y se factura; un FilterExpression solo recorta el resultado después de que ya pagaste la lectura.
  • Las claves de ordenación van ordenadas por bytes. Los operadores de rango comparan lexicográficamente, así que cómo formatea la cadena de la clave de ordenación es tu poder de query.

Por qué la clave de partición está bloqueada a igualdad

DynamoDB guarda items hasheando la clave de partición a una partición física. Un hash te da una ubicación, no un rango — así que no hay nada que escanear a través.

Por eso PK > :v o begins_with(PK, :v) se rechazan de plano. El motor no puede responder "todas las particiones cuya clave empieza por X" sin leer toda la tabla, que es exactamente el Scan que está construido para evitar.

Si vienes de SQL, se siente al revés: WHERE id LIKE 'order%' es trivial en Postgres. En DynamoDB la clave de partición es una dirección, no una columna buscable.

La clave de ordenación es donde vive el poder

Dentro de una partición, los items se almacenan ordenados por la clave de ordenación. Ese orden es lo que explotan los operadores de rango — DynamoDB busca una posición y lee hacia adelante.

OperadorLeeÚsalo para
SK = :vUn item exactoUn hijo concreto por su clave
SK < / <= / > / >= :vUn slice abierto"Todo después de este punto"
SK BETWEEN :a AND :bUn rango cerrado (inclusivo)Una ventana acotada — un rango de fechas
begins_with(SK, :p)Un slice por prefijoUn tipo o jerarquía bajo el PK

No hay LIKE, no hay CONTAINS, no hay ENDS_WITH en la clave. El matching de substring y sufijo no está ordenado por bytes, así que forzarían una lectura completa — por diseño, la API no te lo deja. El matching de substring existe vía contains() en un FilterExpression (donde ya pagaste la lectura); el matching de sufijo no está disponible en el servidor en absoluto — guarda una clave invertida o fíltralo en el cliente. (AWS: Key condition expressions)

Un ejemplo trabajado: mensajes en una app de chat

Digamos que construyes chat por canal. Una tabla, particionada por canal, ordenada por tiempo de mensaje. Esquema de claves original:

  • Clave de partición ChannelRefCH#{channelId}
  • Clave de ordenación PostedAt — un timestamp ISO-8601, MSG#2026-06-23T14:05:00Z

El prefijo MSG# mantiene las filas de mensaje ordenables y distintas de cualquier otro tipo de fila que puedas colocalizar bajo el mismo canal (config pineada, membership).

Carga los mensajes más recientes de un canal. Solo la clave de partición, más nuevos primero:

KeyConditionExpression      ChannelRef = :ch
ExpressionAttributeValues   { ":ch": "CH#general" }
ScanIndexForward            false

ScanIndexForward: false recorre la colección ordenada al revés — la forma barata de obtener "más recientes primero" sin ordenar en el cliente.

Un día concreto con begins_with. Como el timestamp es la clave de ordenación y está guardado como texto, un prefijo de fecha es un slice limpio:

KeyConditionExpression  ChannelRef = :ch AND begins_with(PostedAt, :day)
:ch    "CH#general"
:day   "MSG#2026-06-23"

Eso lee cada mensaje del 2026-06-23 y nada más — DynamoDB busca el prefijo y para cuando se sale del final. Solo funciona porque el prefijo es un ancla izquierda verdadera de una cadena ordenada por bytes.

Una ventana precisa con BETWEEN. Para "los mensajes durante la hora de las 14:00", un rango inclusivo gana a un prefijo:

KeyConditionExpression  ChannelRef = :ch AND PostedAt BETWEEN :lo AND :hi
:ch    "CH#general"
:lo    "MSG#2026-06-23T14:00:00Z"
:hi    "MSG#2026-06-23T14:59:59Z"

BETWEEN es inclusivo en ambos extremos, así que elige los endpoints a propósito — un off-by-one aquí deja caer o duplica en silencio un mensaje del borde.

Puedes montar y copiar cualquiera de estas expresiones, con el mapa de ExpressionAttributeValues rellenado por ti, en el DynamoDB expression builder — útil para acertar la sintaxis de begins_with y BETWEEN a la primera.

Este builder viene preset a una query pk = … AND begins_with(sk, …) — cambia el operador para ver actualizarse el KeyConditionExpression:

Construye tu solicitud
Código generado
new QueryCommand({
  "TableName": "AuditLog",
  "KeyConditionExpression": "#hashKey = :hashKeyValue AND begins_with(#rangeKey, :rangeKeyValue)",
  "ExpressionAttributeNames": {
    "#hashKey": "pk",
    "#rangeKey": "sk"
  },
  "ExpressionAttributeValues": {
    ":hashKeyValue": {
      "S": "TENANT#acme"
    },
    ":rangeKeyValue": {
      "S": "EVENT#2026-06"
    }
  }
})

Míralo en DynoTable

Ejecuta la misma condición de clave contra una partición real de canal. En el momento en que pones un filtro de clave de partición, DynoTable emite un Query — así que cargas solo ese slice, no toda la colección.

La trampa: confundir una condición de clave con un filtro

El error caro es ir a por FilterExpression para hacer el trabajo de una clave. Un filtro ni siquiera puede referenciar PostedAt — es la clave de ordenación, y DynamoDB rechaza un filtro sobre un atributo de clave con un ValidationException. Así que el workaround es duplicar la fecha en un atributo plano no-clave (MessageDate) y filtrar por eso:

KeyConditionExpression   ChannelRef = :ch
FilterExpression         begins_with(MessageDate, :day)

Esto parece equivalente a la condición de clave begins_with de arriba y devuelve las mismas filas — pero primero lee la partición entera del canal y luego descarta todo lo que queda fuera del día. Te facturan la lectura completa.

Los filtros nunca reducen el coste de lectura. Corren después de que DynamoDB ha medido los items, el mismo pie que un Scan filtrado. Si un predicado puede ir en la condición de clave, pertenece ahí.

Arréglalo upstream. Si un patrón de acceso no se puede expresar como una igualdad de PK más un rango de clave de ordenación, eso es una señal de modelado. O reformulas la clave de ordenación, o añades un índice claveado para el patrón — mira GSI vs LSI y single-table design para cómo colocar las claves.

Escollos y próximos pasos

  • La clave de partición es siempre =. Sin rangos, nunca. Si necesitas un rango a través de particiones, has crecido más allá de un solo Query.
  • Una condición de clave de ordenación por query. No puedes hacer AND de dos predicados de sort-key; elige BETWEEN o begins_with, no ambos.
  • Las palabras reservadas necesitan alias. Una clave llamada Timestamp o Name debe usar ExpressionAttributeNames (#ts), o la query falla. (AWS: reserved words)
  • BETWEEN es inclusivo. Ambos extremos coinciden — diseña tus bounds en consecuencia.

Redacta tus condiciones de clave en el expression builder, luego prueba DynoTable para ejecutarlas contra tus propias tablas y ver exactamente qué slice devuelve cada condición de clave.

Actualizado