Le traduzioni sono generate tramite traduzione automatica. In caso di conflitto tra il contenuto di una traduzione e la versione originale in Inglese, quest'ultima prevarrà.
Schema di chiavi multiattributo
Panoramica
Le chiavi multi-attributo consentono di creare chiavi di partizione e ordinamento del Global Secondary Index (GSI) composte da un massimo di quattro attributi ciascuna. Ciò riduce il codice lato client e semplifica la modellazione iniziale dei dati e l'aggiunta di nuovi modelli di accesso in un secondo momento.
Consideriamo uno scenario comune: per creare un GSI che interroghi gli elementi in base a più attributi gerarchici, tradizionalmente è necessario creare chiavi sintetiche concatenando i valori. Ad esempio, in un'app di gioco, per interrogare le partite dei tornei per torneo, regione e round, potresti creare una chiave di partizione GSI sintetica come TOURNAMENT# WINTER2 024 #REGION #NA -EAST e una chiave di ordinamento sintetica come ROUND #SEMIFINALS #BRACKET #UPPER. Questo approccio funziona, ma richiede la concatenazione di stringhe durante la scrittura dei dati, l'analisi durante la lettura e il riempimento delle chiavi sintetiche su tutti gli elementi esistenti se si aggiunge il GSI a una tabella esistente. Ciò rende il codice più disordinato e difficile da mantenere la sicurezza dei tipi sui singoli componenti chiave.
Le chiavi multiattributo risolvono questo problema per. GSIs Definisci la tua chiave di partizione GSI utilizzando più attributi esistenti come TournamentID e region. DynamoDB gestisce automaticamente la logica delle chiavi composite, eseguendo l'hashing delle stesse per la distribuzione dei dati. Gli elementi vengono scritti utilizzando gli attributi naturali del modello di dominio e il GSI li indicizza automaticamente. Nessuna concatenazione, nessuna analisi, nessun riempimento. Il codice rimane pulito, i dati rimangono digitati e le query rimangono semplici. Questo approccio è particolarmente utile quando si hanno dati gerarchici con raggruppamenti di attributi naturali (come torneo → regione → round o organizzazione → dipartimento → squadra).
Esempio di applicazione
Questa guida illustra la creazione di un sistema di tracciamento delle partite di torneo per una piattaforma di eSport. La piattaforma deve interrogare in modo efficiente le partite su più fronti: per torneo e regione per la gestione dei gironi, per giocatore per la cronologia delle partite e per data di programmazione.
Modello di dati
In questa procedura dettagliata, il sistema di tracciamento delle partite dei tornei supporta tre modelli di accesso principali, ognuno dei quali richiede una struttura chiave diversa:
Schema di accesso 1: cerca una partita specifica in base al relativo ID univoco
-
Soluzione: tabella di base con
matchIdchiave di partizione
Schema di accesso 2: interroga tutte le partite di un torneo e di una regione specifici, filtrandole facoltativamente per round, tabellone o partita
-
Soluzione: Indice secondario globale con chiave di partizione multiattributo (
tournamentId+region) e chiave di ordinamento multiattributo (+)roundbracketmatchId -
Query di esempio: «Tutte le partite WINTER2 024 nella regione NA-EST» o «Tutte le SEMIFINALI corrispondono nel girone SUPERIORE per 024/NA-EST» WINTER2
Schema di accesso 3: interroga la cronologia delle partite di un giocatore, filtrandola facoltativamente per intervallo di date o round del torneo
-
Soluzione: Indice secondario globale con chiave di partizione singola (
player1Id) e chiave di ordinamento multiattributo (+)matchDateround -
Domande di esempio: «Tutte le partite del giocatore 101" o «Le partite del giocatore 101 a gennaio 2024"
La differenza fondamentale tra gli approcci tradizionali e quelli con più attributi diventa evidente quando si esamina la struttura degli elementi:
Approccio tradizionale all'indice secondario globale (chiavi concatenate):
// 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 };
Approccio all'indice secondario globale multiattributo (chiavi native):
// 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 };
Con le chiavi multiattributo, gli elementi vengono scritti una sola volta con attributi di dominio naturali. DynamoDB li indicizza automaticamente su GSIs più pagine senza richiedere chiavi sintetiche concatenate.
Schema della tabella di base:
-
Chiave di partizione:
matchId(1 attributo)
Schema dell'indice secondario globale (TournamentRegionIndex con chiavi multiattributo):
-
Chiave di partizione:
tournamentId,region(2 attributi) -
Chiave di ordinamento:
roundbracket,,matchId(3 attributi)
Schema dell'indice secondario globale (PlayerMatchHistoryIndex con chiavi multiattributo):
-
Chiave di partizione:
player1Id(1 attributo) -
Chiave di ordinamento:
matchDate,round(2 attributi)
Tabella base: TournamentMatches
| MatchID (PK) | ID del torneo | region | round | staffa | ID giocatore 1 | ID giocatore 2 | Data della partita | vincitore | punteggio |
|---|---|---|---|---|---|---|---|---|---|
| partita-001 | WINTER2024 | NA-EST | FINALE | CAMPIONATO | 101 | 103 | 2024-01-20 | 101 | 3-1 |
| inquadrato-002 | WINTER2024 | NA-EST | SEMIFINALI | UPPER | 101 | 105 | 2024-01-18 | 101 | 3-2 |
| inquadrato-003 | WINTER2024 | NA-EST | SEMIFINALI | UPPER | 103 | 107 | 2024-01-18 | 103 | 3-0 |
| inquadrato-004 | WINTER2024 | NA-EST | QUARTI DI FINALE | UPPER | 101 | 109 | 2024-01-15 | 101 | 3-1 |
| inquadrato-005 | WINTER2024 | NORD-OVEST | FINALE | CAMPIONATO | 102 | 104 | 2024-01-20 | 102 | 3-2 |
| inquadrato-006 | WINTER2024 | NORD-OVEST | SEMIFINALI | UPPER | 102 | 106 | 2024-01-18 | 102 | 3-1 |
| partita-007 | SPRING2024 | NA-EST | QUARTI DI FINALE | UPPER | 101 | 108 | 2024-03-15 | 101 | 3-0 |
| inquadrato-008 | SPRING2024 | NA-EST | QUARTI DI FINALE | LOWER | 103 | 110 | 2024-03-15 | 103 | 3-2 |
GSI: TournamentRegionIndex (chiavi multiattributo)
| ID torneo (PK) | regione (PK) | rotondo (SK) | staffa (SK) | MatchID (SK) | ID giocatore 1 | ID giocatore 2 | Data della partita | vincitore | punteggio |
|---|---|---|---|---|---|---|---|---|---|
| WINTER2024 | NA-EST | FINALE | CAMPIONATO | partita-001 | 101 | 103 | 2024-01-20 | 101 | 3-1 |
| WINTER2024 | NA-EST | QUARTI DI FINALE | UPPER | match-004 | 101 | 109 | 2024-01-15 | 101 | 3-1 |
| WINTER2024 | NA-EST | SEMIFINALI | UPPER | partita-002 | 101 | 105 | 2024-01-18 | 101 | 3-2 |
| WINTER2024 | NA-EST | SEMIFINALI | UPPER | match-003 | 103 | 107 | 2024-01-18 | 103 | 3-0 |
| WINTER2024 | NORD-OVEST | FINALE | CAMPIONATO | partita-005 | 102 | 104 | 2024-01-20 | 102 | 3-2 |
| WINTER2024 | NORD-OVEST | SEMIFINALI | UPPER | partita-006 | 102 | 106 | 2024-01-18 | 102 | 3-1 |
| SPRING2024 | NA-EST | QUARTI DI FINALE | LOWER | partita-008 | 103 | 110 | 2024-03-15 | 103 | 3-2 |
| SPRING2024 | NA-EST | QUARTI DI FINALE | UPPER | match-007 | 101 | 108 | 2024-03-15 | 101 | 3-0 |
GSI: PlayerMatchHistoryIndex (chiavi multiattributo)
| Player1ID (PK) | MatchDate (SK) | rotondo (SK) | ID del torneo | region | staffa | MatchID | ID giocatore 2 | vincitore | punteggio |
|---|---|---|---|---|---|---|---|---|---|
| 101 | 2024-01-15 | QUARTI DI FINALE | WINTER2024 | NA-EST | UPPER | partita-004 | 109 | 101 | 3-1 |
| 101 | 2024-01-18 | SEMIFINALI | WINTER2024 | NA-EST | UPPER | partita-002 | 105 | 101 | 3-2 |
| 101 | 2024-01-20 | FINALE | WINTER2024 | NA-EST | CAMPIONATO | partita-001 | 103 | 101 | 3-1 |
| 101 | 2024-03-15 | QUARTI DI FINALE | SPRING2024 | NA-EST | UPPER | partita-007 | 108 | 101 | 3-0 |
| 102 | 2024-01-18 | SEMIFINALI | WINTER2024 | NORD-OVEST | UPPER | incontro 006 | 106 | 102 | 3-1 |
| 102 | 2024-01-20 | FINALE | WINTER2024 | NORD-OVEST | CAMPIONATO | partita-005 | 104 | 102 | 3-2 |
| 103 | 2024-01-18 | SEMIFINALI | WINTER2024 | NA-EST | UPPER | partita-003 | 107 | 103 | 3-0 |
| 103 | 2024-03-15 | QUARTI DI FINALE | SPRING2024 | NA-EST | LOWER | partita-008 | 110 | 103 | 3-2 |
Prerequisiti
Prima di iniziare, assicurati di disporre dei seguenti elementi:
Account e autorizzazioni
-
Un AWS account attivo (creane uno qui
se necessario) -
Autorizzazioni IAM per le operazioni DynamoDB:
dynamodb:CreateTabledynamodb:DeleteTabledynamodb:DescribeTabledynamodb:PutItemdynamodb:Querydynamodb:BatchWriteItem
Nota
Nota di sicurezza: per l'uso in produzione, crea una policy IAM personalizzata con solo le autorizzazioni necessarie. Per questo tutorial, puoi utilizzare la policy AWS AmazonDynamoDBFullAccessV2 gestita.
Ambiente di sviluppo
-
Node.js installato sul tuo computer
-
AWS credenziali configurate utilizzando uno di questi metodi:
Opzione 1: AWS CLI
aws configure
Opzione 2: variabili di ambiente
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
Installazione dei pacchetti richiesti
npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
Implementazione
Passaggio 1: creare una tabella GSIs utilizzando chiavi multiattributo
Crea una tabella con una struttura chiave di base semplice e GSIs che utilizzi chiavi multiattributo.
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");
Principali decisioni di progettazione:
Tabella base: la tabella base utilizza una semplice chiave di matchId partizione per le ricerche dirette, mantenendo la struttura della tabella di base semplice e GSIs fornendo al contempo modelli di interrogazione complessi.
TournamentRegionIndex Indice secondario globale: l'indice secondario TournamentRegionIndex globale utilizza tournamentId + region come chiave di partizione multiattributo, creando un isolamento tra regione e torneo in cui i dati vengono distribuiti combinando l'hash di entrambi gli attributi, permettendo di eseguire query efficienti all'interno di un contesto specifico tra torneo e regione. La chiave di ordinamento multiattributo (round+ bracket +matchId) fornisce un ordinamento gerarchico che supporta le query a qualsiasi livello della gerarchia con un ordinamento naturale da generale (round) a specifico (match ID).
PlayerMatchHistoryIndex Indice secondario globale: il PlayerMatchHistoryIndex Global Secondary Index riorganizza i dati per giocatore utilizzandoli player1Id come chiave di partizione, abilitando le interrogazioni tra tornei per un giocatore specifico. La chiave di ordinamento multiattributo (matchDate+round) fornisce un ordine cronologico con la possibilità di filtrare per intervalli di date o turni di torneo specifici.
Fase 2: Inserimento di dati con attributi nativi
Aggiungi i dati delle partite del torneo utilizzando attributi naturali. Il GSI indicizzerà automaticamente questi attributi senza richiedere chiavi sintetiche.
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");
Struttura dei dati spiegata:
Utilizzo degli attributi naturali: ogni attributo rappresenta un vero concetto di torneo senza necessità di concatenazione o analisi di stringhe, e fornisce una mappatura diretta al modello di dominio.
Indicizzazione automatica dell'indice secondario globale: indicizza GSIs automaticamente gli elementi utilizzando gli attributi esistenti (tournamentId,,,, matchId for TournamentRegionIndex e region round bracket player1IdmatchDate, round for PlayerMatchHistoryIndex) senza richiedere chiavi concatenate sintetiche.
Non è necessario il backfill: quando aggiungi un nuovo indice secondario globale con chiavi multiattributo a una tabella esistente, DynamoDB indicizza automaticamente tutti gli elementi esistenti utilizzando i loro attributi naturali, senza bisogno di aggiornare gli elementi con chiavi sintetiche.
Fase 3: Interroga l'indice secondario globale con tutti gli attributi delle chiavi di partizione TournamentRegionIndex
Questo esempio interroga il TournamentRegionIndex Global Secondary Index che ha una chiave di partizione multiattributo (+). tournamentId region Tutti gli attributi delle chiavi di partizione devono essere specificati con condizioni di uguaglianza nelle query: non è possibile eseguire query tournamentId solo con o utilizzare operatori di disuguaglianza sugli attributi delle chiavi di partizione.
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`); });
Output previsto:
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
Interrogazioni non valide:
// Missing region attribute KeyConditionExpression: 'tournamentId = :tournament' // Using inequality on partition key attribute KeyConditionExpression: 'tournamentId = :tournament AND #region > :region'
Prestazioni: le chiavi di partizione con più attributi vengono codificate insieme, fornendo le stesse prestazioni di ricerca O (1) delle chiavi ad attributo singolo.
Fase 4: Interroga le chiavi di ordinamento dell'indice secondario globale left-to-right
Gli attributi delle chiavi di ordinamento devono essere interrogati left-to-right nell'ordine in cui sono definiti nell'indice secondario globale. Questo esempio dimostra l'esecuzione di interrogazioni TournamentRegionIndex a diversi livelli gerarchici: filtraggio per justround, per round + bracket o per tutti e tre gli attributi chiave di ordinamento. Non è possibile saltare gli attributi al centro, ad esempio non è possibile eseguire una query per e mentre si salta. round matchId 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`); }
Output previsto:
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 regole di interrogazione: è necessario interrogare gli attributi in ordine da sinistra a destra, senza saltarne nessuno.
Modelli validi:
Solo primo attributo:
round = 'SEMIFINALS'Primi due attributi:
round = 'SEMIFINALS' AND bracket = 'UPPER'Tutti e tre gli attributi:
round = 'SEMIFINALS' AND bracket = 'UPPER' AND matchId = 'match-002'
Schemi non validi:
Il primo attributo viene ignorato:
bracket = 'UPPER'(salta il giro)Interrogazione non corretta:
matchId = 'match-002' AND round = 'SEMIFINALS'Lasciare spazi vuoti:
round = 'SEMIFINALS' AND matchId = 'match-002'(salta la parentesi)
Nota
Suggerimento di progettazione: ordina gli attributi chiave di ordinamento dal più generale al più specifico per massimizzare la flessibilità delle query.
Fase 5: Utilizza le condizioni di disuguaglianza nelle chiavi di ordinamento dell'Indice secondario globale
Le condizioni di disuguaglianza devono essere l'ultima condizione della ricerca. Questo esempio dimostra l'utilizzo degli operatori di confronto (>=,BETWEEN) e del prefisso matching (begins_with()) sugli attributi delle chiavi di ordinamento. Una volta utilizzato un operatore di disuguaglianza, non è possibile aggiungere ulteriori condizioni di chiave di ordinamento dopo di esso: la disuguaglianza deve essere l'ultima condizione nell'espressione della condizione chiave.
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`); }
Regole degli operatori di disuguaglianza: è possibile utilizzare gli operatori di confronto (>,,,<=) >=<, BETWEEN per le interrogazioni sugli intervalli e per la corrispondenza dei prefissi. begins_with() La disuguaglianza deve essere l'ultima condizione della query.
Schemi validi:
Condizioni di uguaglianza seguite dalla disuguaglianza:
round = 'SEMIFINALS' AND bracket = 'UPPER' AND matchId > 'match-001'Disuguaglianza sul primo attributo:
round BETWEEN 'QUARTERFINALS' AND 'SEMIFINALS'Corrispondenza del prefisso come condizione finale:
round = 'SEMIFINALS' AND begins_with(bracket, 'U')
Schemi non validi:
Aggiungere condizioni dopo una disuguaglianza:
round > 'QUARTERFINALS' AND bracket = 'UPPER'Utilizzo di più disuguaglianze:
round > 'QUARTERFINALS' AND bracket > 'L'
Importante
begins_with()viene considerata una condizione di disuguaglianza, quindi non può essere seguita da alcuna condizione di chiave di ordinamento aggiuntiva.
Fase 6: Interrogazione dell'indice secondario PlayerMatchHistoryIndex globale con una chiave di ordinamento multiattributo
Questo esempio interroga il PlayerMatchHistoryIndex che ha una chiave di partizione singola (player1Id) e una chiave di ordinamento con più attributi (+). matchDate round Ciò consente l'analisi tra tornei interrogando tutte le partite di un giocatore specifico senza conoscere il torneo, IDs mentre la tabella base richiederebbe interrogazioni separate per combinazione torneo-regione.
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`);
Variazioni del modello
Dati di serie temporali con chiavi multiattributo
Ottimizzazione per le query su serie temporali con attributi temporali gerarchici
{ 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
Vantaggi: la gerarchia temporale naturale (anno → mese → giorno → timestamp) consente una granularità delle query efficienti in qualsiasi momento senza analisi o manipolazione della data. L'indice secondario globale indicizza automaticamente tutte le letture utilizzando i loro attributi temporali naturali.
Ordini di e-commerce con chiavi multiattributo
Tieni traccia degli ordini con più dimensioni
{ 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
Dati organizzativi gerarchici
Gerarchie organizzative modello
{ 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
Chiavi sparse con più attributi
Combina chiavi multiattributo per creare un GSI sparso
{ 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
Multi-tenancy SaaS
Piattaforma SaaS multi-tenant con isolamento del cliente
// 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' } }));
Vantaggi: interrogazioni efficienti nel contesto tra inquilino e cliente e organizzazione naturale dei dati.
Transazioni finanziarie
Sistema bancario che monitora le transazioni del conto utilizzando 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' } }));
Esempio completo
L'esempio seguente illustra le chiavi con più attributi dalla configurazione alla pulizia:
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);
Scaffold di codice minimo
// 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' }