Modèle de clés à attributs multiples - Amazon DynamoDB

Les traductions sont fournies par des outils de traduction automatique. En cas de conflit entre le contenu d'une traduction et celui de la version originale en anglais, la version anglaise prévaudra.

Modèle de clés à attributs multiples

Aperçu

Les clés à attributs multiples vous permettent de créer une partition d'index secondaire global (GSI) et de trier les clés composées d'un maximum de quatre attributs chacune. Cela réduit le code côté client et facilite la modélisation initiale des données et l'ajout de nouveaux modèles d'accès ultérieurement.

Imaginons un scénario courant : pour créer un GSI qui interroge des éléments selon plusieurs attributs hiérarchiques, vous devez traditionnellement créer des clés synthétiques en concaténant des valeurs. Par exemple, dans une application de jeu, pour interroger les matchs d'un tournoi par tournoi, région et manche, vous pouvez créer une clé de partition GSI synthétique telle que TOURNAMENT# WINTER2 024 #REGION #NA -EAST et une clé de tri synthétique telle que ROUND #SEMIFINALS #BRACKET #UPPER. Cette approche fonctionne, mais nécessite la concaténation de chaînes lors de l'écriture de données, l'analyse lors de la lecture et le remplissage de clés synthétiques sur tous les éléments existants si vous ajoutez le GSI à une table existante. Cela rend le code plus encombré et il est difficile de maintenir la sécurité des types sur les composants clés individuels.

Les clés à attributs multiples résolvent ce problème pour GSIs. Vous définissez votre clé de partition GSI à l'aide de plusieurs attributs existants tels que TournamentID et region. DynamoDB gère automatiquement la logique des clés composites, en les hachant ensemble pour la distribution des données. Vous écrivez des éléments en utilisant les attributs naturels de votre modèle de domaine, et le GSI les indexe automatiquement. Pas de concaténation, pas d'analyse syntaxique, pas de remblayage. Votre code reste propre, vos données restent saisies et vos requêtes restent simples. Cette approche est particulièrement utile lorsque vous disposez de données hiérarchiques avec des groupements d'attributs naturels (par exemple tournoi → région → manche, ou organisation → département → équipe).

Exemple d'application

Ce guide explique comment créer un système de suivi des matchs de tournoi pour une plateforme d'e-sport. La plateforme doit interroger efficacement les matchs selon plusieurs critères : par tournoi et par région pour la gestion des brackets, par joueur pour l'historique des matchs et par date pour la programmation.

Modèle de données

Dans cette présentation, le système de suivi des matchs de tournoi prend en charge trois modèles d'accès principaux, chacun nécessitant une structure de clé différente :

Modèle d'accès 1 : recherchez une correspondance spécifique à l'aide de son identifiant unique

  • Solution : table de base avec matchId comme clé de partition

Modèle d'accès 2 : recherchez tous les matchs pour un tournoi et une région spécifiques, en filtrant éventuellement par tour, bracket ou match

  • Solution : index secondaire global avec clé de partition multi-attributs (tournamentId+region) et clé de tri multi-attributs (round+ +bracket) matchId

  • Exemples de requêtes : « Tous les WINTER2 024 matchs dans la région NA-EAST » ou « Tous les matchs des DEMI-FINALES dans la tranche SUPÉRIEURE pour 024/NA-EAST » WINTER2

Modèle d'accès 3 : recherchez l'historique des matchs d'un joueur, en filtrant éventuellement par plage de dates ou par tour de tournoi

  • Solution : index secondaire global avec clé de partition unique (player1Id) et clé de tri multi-attributs (matchDate+round)

  • Exemples de requêtes : « Tous les matchs du joueur 101 » ou « Les matchs du joueur 101 en janvier 2024 »

La principale différence entre les approches traditionnelles et les approches à attributs multiples apparaît clairement lorsque l'on examine la structure des articles :

Approche traditionnelle de l'index secondaire global (clés concaténées) :

// Manual concatenation required for GSI keys const item = { matchId: 'match-001', // Base table PK tournamentId: 'WINTER2024', region: 'NA-EAST', round: 'SEMIFINALS', bracket: 'UPPER', player1Id: '101', // Synthetic keys needed for GSI GSI_PK: `TOURNAMENT#${tournamentId}#REGION#${region}`, // Must concatenate GSI_SK: `${round}#${bracket}#${matchId}`, // Must concatenate // ... other attributes };

Approche de l'index secondaire global à attributs multiples (clés natives) :

// Use existing attributes directly - no concatenation needed const item = { matchId: 'match-001', // Base table PK tournamentId: 'WINTER2024', region: 'NA-EAST', round: 'SEMIFINALS', bracket: 'UPPER', player1Id: '101', matchDate: '2024-01-18', // No synthetic keys needed - GSI uses existing attributes directly // ... other attributes };

Avec les clés à attributs multiples, vous pouvez écrire des éléments une seule fois avec des attributs de domaine naturels. DynamoDB les indexe automatiquement sur GSIs plusieurs sans nécessiter de clés concaténées synthétiques.

Schéma de table de base :

  • Clé de partition : matchId (1 attribut)

Schéma d'index secondaire global (TournamentRegionIndex avec clés à attributs multiples) :

  • Clé de partition :tournamentId, region (2 attributs)

  • Clé de tri : roundbracket,, matchId (3 attributs)

Schéma d'index secondaire global (PlayerMatchHistoryIndex avec clés à attributs multiples) :

  • Clé de partition : player1Id (1 attribut)

  • Clé de tri :matchDate, round (2 attributs)

Table de base : TournamentMatches

MatchID (PK) ID du tournoi region round crochet ID du joueur 1 ID du joueur 2 Date du match vainqueur score
match-001 WINTER2024 NA-EAST FINALES CHAMPIONNAT 101 103 20-01-2024 101 3-1
match-002 WINTER2024 NA-EAST DEMI-FINALES UPPER 101 105 18-01-2024 101 3-2
match-003 WINTER2024 NA-EAST DEMI-FINALES UPPER 103 107 18-01-2024 103 3-0
match-004 WINTER2024 NA-EAST QUARTS DE FINALE UPPER 101 109 15-01-2024 101 3-1
match-005 WINTER2024 NA-WEST FINALES CHAMPIONNAT 102 104 20-01-2024 102 3-2
match-006 WINTER2024 NA-WEST DEMI-FINALES UPPER 102 106 18-01-2024 102 3-1
match-007 SPRING2024 NA-EAST QUARTS DE FINALE UPPER 101 108 15/03/2024 101 3-0
match-008 SPRING2024 NA-EAST QUARTS DE FINALE LOWER 103 110 15/03/2024 103 3-2

GSI : TournamentRegionIndex (clés à attributs multiples)

ID du tournoi (PK) région (PK) rond (SK) support (SK) MatchID (SK) ID du joueur 1 ID du joueur 2 Date du match vainqueur score
WINTER2024 NA-EAST FINALES CHAMPIONNAT match-001 101 103 20-01-2024 101 3-1
WINTER2024 NA-EAST QUARTS DE FINALE UPPER match-004 101 109 15-01-2024 101 3-1
WINTER2024 NA-EAST DEMI-FINALES UPPER match-002 101 105 18-01-2024 101 3-2
WINTER2024 NA-EAST DEMI-FINALES UPPER match-003 103 107 18-01-2024 103 3-0
WINTER2024 NA-WEST FINALES CHAMPIONNAT match-005 102 104 20-01-2024 102 3-2
WINTER2024 NA-WEST DEMI-FINALES UPPER match-006 102 106 18-01-2024 102 3-1
SPRING2024 NA-EAST QUARTS DE FINALE LOWER match-008 103 110 15/03/2024 103 3-2
SPRING2024 NA-EAST QUARTS DE FINALE UPPER match-007 101 108 15/03/2024 101 3-0

GSI : PlayerMatchHistoryIndex (clés à attributs multiples)

ID du joueur 1 (PK) MatchDate (SK) rond (SK) ID du tournoi region crochet Identifiant du match ID du joueur 2 vainqueur score
101 15-01-2024 QUARTS DE FINALE WINTER2024 NA-EAST UPPER match-004 109 101 3-1
101 18-01-2024 DEMI-FINALES WINTER2024 NA-EAST UPPER match-002 105 101 3-2
101 20-01-2024 FINALES WINTER2024 NA-EAST CHAMPIONNAT match-001 103 101 3-1
101 15/03/2024 QUARTS DE FINALE SPRING2024 NA-EAST UPPER match-007 108 101 3-0
102 18-01-2024 DEMI-FINALES WINTER2024 NA-WEST UPPER match-006 106 102 3-1
102 20-01-2024 FINALES WINTER2024 NA-WEST CHAMPIONNAT match-005 104 102 3-2
103 18-01-2024 DEMI-FINALES WINTER2024 NA-EAST UPPER match-003 107 103 3-0
103 15/03/2024 QUARTS DE FINALE SPRING2024 NA-EAST LOWER match-008 110 103 3-2

Prérequis

Avant de commencer, assurez-vous de disposer des éléments suivants :

Compte et autorisations

  • Un AWS compte actif (créez-en un ici si nécessaire)

  • Autorisations IAM pour les opérations DynamoDB :

    • dynamodb:CreateTable

    • dynamodb:DeleteTable

    • dynamodb:DescribeTable

    • dynamodb:PutItem

    • dynamodb:Query

    • dynamodb:BatchWriteItem

Note

Note de sécurité : Pour une utilisation en production, créez une politique IAM personnalisée avec uniquement les autorisations dont vous avez besoin. Pour ce didacticiel, vous pouvez utiliser la politique AWS géréeAmazonDynamoDBFullAccessV2.

Environnement de développement

  • Node.js installé sur votre machine

  • AWS informations d'identification configurées à l'aide de l'une des méthodes suivantes :

Option 1 : AWS CLI

aws configure

Option 2 : variables d’environnement

export AWS_ACCESS_KEY_ID=your_access_key_here export AWS_SECRET_ACCESS_KEY=your_secret_key_here export AWS_DEFAULT_REGION=us-east-1

Installation des packages obligatoires

npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb

Mise en œuvre

Étape 1 : Création d'une table à GSIs l'aide de clés à attributs multiples

Créez une table avec une structure de clés de base simple et utilisant GSIs des clés à attributs multiples.

import { DynamoDBClient, CreateTableCommand } from "@aws-sdk/client-dynamodb"; const client = new DynamoDBClient({ region: 'us-west-2' }); const response = await client.send(new CreateTableCommand({ TableName: 'TournamentMatches', // Base table: Simple partition key KeySchema: [ { AttributeName: 'matchId', KeyType: 'HASH' } // Simple PK ], AttributeDefinitions: [ { AttributeName: 'matchId', AttributeType: 'S' }, { AttributeName: 'tournamentId', AttributeType: 'S' }, { AttributeName: 'region', AttributeType: 'S' }, { AttributeName: 'round', AttributeType: 'S' }, { AttributeName: 'bracket', AttributeType: 'S' }, { AttributeName: 'player1Id', AttributeType: 'S' }, { AttributeName: 'matchDate', AttributeType: 'S' } ], // GSIs with multi-attribute keys GlobalSecondaryIndexes: [ { IndexName: 'TournamentRegionIndex', KeySchema: [ { AttributeName: 'tournamentId', KeyType: 'HASH' }, // GSI PK attribute 1 { AttributeName: 'region', KeyType: 'HASH' }, // GSI PK attribute 2 { AttributeName: 'round', KeyType: 'RANGE' }, // GSI SK attribute 1 { AttributeName: 'bracket', KeyType: 'RANGE' }, // GSI SK attribute 2 { AttributeName: 'matchId', KeyType: 'RANGE' } // GSI SK attribute 3 ], Projection: { ProjectionType: 'ALL' } }, { IndexName: 'PlayerMatchHistoryIndex', KeySchema: [ { AttributeName: 'player1Id', KeyType: 'HASH' }, // GSI PK { AttributeName: 'matchDate', KeyType: 'RANGE' }, // GSI SK attribute 1 { AttributeName: 'round', KeyType: 'RANGE' } // GSI SK attribute 2 ], Projection: { ProjectionType: 'ALL' } } ], BillingMode: 'PAY_PER_REQUEST' })); console.log("Table with multi-attribute GSI keys created successfully");

Principales décisions de conception :

Table de base : La table de base utilise une clé de matchId partition simple pour les recherches par correspondance directe, ce qui permet de maintenir la structure de la table de base simple tout en GSIs fournissant des modèles de requête complexes.

TournamentRegionIndex Index secondaire global : L'index secondaire TournamentRegionIndex mondial utilise tournamentId + region comme clé de partition à attributs multiples, ce qui crée une isolation entre les régions de tournois, les données étant distribuées par le hachage des deux attributs combinés, ce qui permet d'effectuer des requêtes efficaces dans un contexte de région de tournoi spécifique. La clé de tri à attributs multiples (round+ bracket +matchId) permet un tri hiérarchique qui prend en charge les requêtes à tous les niveaux de la hiérarchie avec un ordre naturel, du général (rond) au spécifique (identifiant de correspondance).

PlayerMatchHistoryIndex Index secondaire mondial : L'index secondaire PlayerMatchHistoryIndex mondial réorganise les données par joueur en les utilisant player1Id comme clé de partition, ce qui permet d'effectuer des requêtes entre tournois pour un joueur spécifique. La clé de tri à attributs multiples (matchDate+round) fournit un ordre chronologique avec la possibilité de filtrer par plages de dates ou par tours de tournoi spécifiques.

Étape 2 : Insérer des données avec des attributs natifs

Ajoutez les données des matchs du tournoi à l'aide d'attributs naturels. Le GSI indexera automatiquement ces attributs sans nécessiter de clés synthétiques.

import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; import { DynamoDBDocumentClient, PutCommand } from "@aws-sdk/lib-dynamodb"; const client = new DynamoDBClient({ region: 'us-west-2' }); const docClient = DynamoDBDocumentClient.from(client); // Tournament match data - no synthetic keys needed for GSIs const matches = [ // Winter 2024 Tournament, NA-EAST region { matchId: 'match-001', tournamentId: 'WINTER2024', region: 'NA-EAST', round: 'FINALS', bracket: 'CHAMPIONSHIP', player1Id: '101', player2Id: '103', matchDate: '2024-01-20', winner: '101', score: '3-1' }, { matchId: 'match-002', tournamentId: 'WINTER2024', region: 'NA-EAST', round: 'SEMIFINALS', bracket: 'UPPER', player1Id: '101', player2Id: '105', matchDate: '2024-01-18', winner: '101', score: '3-2' }, { matchId: 'match-003', tournamentId: 'WINTER2024', region: 'NA-EAST', round: 'SEMIFINALS', bracket: 'UPPER', player1Id: '103', player2Id: '107', matchDate: '2024-01-18', winner: '103', score: '3-0' }, { matchId: 'match-004', tournamentId: 'WINTER2024', region: 'NA-EAST', round: 'QUARTERFINALS', bracket: 'UPPER', player1Id: '101', player2Id: '109', matchDate: '2024-01-15', winner: '101', score: '3-1' }, // Winter 2024 Tournament, NA-WEST region { matchId: 'match-005', tournamentId: 'WINTER2024', region: 'NA-WEST', round: 'FINALS', bracket: 'CHAMPIONSHIP', player1Id: '102', player2Id: '104', matchDate: '2024-01-20', winner: '102', score: '3-2' }, { matchId: 'match-006', tournamentId: 'WINTER2024', region: 'NA-WEST', round: 'SEMIFINALS', bracket: 'UPPER', player1Id: '102', player2Id: '106', matchDate: '2024-01-18', winner: '102', score: '3-1' }, // Spring 2024 Tournament, NA-EAST region { matchId: 'match-007', tournamentId: 'SPRING2024', region: 'NA-EAST', round: 'QUARTERFINALS', bracket: 'UPPER', player1Id: '101', player2Id: '108', matchDate: '2024-03-15', winner: '101', score: '3-0' }, { matchId: 'match-008', tournamentId: 'SPRING2024', region: 'NA-EAST', round: 'QUARTERFINALS', bracket: 'LOWER', player1Id: '103', player2Id: '110', matchDate: '2024-03-15', winner: '103', score: '3-2' } ]; // Insert all matches for (const match of matches) { await docClient.send(new PutCommand({ TableName: 'TournamentMatches', Item: match })); console.log(`Added: ${match.matchId} - ${match.tournamentId}/${match.region} - ${match.round} ${match.bracket}`); } console.log(`\nInserted ${matches.length} tournament matches`); console.log("No synthetic keys created - GSIs use native attributes automatically");

Structure des données expliquée :

Utilisation naturelle des attributs : chaque attribut représente un véritable concept de tournoi sans aucune concaténation ou analyse de chaînes requise, fournissant ainsi un mappage direct avec le modèle de domaine.

Indexation automatique de l'index secondaire global : Indexation GSIs automatique des éléments à l'aide des attributs existants (tournamentIdregion,round,bracket, matchId pour TournamentRegionIndex et player1IdmatchDate, round pour PlayerMatchHistoryIndex) sans nécessiter de clés concaténées synthétiques.

Aucun remplissage supplémentaire n'est nécessaire : lorsque vous ajoutez un nouvel index secondaire global avec des clés à attributs multiples à une table existante, DynamoDB indexe automatiquement tous les éléments existants à l'aide de leurs attributs naturels. Il n'est pas nécessaire de mettre à jour les éléments à l'aide de clés synthétiques.

Étape 3 : Interrogez TournamentRegionIndex l'index secondaire global avec tous les attributs de clé de partition

Cet exemple interroge l'index secondaire TournamentRegionIndex global qui possède une clé de partition à attributs multiples (tournamentId+region). Tous les attributs de clé de partition doivent être spécifiés avec des conditions d'égalité dans les requêtes. Vous ne pouvez pas effectuer de requête tournamentId uniquement ou utiliser des opérateurs d'inégalité sur les attributs de clé de partition.

import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; import { DynamoDBDocumentClient, QueryCommand } from "@aws-sdk/lib-dynamodb"; const client = new DynamoDBClient({ region: 'us-west-2' }); const docClient = DynamoDBDocumentClient.from(client); // Query GSI: All matches for WINTER2024 tournament in NA-EAST region const response = await docClient.send(new QueryCommand({ TableName: 'TournamentMatches', IndexName: 'TournamentRegionIndex', KeyConditionExpression: 'tournamentId = :tournament AND #region = :region', ExpressionAttributeNames: { '#region': 'region', // 'region' is a reserved keyword '#tournament': 'tournament' }, ExpressionAttributeValues: { ':tournament': 'WINTER2024', ':region': 'NA-EAST' } })); console.log(`Found ${response.Items.length} matches for WINTER2024/NA-EAST:\n`); response.Items.forEach(match => { console.log(` ${match.round} | ${match.bracket} | ${match.matchId}`); console.log(` Players: ${match.player1Id} vs ${match.player2Id}`); console.log(` Winner: ${match.winner}, Score: ${match.score}\n`); });

Résultat attendu :

Found 4 matches for WINTER2024/NA-EAST:

  FINALS | CHAMPIONSHIP | match-001
    Players: 101 vs 103
    Winner: 101, Score: 3-1

  QUARTERFINALS | UPPER | match-004
    Players: 101 vs 109
    Winner: 101, Score: 3-1

  SEMIFINALS | UPPER | match-002
    Players: 101 vs 105
    Winner: 101, Score: 3-2

  SEMIFINALS | UPPER | match-003
    Players: 103 vs 107
    Winner: 103, Score: 3-0

Requêtes non valides :

// Missing region attribute KeyConditionExpression: 'tournamentId = :tournament' // Using inequality on partition key attribute KeyConditionExpression: 'tournamentId = :tournament AND #region > :region'

Performances : les clés de partition à attributs multiples sont hachées ensemble, offrant les mêmes performances de recherche O (1) que les clés à attribut unique.

Étape 4 : demander les clés de tri de l'index secondaire global left-to-right

Les attributs clés de tri doivent être interrogés left-to-right dans l'ordre dans lequel ils sont définis dans l'index secondaire global. Cet exemple montre comment interroger les différents TournamentRegionIndex niveaux hiérarchiques : filtrage par simpleround, par round + bracket ou par les trois attributs clés de tri. Vous ne pouvez pas ignorer les attributs au milieu. Par exemple, vous ne pouvez pas effectuer d'interrogation par round et matchId pendant les sauts. bracket

import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; import { DynamoDBDocumentClient, QueryCommand } from "@aws-sdk/lib-dynamodb"; const client = new DynamoDBClient({ region: 'us-west-2' }); const docClient = DynamoDBDocumentClient.from(client); // Query 1: Filter by first sort key attribute (round) console.log("Query 1: All SEMIFINALS matches"); const query1 = await docClient.send(new QueryCommand({ TableName: 'TournamentMatches', IndexName: 'TournamentRegionIndex', KeyConditionExpression: 'tournamentId = :tournament AND #region = :region AND round = :round', ExpressionAttributeNames: { '#region': 'region' // 'region' is a reserved keyword }, ExpressionAttributeValues: { ':tournament': 'WINTER2024', ':region': 'NA-EAST', ':round': 'SEMIFINALS' } })); console.log(` Found ${query1.Items.length} matches\n`); // Query 2: Filter by first two sort key attributes (round + bracket) console.log("Query 2: SEMIFINALS UPPER bracket matches"); const query2 = await docClient.send(new QueryCommand({ TableName: 'TournamentMatches', IndexName: 'TournamentRegionIndex', KeyConditionExpression: 'tournamentId = :tournament AND #region = :region AND round = :round AND bracket = :bracket', ExpressionAttributeNames: { '#region': 'region' // 'region' is a reserved keyword }, ExpressionAttributeValues: { ':tournament': 'WINTER2024', ':region': 'NA-EAST', ':round': 'SEMIFINALS', ':bracket': 'UPPER' } })); console.log(` Found ${query2.Items.length} matches\n`); // Query 3: Filter by all three sort key attributes (round + bracket + matchId) console.log("Query 3: Specific match in SEMIFINALS UPPER bracket"); const query3 = await docClient.send(new QueryCommand({ TableName: 'TournamentMatches', IndexName: 'TournamentRegionIndex', KeyConditionExpression: 'tournamentId = :tournament AND #region = :region AND round = :round AND bracket = :bracket AND matchId = :matchId', ExpressionAttributeNames: { '#region': 'region' // 'region' is a reserved keyword }, ExpressionAttributeValues: { ':tournament': 'WINTER2024', ':region': 'NA-EAST', ':round': 'SEMIFINALS', ':bracket': 'UPPER', ':matchId': 'match-002' } })); console.log(` Found ${query3.Items.length} matches\n`); // Query 4: INVALID - skipping round console.log("Query 4: Attempting to skip first sort key attribute (WILL FAIL)"); try { const query4 = await docClient.send(new QueryCommand({ TableName: 'TournamentMatches', IndexName: 'TournamentRegionIndex', KeyConditionExpression: 'tournamentId = :tournament AND #region = :region AND bracket = :bracket', ExpressionAttributeNames: { '#region': 'region' // 'region' is a reserved keyword }, ExpressionAttributeValues: { ':tournament': 'WINTER2024', ':region': 'NA-EAST', ':bracket': 'UPPER' } })); } catch (error) { console.log(` Error: ${error.message}`); console.log(` Cannot skip sort key attributes - must query left-to-right\n`); }

Résultat attendu :

Query 1: All SEMIFINALS matches
  Found 2 matches

Query 2: SEMIFINALS UPPER bracket matches
  Found 2 matches

Query 3: Specific match in SEMIFINALS UPPER bracket
  Found 1 matches

Query 4: Attempting to skip first sort key attribute (WILL FAIL)
  Error: Query key condition not supported
  Cannot skip sort key attributes - must query left-to-right

Left-to-right règles de requête : vous devez interroger les attributs dans l'ordre de gauche à droite, sans en sauter aucun.

Modèles valides :

  • Premier attribut uniquement : round = 'SEMIFINALS'

  • Les deux premiers attributs : round = 'SEMIFINALS' AND bracket = 'UPPER'

  • Les trois attributs : round = 'SEMIFINALS' AND bracket = 'UPPER' AND matchId = 'match-002'

Modèles non valides :

  • Ignorer le premier attribut : bracket = 'UPPER' (saute le tour)

  • Interrogation hors ordre : matchId = 'match-002' AND round = 'SEMIFINALS'

  • Laisser des espaces : round = 'SEMIFINALS' AND matchId = 'match-002' (ne tient pas compte du crochet)

Note

Conseil de conception : Triez les attributs clés du plus général au plus spécifique afin d'optimiser la flexibilité des requêtes.

Étape 5 : Utiliser les conditions d'inégalité sur les clés de tri de l'indice secondaire global

Les conditions d'inégalité doivent être la dernière condition de votre requête. Cet exemple montre comment utiliser les opérateurs de comparaison (>=,BETWEEN) et la correspondance de préfixes (begins_with()) sur les attributs clés de tri. Une fois que vous avez utilisé un opérateur d'inégalité, vous ne pouvez pas ajouter de conditions clés de tri supplémentaires après celui-ci. L'inégalité doit être la condition finale de votre expression de condition clé.

import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; import { DynamoDBDocumentClient, QueryCommand } from "@aws-sdk/lib-dynamodb"; const client = new DynamoDBClient({ region: 'us-west-2' }); const docClient = DynamoDBDocumentClient.from(client); // Query 1: Round comparison (inequality on first sort key attribute) console.log("Query 1: Matches from QUARTERFINALS onwards"); const query1 = await docClient.send(new QueryCommand({ TableName: 'TournamentMatches', IndexName: 'TournamentRegionIndex', KeyConditionExpression: 'tournamentId = :tournament AND #region = :region AND round >= :round', ExpressionAttributeNames: { '#region': 'region' // 'region' is a reserved keyword }, ExpressionAttributeValues: { ':tournament': 'WINTER2024', ':region': 'NA-EAST', ':round': 'QUARTERFINALS' } })); console.log(` Found ${query1.Items.length} matches\n`); // Query 2: Round range with BETWEEN console.log("Query 2: Matches between QUARTERFINALS and SEMIFINALS"); const query2 = await docClient.send(new QueryCommand({ TableName: 'TournamentMatches', IndexName: 'TournamentRegionIndex', KeyConditionExpression: 'tournamentId = :tournament AND #region = :region AND round BETWEEN :start AND :end', ExpressionAttributeNames: { '#region': 'region' // 'region' is a reserved keyword }, ExpressionAttributeValues: { ':tournament': 'WINTER2024', ':region': 'NA-EAST', ':start': 'QUARTERFINALS', ':end': 'SEMIFINALS' } })); console.log(` Found ${query2.Items.length} matches\n`); // Query 3: Prefix matching with begins_with (treated as inequality) console.log("Query 3: Matches in brackets starting with 'U'"); const query3 = await docClient.send(new QueryCommand({ TableName: 'TournamentMatches', IndexName: 'TournamentRegionIndex', KeyConditionExpression: 'tournamentId = :tournament AND #region = :region AND round = :round AND begins_with(bracket, :prefix)', ExpressionAttributeNames: { '#region': 'region' // 'region' is a reserved keyword }, ExpressionAttributeValues: { ':tournament': 'WINTER2024', ':region': 'NA-EAST', ':round': 'SEMIFINALS', ':prefix': 'U' } })); console.log(` Found ${query3.Items.length} matches\n`); // Query 4: INVALID - condition after inequality console.log("Query 4: Attempting condition after inequality (WILL FAIL)"); try { const query4 = await docClient.send(new QueryCommand({ TableName: 'TournamentMatches', IndexName: 'TournamentRegionIndex', KeyConditionExpression: 'tournamentId = :tournament AND #region = :region AND round > :round AND bracket = :bracket', ExpressionAttributeNames: { '#region': 'region' // 'region' is a reserved keyword }, ExpressionAttributeValues: { ':tournament': 'WINTER2024', ':region': 'NA-EAST', ':round': 'QUARTERFINALS', ':bracket': 'UPPER' } })); } catch (error) { console.log(` Error: ${error.message}`); console.log(` Cannot add conditions after inequality - it must be last\n`); }

Règles relatives aux opérateurs d'inégalité : vous pouvez utiliser des opérateurs de comparaison (>>=,<,,<=) BETWEEN pour les requêtes de plage et begins_with() pour la correspondance de préfixes. L'inégalité doit être la dernière condition de votre requête.

Modèles valides :

  • Conditions d'égalité suivies d'inégalités : round = 'SEMIFINALS' AND bracket = 'UPPER' AND matchId > 'match-001'

  • Inégalité sur le premier attribut : round BETWEEN 'QUARTERFINALS' AND 'SEMIFINALS'

  • Correspondance du préfixe comme condition finale : round = 'SEMIFINALS' AND begins_with(bracket, 'U')

Modèles non valides :

  • Ajouter des conditions après une inégalité : round > 'QUARTERFINALS' AND bracket = 'UPPER'

  • En utilisant de multiples inégalités : round > 'QUARTERFINALS' AND bracket > 'L'

Important

begins_with()est traitée comme une condition d'inégalité, de sorte qu'aucune condition clé de tri supplémentaire ne peut la suivre.

Étape 6 : Interroger l'index secondaire PlayerMatchHistoryIndex global avec une clé de tri à attributs multiples

Cet exemple interroge celui PlayerMatchHistoryIndex qui possède une clé de partition unique (player1Id) et une clé de tri multi-attributs (matchDate+round). Cela permet d'effectuer une analyse entre tournois en interrogeant tous les matchs pour un joueur spécifique sans connaître le tournoi, IDs alors que le tableau de base nécessiterait des requêtes distinctes par combinaison tournien-région.

import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; import { DynamoDBDocumentClient, QueryCommand } from "@aws-sdk/lib-dynamodb"; const client = new DynamoDBClient({ region: 'us-west-2' }); const docClient = DynamoDBDocumentClient.from(client); // Query 1: All matches for Player 101 across all tournaments console.log("Query 1: All matches for Player 101"); const query1 = await docClient.send(new QueryCommand({ TableName: 'TournamentMatches', IndexName: 'PlayerMatchHistoryIndex', KeyConditionExpression: 'player1Id = :player', ExpressionAttributeValues: { ':player': '101' } })); console.log(` Found ${query1.Items.length} matches for Player 101:`); query1.Items.forEach(match => { console.log(` ${match.tournamentId}/${match.region} - ${match.matchDate} - ${match.round}`); }); console.log(); // Query 2: Player 101 matches on specific date console.log("Query 2: Player 101 matches on 2024-01-18"); const query2 = await docClient.send(new QueryCommand({ TableName: 'TournamentMatches', IndexName: 'PlayerMatchHistoryIndex', KeyConditionExpression: 'player1Id = :player AND matchDate = :date', ExpressionAttributeValues: { ':player': '101', ':date': '2024-01-18' } })); console.log(` Found ${query2.Items.length} matches\n`); // Query 3: Player 101 SEMIFINALS matches on specific date console.log("Query 3: Player 101 SEMIFINALS matches on 2024-01-18"); const query3 = await docClient.send(new QueryCommand({ TableName: 'TournamentMatches', IndexName: 'PlayerMatchHistoryIndex', KeyConditionExpression: 'player1Id = :player AND matchDate = :date AND round = :round', ExpressionAttributeValues: { ':player': '101', ':date': '2024-01-18', ':round': 'SEMIFINALS' } })); console.log(` Found ${query3.Items.length} matches\n`); // Query 4: Player 101 matches in date range console.log("Query 4: Player 101 matches in January 2024"); const query4 = await docClient.send(new QueryCommand({ TableName: 'TournamentMatches', IndexName: 'PlayerMatchHistoryIndex', KeyConditionExpression: 'player1Id = :player AND matchDate BETWEEN :start AND :end', ExpressionAttributeValues: { ':player': '101', ':start': '2024-01-01', ':end': '2024-01-31' } })); console.log(` Found ${query4.Items.length} matches\n`);

Variations de motifs

Données chronologiques avec clés à attributs multiples

Optimisation pour les requêtes de séries chronologiques avec des attributs temporels hiérarchiques

{ TableName: 'IoTReadings', // Base table: Simple partition key KeySchema: [ { AttributeName: 'readingId', KeyType: 'HASH' } ], AttributeDefinitions: [ { AttributeName: 'readingId', AttributeType: 'S' }, { AttributeName: 'deviceId', AttributeType: 'S' }, { AttributeName: 'locationId', AttributeType: 'S' }, { AttributeName: 'year', AttributeType: 'S' }, { AttributeName: 'month', AttributeType: 'S' }, { AttributeName: 'day', AttributeType: 'S' }, { AttributeName: 'timestamp', AttributeType: 'S' } ], // GSI with multi-attribute keys for time-series queries GlobalSecondaryIndexes: [{ IndexName: 'DeviceLocationTimeIndex', KeySchema: [ { AttributeName: 'deviceId', KeyType: 'HASH' }, { AttributeName: 'locationId', KeyType: 'HASH' }, { AttributeName: 'year', KeyType: 'RANGE' }, { AttributeName: 'month', KeyType: 'RANGE' }, { AttributeName: 'day', KeyType: 'RANGE' }, { AttributeName: 'timestamp', KeyType: 'RANGE' } ], Projection: { ProjectionType: 'ALL' } }], BillingMode: 'PAY_PER_REQUEST' } // Query patterns enabled via GSI: // - All readings for device in location // - Readings for specific year // - Readings for specific month in year // - Readings for specific day // - Readings in time range

Avantages : La hiérarchie temporelle naturelle (année → mois → jour → horodatage) permet des requêtes efficaces à tout moment avec une granularité sans analyse ou manipulation de dates. L'index secondaire global indexe automatiquement toutes les lectures en utilisant leurs attributs temporels naturels.

Commandes de commerce électronique avec clés à attributs multiples

Suivez les commandes avec plusieurs dimensions

{ TableName: 'Orders', // Base table: Simple partition key KeySchema: [ { AttributeName: 'orderId', KeyType: 'HASH' } ], AttributeDefinitions: [ { AttributeName: 'orderId', AttributeType: 'S' }, { AttributeName: 'sellerId', AttributeType: 'S' }, { AttributeName: 'region', AttributeType: 'S' }, { AttributeName: 'orderDate', AttributeType: 'S' }, { AttributeName: 'category', AttributeType: 'S' }, { AttributeName: 'customerId', AttributeType: 'S' }, { AttributeName: 'orderStatus', AttributeType: 'S' } ], GlobalSecondaryIndexes: [ { IndexName: 'SellerRegionIndex', KeySchema: [ { AttributeName: 'sellerId', KeyType: 'HASH' }, { AttributeName: 'region', KeyType: 'HASH' }, { AttributeName: 'orderDate', KeyType: 'RANGE' }, { AttributeName: 'category', KeyType: 'RANGE' }, { AttributeName: 'orderId', KeyType: 'RANGE' } ], Projection: { ProjectionType: 'ALL' } }, { IndexName: 'CustomerOrdersIndex', KeySchema: [ { AttributeName: 'customerId', KeyType: 'HASH' }, { AttributeName: 'orderDate', KeyType: 'RANGE' }, { AttributeName: 'orderStatus', KeyType: 'RANGE' } ], Projection: { ProjectionType: 'ALL' } } ], BillingMode: 'PAY_PER_REQUEST' } // SellerRegionIndex GSI queries: // - Orders by seller and region // - Orders by seller, region, and date // - Orders by seller, region, date, and category // CustomerOrdersIndex GSI queries: // - Customer's orders // - Customer's orders by date // - Customer's orders by date and status

Données d'organisation hiérarchiques

Hiérarchies organisationnelles modélisées

{ TableName: 'Employees', // Base table: Simple partition key KeySchema: [ { AttributeName: 'employeeId', KeyType: 'HASH' } ], AttributeDefinitions: [ { AttributeName: 'employeeId', AttributeType: 'S' }, { AttributeName: 'companyId', AttributeType: 'S' }, { AttributeName: 'divisionId', AttributeType: 'S' }, { AttributeName: 'departmentId', AttributeType: 'S' }, { AttributeName: 'teamId', AttributeType: 'S' }, { AttributeName: 'skillCategory', AttributeType: 'S' }, { AttributeName: 'skillLevel', AttributeType: 'S' }, { AttributeName: 'yearsExperience', AttributeType: 'N' } ], GlobalSecondaryIndexes: [ { IndexName: 'OrganizationIndex', KeySchema: [ { AttributeName: 'companyId', KeyType: 'HASH' }, { AttributeName: 'divisionId', KeyType: 'HASH' }, { AttributeName: 'departmentId', KeyType: 'RANGE' }, { AttributeName: 'teamId', KeyType: 'RANGE' }, { AttributeName: 'employeeId', KeyType: 'RANGE' } ], Projection: { ProjectionType: 'ALL' } }, { IndexName: 'SkillsIndex', KeySchema: [ { AttributeName: 'skillCategory', KeyType: 'HASH' }, { AttributeName: 'skillLevel', KeyType: 'RANGE' }, { AttributeName: 'yearsExperience', KeyType: 'RANGE' } ], Projection: { ProjectionType: 'INCLUDE', NonKeyAttributes: ['employeeId', 'name'] } } ], BillingMode: 'PAY_PER_REQUEST' } // OrganizationIndex GSI query patterns: // - All employees in company/division // - Employees in specific department // - Employees in specific team // SkillsIndex GSI query patterns: // - Employees by skill and experience level

Clés à attributs multiples éparses

Combinez des clés à attributs multiples pour créer un GSI clairsemé

{ TableName: 'Products', // Base table: Simple partition key KeySchema: [ { AttributeName: 'productId', KeyType: 'HASH' } ], AttributeDefinitions: [ { AttributeName: 'productId', AttributeType: 'S' }, { AttributeName: 'categoryId', AttributeType: 'S' }, { AttributeName: 'subcategoryId', AttributeType: 'S' }, { AttributeName: 'averageRating', AttributeType: 'N' }, { AttributeName: 'reviewCount', AttributeType: 'N' } ], GlobalSecondaryIndexes: [ { IndexName: 'CategoryIndex', KeySchema: [ { AttributeName: 'categoryId', KeyType: 'HASH' }, { AttributeName: 'subcategoryId', KeyType: 'HASH' }, { AttributeName: 'productId', KeyType: 'RANGE' } ], Projection: { ProjectionType: 'ALL' } }, { IndexName: 'ReviewedProductsIndex', KeySchema: [ { AttributeName: 'categoryId', KeyType: 'HASH' }, { AttributeName: 'averageRating', KeyType: 'RANGE' }, // Optional attribute { AttributeName: 'reviewCount', KeyType: 'RANGE' } // Optional attribute ], Projection: { ProjectionType: 'ALL' } } ], BillingMode: 'PAY_PER_REQUEST' } // Only products with reviews appear in ReviewedProductsIndex GSI // Automatic filtering without application logic // Multi-attribute sort key enables rating and count queries

SaaS multi-tenant

Plateforme SaaS multi-locataires avec isolation du client

// Table design { TableName: 'SaasData', // Base table: Simple partition key KeySchema: [ { AttributeName: 'resourceId', KeyType: 'HASH' } ], AttributeDefinitions: [ { AttributeName: 'resourceId', AttributeType: 'S' }, { AttributeName: 'tenantId', AttributeType: 'S' }, { AttributeName: 'customerId', AttributeType: 'S' }, { AttributeName: 'resourceType', AttributeType: 'S' } ], // GSI with multi-attribute keys for tenant-customer isolation GlobalSecondaryIndexes: [{ IndexName: 'TenantCustomerIndex', KeySchema: [ { AttributeName: 'tenantId', KeyType: 'HASH' }, { AttributeName: 'customerId', KeyType: 'HASH' }, { AttributeName: 'resourceType', KeyType: 'RANGE' }, { AttributeName: 'resourceId', KeyType: 'RANGE' } ], Projection: { ProjectionType: 'ALL' } }], BillingMode: 'PAY_PER_REQUEST' } // Query GSI: All resources for tenant T001, customer C001 const resources = await docClient.send(new QueryCommand({ TableName: 'SaasData', IndexName: 'TenantCustomerIndex', KeyConditionExpression: 'tenantId = :tenant AND customerId = :customer', ExpressionAttributeValues: { ':tenant': 'T001', ':customer': 'C001' } })); // Query GSI: Specific resource type for tenant/customer const documents = await docClient.send(new QueryCommand({ TableName: 'SaasData', IndexName: 'TenantCustomerIndex', KeyConditionExpression: 'tenantId = :tenant AND customerId = :customer AND resourceType = :type', ExpressionAttributeValues: { ':tenant': 'T001', ':customer': 'C001', ':type': 'document' } }));

Avantages : requêtes efficaces dans le contexte locataire-client et organisation naturelle des données.

Transactions financières

Le système bancaire suit les transactions du compte en utilisant GSIs

// Table design { TableName: 'BankTransactions', // Base table: Simple partition key KeySchema: [ { AttributeName: 'transactionId', KeyType: 'HASH' } ], AttributeDefinitions: [ { AttributeName: 'transactionId', AttributeType: 'S' }, { AttributeName: 'accountId', AttributeType: 'S' }, { AttributeName: 'year', AttributeType: 'S' }, { AttributeName: 'month', AttributeType: 'S' }, { AttributeName: 'day', AttributeType: 'S' }, { AttributeName: 'transactionType', AttributeType: 'S' } ], GlobalSecondaryIndexes: [ { IndexName: 'AccountTimeIndex', KeySchema: [ { AttributeName: 'accountId', KeyType: 'HASH' }, { AttributeName: 'year', KeyType: 'RANGE' }, { AttributeName: 'month', KeyType: 'RANGE' }, { AttributeName: 'day', KeyType: 'RANGE' }, { AttributeName: 'transactionId', KeyType: 'RANGE' } ], Projection: { ProjectionType: 'ALL' } }, { IndexName: 'TransactionTypeIndex', KeySchema: [ { AttributeName: 'accountId', KeyType: 'HASH' }, { AttributeName: 'transactionType', KeyType: 'RANGE' }, { AttributeName: 'year', KeyType: 'RANGE' }, { AttributeName: 'month', KeyType: 'RANGE' } ], Projection: { ProjectionType: 'ALL' } } ], BillingMode: 'PAY_PER_REQUEST' } // Query AccountTimeIndex GSI: All transactions for account in 2023 const yearTransactions = await docClient.send(new QueryCommand({ TableName: 'BankTransactions', IndexName: 'AccountTimeIndex', KeyConditionExpression: 'accountId = :account AND #year = :year', ExpressionAttributeNames: { '#year': 'year' }, ExpressionAttributeValues: { ':account': 'ACC-12345', ':year': '2023' } })); // Query AccountTimeIndex GSI: Transactions in specific month const monthTransactions = await docClient.send(new QueryCommand({ TableName: 'BankTransactions', IndexName: 'AccountTimeIndex', KeyConditionExpression: 'accountId = :account AND #year = :year AND #month = :month', ExpressionAttributeNames: { '#year': 'year', '#month': 'month' }, ExpressionAttributeValues: { ':account': 'ACC-12345', ':year': '2023', ':month': '11' } })); // Query TransactionTypeIndex GSI: Deposits in 2023 const deposits = await docClient.send(new QueryCommand({ TableName: 'BankTransactions', IndexName: 'TransactionTypeIndex', KeyConditionExpression: 'accountId = :account AND transactionType = :type AND #year = :year', ExpressionAttributeNames: { '#year': 'year' }, ExpressionAttributeValues: { ':account': 'ACC-12345', ':type': 'deposit', ':year': '2023' } }));

Exemple complet

L'exemple suivant illustre les clés à attributs multiples, de la configuration au nettoyage :

import { DynamoDBClient, CreateTableCommand, DeleteTableCommand, waitUntilTableExists } from "@aws-sdk/client-dynamodb"; import { DynamoDBDocumentClient, PutCommand, QueryCommand } from "@aws-sdk/lib-dynamodb"; const client = new DynamoDBClient({ region: 'us-west-2' }); const docClient = DynamoDBDocumentClient.from(client); async function multiAttributeKeysDemo() { console.log("Starting Multi-Attribute GSI Keys Demo\n"); // Step 1: Create table with GSIs using multi-attribute keys console.log("1. Creating table with multi-attribute GSI keys..."); await client.send(new CreateTableCommand({ TableName: 'TournamentMatches', KeySchema: [ { AttributeName: 'matchId', KeyType: 'HASH' } ], AttributeDefinitions: [ { AttributeName: 'matchId', AttributeType: 'S' }, { AttributeName: 'tournamentId', AttributeType: 'S' }, { AttributeName: 'region', AttributeType: 'S' }, { AttributeName: 'round', AttributeType: 'S' }, { AttributeName: 'bracket', AttributeType: 'S' }, { AttributeName: 'player1Id', AttributeType: 'S' }, { AttributeName: 'matchDate', AttributeType: 'S' } ], GlobalSecondaryIndexes: [ { IndexName: 'TournamentRegionIndex', KeySchema: [ { AttributeName: 'tournamentId', KeyType: 'HASH' }, { AttributeName: 'region', KeyType: 'HASH' }, { AttributeName: 'round', KeyType: 'RANGE' }, { AttributeName: 'bracket', KeyType: 'RANGE' }, { AttributeName: 'matchId', KeyType: 'RANGE' } ], Projection: { ProjectionType: 'ALL' } }, { IndexName: 'PlayerMatchHistoryIndex', KeySchema: [ { AttributeName: 'player1Id', KeyType: 'HASH' }, { AttributeName: 'matchDate', KeyType: 'RANGE' }, { AttributeName: 'round', KeyType: 'RANGE' } ], Projection: { ProjectionType: 'ALL' } } ], BillingMode: 'PAY_PER_REQUEST' })); await waitUntilTableExists({ client, maxWaitTime: 120 }, { TableName: 'TournamentMatches' }); console.log("Table created\n"); // Step 2: Insert tournament matches console.log("2. Inserting tournament matches..."); const matches = [ { matchId: 'match-001', tournamentId: 'WINTER2024', region: 'NA-EAST', round: 'FINALS', bracket: 'CHAMPIONSHIP', player1Id: '101', player2Id: '103', matchDate: '2024-01-20', winner: '101', score: '3-1' }, { matchId: 'match-002', tournamentId: 'WINTER2024', region: 'NA-EAST', round: 'SEMIFINALS', bracket: 'UPPER', player1Id: '101', player2Id: '105', matchDate: '2024-01-18', winner: '101', score: '3-2' }, { matchId: 'match-003', tournamentId: 'WINTER2024', region: 'NA-WEST', round: 'FINALS', bracket: 'CHAMPIONSHIP', player1Id: '102', player2Id: '104', matchDate: '2024-01-20', winner: '102', score: '3-2' }, { matchId: 'match-004', tournamentId: 'SPRING2024', region: 'NA-EAST', round: 'QUARTERFINALS', bracket: 'UPPER', player1Id: '101', player2Id: '108', matchDate: '2024-03-15', winner: '101', score: '3-0' } ]; for (const match of matches) { await docClient.send(new PutCommand({ TableName: 'TournamentMatches', Item: match })); } console.log(`Inserted ${matches.length} tournament matches\n`); // Step 3: Query GSI with multi-attribute partition key console.log("3. Query TournamentRegionIndex GSI: WINTER2024/NA-EAST matches"); const gsiQuery1 = await docClient.send(new QueryCommand({ TableName: 'TournamentMatches', IndexName: 'TournamentRegionIndex', KeyConditionExpression: 'tournamentId = :tournament AND #region = :region', ExpressionAttributeNames: { '#region': 'region' }, ExpressionAttributeValues: { ':tournament': 'WINTER2024', ':region': 'NA-EAST' } })); console.log(` Found ${gsiQuery1.Items.length} matches:`); gsiQuery1.Items.forEach(match => { console.log(` ${match.round} - ${match.bracket} - ${match.winner} won`); }); // Step 4: Query GSI with multi-attribute sort key console.log("\n4. Query PlayerMatchHistoryIndex GSI: All matches for Player 101"); const gsiQuery2 = await docClient.send(new QueryCommand({ TableName: 'TournamentMatches', IndexName: 'PlayerMatchHistoryIndex', KeyConditionExpression: 'player1Id = :player', ExpressionAttributeValues: { ':player': '101' } })); console.log(` Found ${gsiQuery2.Items.length} matches for Player 101:`); gsiQuery2.Items.forEach(match => { console.log(` ${match.tournamentId}/${match.region} - ${match.matchDate} - ${match.round}`); }); console.log("\nDemo complete"); console.log("No synthetic keys needed - GSIs use native attributes automatically"); } async function cleanup() { console.log("Deleting table..."); await client.send(new DeleteTableCommand({ TableName: 'TournamentMatches' })); console.log("Table deleted"); } // Run demo multiAttributeKeysDemo().catch(console.error); // Uncomment to cleanup: // cleanup().catch(console.error);

Échafaudage de code minimal

// 1. Create table with GSI using multi-attribute keys await client.send(new CreateTableCommand({ TableName: 'MyTable', KeySchema: [ { AttributeName: 'id', KeyType: 'HASH' } // Simple base table PK ], AttributeDefinitions: [ { AttributeName: 'id', AttributeType: 'S' }, { AttributeName: 'attr1', AttributeType: 'S' }, { AttributeName: 'attr2', AttributeType: 'S' }, { AttributeName: 'attr3', AttributeType: 'S' }, { AttributeName: 'attr4', AttributeType: 'S' } ], GlobalSecondaryIndexes: [{ IndexName: 'MyGSI', KeySchema: [ { AttributeName: 'attr1', KeyType: 'HASH' }, // GSI PK attribute 1 { AttributeName: 'attr2', KeyType: 'HASH' }, // GSI PK attribute 2 { AttributeName: 'attr3', KeyType: 'RANGE' }, // GSI SK attribute 1 { AttributeName: 'attr4', KeyType: 'RANGE' } // GSI SK attribute 2 ], Projection: { ProjectionType: 'ALL' } }], BillingMode: 'PAY_PER_REQUEST' })); // 2. Insert items with native attributes (no concatenation needed for GSI) await docClient.send(new PutCommand({ TableName: 'MyTable', Item: { id: 'item-001', attr1: 'value1', attr2: 'value2', attr3: 'value3', attr4: 'value4', // ... other attributes } })); // 3. Query GSI with all partition key attributes await docClient.send(new QueryCommand({ TableName: 'MyTable', IndexName: 'MyGSI', KeyConditionExpression: 'attr1 = :v1 AND attr2 = :v2', ExpressionAttributeValues: { ':v1': 'value1', ':v2': 'value2' } })); // 4. Query GSI with sort key attributes (left-to-right) await docClient.send(new QueryCommand({ TableName: 'MyTable', IndexName: 'MyGSI', KeyConditionExpression: 'attr1 = :v1 AND attr2 = :v2 AND attr3 = :v3', ExpressionAttributeValues: { ':v1': 'value1', ':v2': 'value2', ':v3': 'value3' } })); // Note: If any attribute name is a DynamoDB reserved keyword, use ExpressionAttributeNames: // KeyConditionExpression: 'attr1 = :v1 AND #attr2 = :v2' // ExpressionAttributeNames: { '#attr2': 'attr2' }

Ressources supplémentaires