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 = :vy nada más — sin rangos, sinbegins_with, sinIN. DynamoDB la hashea para localizar una partición. - La admite un operador de rango.
=,<,<=,>,>=,BETWEEN, obegins_with— aquí es donde cortas una . - No es un filtro. Una condición de clave decide qué se lee y se factura; un
FilterExpressionsolo 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.
| Operador | Lee | Úsalo para |
|---|---|---|
SK = :v | Un item exacto | Un hijo concreto por su clave |
SK < / <= / > / >= :v | Un slice abierto | "Todo después de este punto" |
SK BETWEEN :a AND :b | Un rango cerrado (inclusivo) | Una ventana acotada — un rango de fechas |
begins_with(SK, :p) | Un slice por prefijo | Un 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
ChannelRef—CH#{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:
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 soloQuery. - Una condición de clave de ordenación por query. No puedes hacer
ANDde dos predicados de sort-key; eligeBETWEENobegins_with, no ambos. - Las palabras reservadas necesitan alias. Una clave llamada
TimestampoNamedebe usarExpressionAttributeNames(#ts), o la query falla. (AWS: reserved words) BETWEENes 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.