

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à.

# Modelli di progettazione
<a name="GSI.DesignPatterns"></a>

I modelli di progettazione forniscono soluzioni comprovate alle sfide più comuni quando si lavora con indici secondari globali. Questi modelli consentono di creare applicazioni efficienti e scalabili mostrando come strutturare gli indici per casi d'uso specifici.

Ogni modello include una guida all'implementazione completa con esempi di codice, best practice e casi d'uso reali per aiutarvi ad applicare il pattern alle vostre applicazioni.

**Topics**
+ [Chiavi con più attributi](GSI.DesignPattern.MultiAttributeKeys.md)

# Schema di chiavi multiattributo
<a name="GSI.DesignPattern.MultiAttributeKeys"></a>

## Panoramica
<a name="GSI.DesignPattern.MultiAttributeKeys.Overview"></a>

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\$1 WINTER2 024 \$1REGION \$1NA -EAST e una chiave di ordinamento sintetica come ROUND \$1SEMIFINALS \$1BRACKET \$1UPPER. 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
<a name="GSI.DesignPattern.MultiAttributeKeys.ApplicationExample"></a>

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
<a name="GSI.DesignPattern.MultiAttributeKeys.DataModel"></a>

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 `matchId` chiave 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`\$1`region`) e chiave di ordinamento multiattributo (\$1) `round` `bracket` `matchId`
+ **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 (\$1) `matchDate` `round`
+ **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: `round``bracket`,, `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
<a name="GSI.DesignPattern.MultiAttributeKeys.BaseTable"></a>


| 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)
<a name="GSI.DesignPattern.MultiAttributeKeys.TournamentRegionIndexTable"></a>


| 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)
<a name="GSI.DesignPattern.MultiAttributeKeys.PlayerMatchHistoryIndexTable"></a>


| 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
<a name="GSI.DesignPattern.MultiAttributeKeys.Prerequisites"></a>

Prima di iniziare, assicurati di disporre dei seguenti elementi:

### Account e autorizzazioni
<a name="GSI.DesignPattern.MultiAttributeKeys.Prerequisites.AWSAccount"></a>
+ Un AWS account attivo ([creane uno qui](https://aws.amazon.com/free/) se necessario)
+ Autorizzazioni IAM per le operazioni DynamoDB:
  + `dynamodb:CreateTable`
  + `dynamodb:DeleteTable`
  + `dynamodb:DescribeTable`
  + `dynamodb:PutItem`
  + `dynamodb:Query`
  + `dynamodb: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
<a name="GSI.DesignPattern.MultiAttributeKeys.Prerequisites.DevEnvironment"></a>
+ 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
<a name="GSI.DesignPattern.MultiAttributeKeys.Prerequisites.InstallPackages"></a>

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

## Implementazione
<a name="GSI.DesignPattern.MultiAttributeKeys.Implementation"></a>

### Passaggio 1: creare una tabella GSIs utilizzando chiavi multiattributo
<a name="GSI.DesignPattern.MultiAttributeKeys.CreateTable"></a>

Crea una tabella con una struttura chiave di base semplice e GSIs che utilizzi chiavi multiattributo.

#### esempio di codice
<a name="w2aac19c13c45c23b9c11b3b5b1"></a>

```
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` \$1 `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`\$1 `bracket` \$1`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`\$1`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
<a name="GSI.DesignPattern.MultiAttributeKeys.InsertData"></a>

Aggiungi i dati delle partite del torneo utilizzando attributi naturali. Il GSI indicizzerà automaticamente questi attributi senza richiedere chiavi sintetiche.

#### esempio di codice
<a name="w2aac19c13c45c23b9c11b5b5b1"></a>

```
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` `player1Id``matchDate`, `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
<a name="GSI.DesignPattern.MultiAttributeKeys.QueryAllPartitionKeys"></a>

Questo esempio interroga il TournamentRegionIndex Global Secondary Index che ha una chiave di partizione multiattributo (\$1). `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.

#### esempio di codice
<a name="w2aac19c13c45c23b9c11b7b5b1"></a>

```
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
<a name="GSI.DesignPattern.MultiAttributeKeys.QuerySortKeysLeftToRight"></a>

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 just`round`, per `round` \$1 `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`

#### esempio di codice
<a name="w2aac19c13c45c23b9c11b9b5b1"></a>

```
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
<a name="GSI.DesignPattern.MultiAttributeKeys.InequalityConditions"></a>

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.

#### esempio di codice
<a name="w2aac19c13c45c23b9c11c11b5b1"></a>

```
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
<a name="GSI.DesignPattern.MultiAttributeKeys.QueryPlayerHistory"></a>

Questo esempio interroga il PlayerMatchHistoryIndex che ha una chiave di partizione singola (`player1Id`) e una chiave di ordinamento con più attributi (\$1). `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.

#### esempio di codice
<a name="w2aac19c13c45c23b9c11c13b5b1"></a>

```
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
<a name="GSI.DesignPattern.MultiAttributeKeys.PatternVariations"></a>

### Dati di serie temporali con chiavi multiattributo
<a name="GSI.DesignPattern.MultiAttributeKeys.TimeSeries"></a>

Ottimizzazione per le query su serie temporali con attributi temporali gerarchici

#### esempio di codice
<a name="w2aac19c13c45c23b9c13b3b5b1"></a>

```
{
    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
<a name="GSI.DesignPattern.MultiAttributeKeys.ECommerce"></a>

Tieni traccia degli ordini con più dimensioni

#### esempio di codice
<a name="w2aac19c13c45c23b9c13b5b5b1"></a>

```
{
    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
<a name="GSI.DesignPattern.MultiAttributeKeys.Hierarchical"></a>

Gerarchie organizzative modello

#### esempio di codice
<a name="w2aac19c13c45c23b9c13b7b5b1"></a>

```
{
    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
<a name="GSI.DesignPattern.MultiAttributeKeys.Sparse"></a>

Combina chiavi multiattributo per creare un GSI sparso

#### esempio di codice
<a name="w2aac19c13c45c23b9c13b9b5b1"></a>

```
{
    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
<a name="GSI.DesignPattern.MultiAttributeKeys.SaaS"></a>

Piattaforma SaaS multi-tenant con isolamento del cliente

#### esempio di codice
<a name="w2aac19c13c45c23b9c13c11b5b1"></a>

```
// 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
<a name="GSI.DesignPattern.MultiAttributeKeys.Financial"></a>

Sistema bancario che monitora le transazioni del conto utilizzando GSIs

#### esempio di codice
<a name="w2aac19c13c45c23b9c13c13b5b1"></a>

```
// 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
<a name="GSI.DesignPattern.MultiAttributeKeys.CompleteExample"></a>

L'esempio seguente illustra le chiavi con più attributi dalla configurazione alla pulizia:

### esempio di codice
<a name="w2aac19c13c45c23b9c15b5b1"></a>

```
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**

### esempio di codice
<a name="w2aac19c13c45c23b9c15b9b1"></a>

```
// 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' }
```

## Risorse aggiuntive
<a name="GSI.DesignPattern.MultiAttributeKeys.AdditionalResources"></a>
+ [Best practice per DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/best-practices.html)
+ [Lavorare con tabelle e dati](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithTables.html)
+ [Indici secondari globali](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html)
+ [Operazioni di interrogazione e scansione](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html)