

Die vorliegende Übersetzung wurde maschinell erstellt. Im Falle eines Konflikts oder eines Widerspruchs zwischen dieser übersetzten Fassung und der englischen Fassung (einschließlich infolge von Verzögerungen bei der Übersetzung) ist die englische Fassung maßgeblich.

# Multi-attribute Tastenmuster
<a name="GSI.DesignPattern.MultiAttributeKeys"></a>

## -Übersicht
<a name="GSI.DesignPattern.MultiAttributeKeys.Overview"></a>

Multi-attribute Mithilfe von Schlüsseln können Sie eine GSI-Partition (Global Secondary Index) erstellen und Schlüssel sortieren, die jeweils aus bis zu vier Attributen bestehen. Dies reduziert den clientseitigen Code und erleichtert das anfängliche Modellieren von Daten und das Hinzufügen neuer Zugriffsmuster zu einem späteren Zeitpunkt.

Stellen Sie sich ein gängiges Szenario vor: Um eine GSI zu erstellen, die Elemente anhand mehrerer hierarchischer Attribute abfragt, müssten Sie traditionell synthetische Schlüssel erstellen, indem Sie Werte verketten. Um beispielsweise in einer Spiele-App Turnierspiele nach Turnier, Region und Runde abzufragen, könnten Sie einen synthetischen GSI-Partitionsschlüssel wie TOURNAMENT \#WINTER2024 \#REGION \# NA-EAST und einen synthetischen Sortierschlüssel wie ROUND \#SEMIFINALS \#BRACKET \#UPPER erstellen. Dieser Ansatz funktioniert, erfordert jedoch eine Verkettung von Zeichenketten beim Schreiben von Daten, das Analysieren beim Lesen und das Auffüllen synthetischer Schlüssel für alle vorhandenen Elemente, wenn Sie die GSI zu einer vorhandenen Tabelle hinzufügen. Dadurch wird der Code unübersichtlicher und es ist schwieriger, die Typsicherheit einzelner Schlüsselkomponenten aufrechtzuerhalten.

Multi-attribute Schlüssel lösen dieses Problem für GSIs. Sie definieren Ihren GSI-Partitionsschlüssel mithilfe mehrerer vorhandener Attribute wie Turnier-ID und Region. DynamoDB verarbeitet die Logik der zusammengesetzten Schlüssel automatisch und verknüpft sie für die Datenverteilung miteinander. Sie schreiben Elemente mit natürlichen Attributen aus Ihrem Domänenmodell, und der GSI indexiert sie automatisch. Keine Verkettung, kein Parsen, kein Auffüllen. Ihr Code bleibt sauber, Ihre Daten bleiben eingegeben und Ihre Abfragen bleiben einfach. Dieser Ansatz ist besonders nützlich, wenn Sie hierarchische Daten mit natürlichen Attributgruppierungen haben (wie Turnier → Region → Runde oder Organisation → Abteilung → Mannschaft).

## Beispiel für eine Anwendung
<a name="GSI.DesignPattern.MultiAttributeKeys.ApplicationExample"></a>

In diesem Leitfaden wird der Aufbau eines Systems zur Verfolgung von Turnierspielen für eine E-Sport-Plattform beschrieben. Die Plattform muss Spiele in mehreren Dimensionen effizient abfragen können: nach Turnieren und Regionen für die Bracketverwaltung, nach Spielern für den Spielverlauf und nach Datum für die Planung.

## Datenmodell
<a name="GSI.DesignPattern.MultiAttributeKeys.DataModel"></a>

In dieser exemplarischen Vorgehensweise unterstützt das System zur Nachverfolgung von Turnierspielen drei primäre Zugriffsmuster, für die jeweils eine andere Schlüsselstruktur erforderlich ist:

**Zugriffsmuster 1: ** Suchen Sie nach einem bestimmten Spiel anhand seiner eindeutigen ID
+ **Lösung: ** Basistabelle mit `matchId` als Partitionsschlüssel

**Zugriffsmuster 2: ** Fragen Sie alle Spiele für ein bestimmtes Turnier und eine bestimmte Region ab und filtern Sie optional nach Runde, Gruppe oder Spiel
+ **Lösung: ** Globaler Sekundärindex mit Partitionsschlüssel mit mehreren Attributen (`tournamentId`\+`region`) und Sortierschlüssel mit mehreren Attributen (`round`\+ \+`bracket`) `matchId`
+ **Beispielabfragen: ** „Alle WINTER2024-Spiele in der NA-EAST Region“ oder „Alle HALBFINALSPIELE in der OBEREN Klammer für“ WINTER2024/NA-EAST

**Zugriffsmuster 3: ** Fragen Sie den Spielverlauf eines Spielers ab und filtern Sie optional nach Zeitraum oder Turnierrunde
+ **Lösung: ** Globaler Sekundärindex mit einem Partitionsschlüssel (`player1Id`) und einem Sortierschlüssel mit mehreren Attributen (`matchDate`\+`round`)
+ **Beispielabfragen: ** „Alle Spiele für Spieler 101" oder „Spiele von Spieler 101 im Januar 2024"

Der entscheidende Unterschied zwischen herkömmlichen Ansätzen und Ansätzen mit mehreren Attributen wird deutlich, wenn man die Artikelstruktur untersucht:

**Traditioneller Ansatz des globalen Sekundärindexes (verkettete Schlüssel): **

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

**Multi-attribute Globaler sekundärer Index-Ansatz (systemeigene Schlüssel): **

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

Bei Schlüsseln mit mehreren Attributen schreiben Sie Elemente mit natürlichen Domänenattributen einmal. DynamoDB indexiert sie automatisch über mehrere GSIs hinweg, ohne dass synthetische verkettete Schlüssel erforderlich sind.

**Schema der Basistabelle: **
+ Partitionsschlüssel: `matchId` (1 Attribut)

**Globales sekundäres Indexschema (TournamentRegionIndex mit Schlüsseln mit mehreren Attributen): **
+ Partitionsschlüssel:`tournamentId`, `region` (2 Attribute)
+ Sortierschlüssel:`round`,`bracket`, `matchId` (3 Attribute)

**Globales sekundäres Indexschema (PlayerMatchHistoryIndex mit Schlüsseln mit mehreren Attributen): **
+ Partitionsschlüssel: `player1Id` (1 Attribut)
+ Sortierschlüssel:`matchDate`, `round` (2 Attribute)

### Basistabelle: TournamentMatches
<a name="GSI.DesignPattern.MultiAttributeKeys.BaseTable"></a>


| MatchID (PK) | Turnier-ID | Region | round | Halterung | ID des Spielers 1 | Spieler-2-ID | Datum des Spiels | Gewinner | score | 
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | 
| Spiel-001 | WINTER 2024 | NA-EAST | FINALE | MEISTERSCHAFT | 101 | 103 | 20-01-20 | 101 | 3-1 | 
| Spiel-002 | WINTER 2024 | NA-EAST | HALBFINALE | UPPER | 101 | 105 | 18.01.2024 | 101 | 3-2 | 
| Spiel-003 | WINTER 2024 | NA-EAST | HALBFINALE | UPPER | 103 | 107 | 18.01.2024 | 103 | 3-0 | 
| Spiel-04 | WINTER 2024 | NA-EAST | VIERTELFINALE | UPPER | 101 | 109 | 15.01.2024 | 101 | 3-1 | 
| Spiel-005 | WINTER 2024 | NA-WEST | FINALE | MEISTERSCHAFT | 102 | 104 | 20-01-20 | 102 | 3-2 | 
| Spiel-06 | WINTER 2024 | NA-WEST | HALBFINALE | UPPER | 102 | 106 | 18.01.2024 | 102 | 3-1 | 
| Spiel-007 | FRÜHLING 2024 | NA-EAST | VIERTELFINALE | UPPER | 101 | 108 | 15.03.2020 | 101 | 3-0 | 
| Spiel-008 | FRÜHLING 2024 | NA-EAST | VIERTELFINALE | LOWER | 103 | 110 | 15.03.2020 | 103 | 3-2 | 

### GSI: TournamentRegionIndex (Schlüssel mit mehreren Attributen)
<a name="GSI.DesignPattern.MultiAttributeKeys.TournamentRegionIndexTable"></a>


| Turnier-ID (PK) | Region (PK) | rund (SK) | Klammer (SK) | Spiel-ID (SK) | ID des Spielers 1 | Spieler2-ID | Datum des Spiels | Gewinner | score | 
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | 
| WINTER 2024 | NA-EAST | FINALE | MEISTERSCHAFT | Spiel-001 | 101 | 103 | 20-01-20 | 101 | 3-1 | 
| WINTER 2024 | NA-EAST | VIERTELFINALE | UPPER | Spiel-04 | 101 | 109 | 15.01.2020 | 101 | 3-1 | 
| WINTER 2024 | NA-EAST | HALBFINALE | UPPER | Spiel-002 | 101 | 105 | 18.01.2020 | 101 | 3-2 | 
| WINTER 2024 | NA-EAST | HALBFINALE | UPPER | spiel-003 | 103 | 107 | 18.01.2020 | 103 | 3-0 | 
| WINTER 2024 | NA-WEST | FINALE | MEISTERSCHAFT | Spiel-005 | 102 | 104 | 20-01-20 | 102 | 3-2 | 
| WINTER 2024 | NA-WEST | HALBFINALE | UPPER | Spiel-06 | 102 | 106 | 18.01.2020 | 102 | 3-1 | 
| FRÜHLING 2024 | NA-EAST | VIERTELFINALE | LOWER | Spiel-08 | 103 | 110 | 15.03.2020 | 103 | 3-2 | 
| FRÜHLING 2024 | NA-EAST | VIERTELFINALE | UPPER | Spiel-007 | 101 | 108 | 15.03.2020 | 101 | 3-0 | 

### GSI: PlayerMatchHistoryIndex (Schlüssel mit mehreren Attributen)
<a name="GSI.DesignPattern.MultiAttributeKeys.PlayerMatchHistoryIndexTable"></a>


| Spieler1-ID (PK) | Spieldatum (SK) | Runde (SK) | Turnier-ID | Region | Halterung | Spiel-ID | ID von Spieler 2 | Gewinner | score | 
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | 
| 101 | 2024-01-15 | VIERTELFINALE | WINTER 2024 | NA-EAST | UPPER | Spiel-04 | 109 | 101 | 3-1 | 
| 101 | 18.01.2020 | HALBFINALE | WINTER 2024 | NA-EAST | UPPER | Spiel-002 | 105 | 101 | 3-2 | 
| 101 | 20-01-20 | ENDSPIELE | WINTER 2024 | NA-EAST | MEISTERSCHAFT | Spiel-001 | 103 | 101 | 3-1 | 
| 101 | 15.03.2024 | VIERTELFINALE | FRÜHLING 2024 | NA-EAST | UPPER | Spiel-007 | 108 | 101 | 3-0 | 
| 102 | 18.01.2020 | HALBFINALE | WINTER 2024 | NA-WEST | UPPER | Spiel-06 | 106 | 102 | 3-1 | 
| 102 | 20-01-20 | ENDSPIELE | WINTER 2024 | NA-WEST | MEISTERSCHAFT | Spiel-005 | 104 | 102 | 3-2 | 
| 103 | 18.01.2020 | HALBFINALE | WINTER 2024 | NA-EAST | UPPER | Spiel 003 | 107 | 103 | 3-0 | 
| 103 | 15.03.2020 | VIERTELFINALE | FRÜHLING 2024 | NA-EAST | LOWER | Spiel-008 | 110 | 103 | 3-2 | 

## Voraussetzungen
<a name="GSI.DesignPattern.MultiAttributeKeys.Prerequisites"></a>

Stellen Sie vor Beginn sicher, dass Sie über Folgendes verfügen:

### Konto und Berechtigungen
<a name="GSI.DesignPattern.MultiAttributeKeys.Prerequisites.AWSAccount"></a>
+ Ein aktives AWS Konto ([erstellen Sie hier eines](https://aws.amazon.com/free/), falls erforderlich)
+ IAM-Berechtigungen für DynamoDB-Operationen:
  + `dynamodb:CreateTable`
  + `dynamodb:DeleteTable`
  + `dynamodb:DescribeTable`
  + `dynamodb:PutItem`
  + `dynamodb:Query`
  + `dynamodb:BatchWriteItem`

**Anmerkung**  
**Sicherheitshinweis: Erstellen Sie ** für den Einsatz in der Produktion eine benutzerdefinierte IAM-Richtlinie mit nur den Berechtigungen, die Sie benötigen. Für dieses Tutorial können Sie die AWS verwaltete Richtlinie `AmazonDynamoDBFullAccessV2` verwenden.

### Entwicklungsumgebung
<a name="GSI.DesignPattern.MultiAttributeKeys.Prerequisites.DevEnvironment"></a>
+ Node.js auf Ihrem Computer installiert
+ AWS Anmeldeinformationen, die mit einer der folgenden Methoden konfiguriert wurden:

**Option 1: AWS CLI **

```
aws configure
```

**Option 2: Umgebungsvariablen**

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

### Installieren erforderlicher Pakete
<a name="GSI.DesignPattern.MultiAttributeKeys.Prerequisites.InstallPackages"></a>

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

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

### Schritt 1: Erstellen Sie eine Tabelle mit GSIs mithilfe von Schlüsseln mit mehreren Attributen
<a name="GSI.DesignPattern.MultiAttributeKeys.CreateTable"></a>

Erstellen Sie eine Tabelle mit einer einfachen Basisschlüsselstruktur und GSIs, die Schlüssel mit mehreren Attributen verwenden.

#### Codebeispiel
<a name="w2aac19c13c49c23b9c11b3b5b1"></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");
```

**Wichtige Entwurfsentscheidungen: **

**Basistabelle: ** Die Basistabelle verwendet einen einfachen `matchId` Partitionsschlüssel für direkte Treffer, wodurch die Struktur der Basistabelle übersichtlich bleibt, während die GSIs die komplexen Abfragemuster bereitstellen.

**TournamentRegionIndex Globaler Sekundärindex: ** Der `TournamentRegionIndex` globale sekundäre Index verwendet `tournamentId` \+ `region` als Partitionsschlüssel mit mehreren Attributen. Dadurch wird eine Isolierung zwischen den Turnierregionen erreicht, wobei die Daten durch den Hash beider Attribute zusammen verteilt werden, was effiziente Abfragen innerhalb eines bestimmten Turnierregionskontextes ermöglicht. Der Sortierschlüssel mit mehreren Attributen (`round`\+ `bracket` \+`matchId`) ermöglicht eine hierarchische Sortierung, die Abfragen auf jeder Hierarchieebene mit natürlicher Reihenfolge von allgemein (Runde) bis spezifisch (Match-ID) unterstützt.

**PlayerMatchHistoryIndex Globaler sekundärer Index: ** Der `PlayerMatchHistoryIndex` globale sekundäre Index organisiert Daten nach Spielern neu, wobei er `player1Id` als Partitionsschlüssel verwendet wird, sodass turnierübergreifende Abfragen für einen bestimmten Spieler möglich sind. Der Sortierschlüssel mit mehreren Attributen (`matchDate`\+`round`) ermöglicht eine chronologische Sortierung mit der Möglichkeit, nach Datumsbereichen oder bestimmten Turnierrunden zu filtern.

### Schritt 2: Daten mit systemeigenen Attributen einfügen
<a name="GSI.DesignPattern.MultiAttributeKeys.InsertData"></a>

Fügen Sie Turnierspieldaten mithilfe natürlicher Attribute hinzu. Das GSI indexiert diese Attribute automatisch, ohne dass synthetische Schlüssel erforderlich sind.

#### Codebeispiel
<a name="w2aac19c13c49c23b9c11b5b5b1"></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");
```

**Datenstruktur erklärt: **

**Natürliche Verwendung von Attributen: ** Jedes Attribut steht für ein echtes Turnierkonzept, für das keine Verkettung oder Analyse von Zeichenketten erforderlich ist, sodass eine direkte Zuordnung zum Domänenmodell möglich ist.

**Automatische Indexierung des globalen sekundären Indexes: ** Die GSIs indizieren Elemente automatisch anhand der vorhandenen Attribute (`tournamentId`,,, `matchId` für TournamentRegionIndex und`region`, `round` für PlayerMatchHistoryIndex) `round``bracket`, ohne dass synthetische `player1Id` verkettete `matchDate` Schlüssel erforderlich sind.

**Kein Auffüllen erforderlich: ** Wenn Sie einer vorhandenen Tabelle einen neuen globalen sekundären Index mit Schlüsseln mit mehreren Attributen hinzufügen, indexiert DynamoDB automatisch alle vorhandenen Elemente anhand ihrer natürlichen Attribute — Sie müssen keine Elemente mit synthetischen Schlüsseln aktualisieren.

### Schritt 3: Abfragen TournamentRegionIndex des globalen sekundären Index mit allen Partitionsschlüsselattributen
<a name="GSI.DesignPattern.MultiAttributeKeys.QueryAllPartitionKeys"></a>

In diesem Beispiel wird der TournamentRegionIndex globale sekundäre Index abgefragt, der über einen Partitionsschlüssel mit mehreren Attributen (`tournamentId`\+`region`) verfügt. Alle Partitionsschlüsselattribute müssen in Abfragen mit Gleichheitsbedingungen angegeben werden. Sie können keine Abfragen `tournamentId` allein durchführen oder Ungleichheitsoperatoren für Partitionsschlüsselattribute verwenden.

#### Codebeispiel
<a name="w2aac19c13c49c23b9c11b7b5b1"></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`);
});
```

**Erwartete Ausgabe: **

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

**Ungültige Abfragen: **

```
// Missing region attribute
KeyConditionExpression: 'tournamentId = :tournament'

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

**Leistung: ** Multi-attribute Partitionsschlüssel werden zusammen gehasht und bieten dieselbe O (1) -Suchleistung wie Schlüssel mit Einzelattributen.

### Schritt 4: Abfragen der Sortierschlüssel für den globalen sekundären Index von links nach rechts
<a name="GSI.DesignPattern.MultiAttributeKeys.QuerySortKeysLeftToRight"></a>

Sortierschlüsselattribute müssen von links nach rechts in der Reihenfolge abgefragt werden, in der sie im globalen sekundären Index definiert sind. Dieses Beispiel veranschaulicht das Abfragen TournamentRegionIndex auf verschiedenen Hierarchieebenen: Filtern nach nur`round`, `round` nach\+oder nach allen drei `bracket` Sortierschlüsselattributen. Sie können Attribute in der Mitte nicht überspringen — Sie können beispielsweise nicht nach und während des Überspringens `round` abfragen. `matchId` `bracket`

#### Codebeispiel
<a name="w2aac19c13c49c23b9c11b9b5b1"></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`);
}
```

**Erwartete Ausgabe: **

```
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 Abfrageregeln: ** Sie müssen Attribute in der Reihenfolge von links nach rechts abfragen, ohne sie zu überspringen.

**Gültige Muster: **
+ Nur erstes Attribut: `round = 'SEMIFINALS'`
+ Die ersten beiden Attribute: `round = 'SEMIFINALS' AND bracket = 'UPPER'`
+ Alle drei Attribute: `round = 'SEMIFINALS' AND bracket = 'UPPER' AND matchId = 'match-002'`

**Ungültige Muster: **
+ Das erste Attribut überspringen: `bracket = 'UPPER'` (überspringt den Vorgang)
+ Abfrage nicht in der richtigen Reihenfolge: `matchId = 'match-002' AND round = 'SEMIFINALS'`
+ Lücken hinterlassen: `round = 'SEMIFINALS' AND matchId = 'match-002'` (überspringt die Klammer)

**Anmerkung**  
**Designtipp: ** Sortieren Sie die wichtigsten Attribute von den allgemeinsten bis hin zu den spezifischsten, um die Flexibilität bei der Abfrage zu maximieren.

### Schritt 5: Verwenden Sie Ungleichheitsbedingungen für die Sortierschlüssel des globalen sekundären Index
<a name="GSI.DesignPattern.MultiAttributeKeys.InequalityConditions"></a>

Ungleichheitsbedingungen müssen die letzte Bedingung in Ihrer Abfrage sein. Dieses Beispiel zeigt die Verwendung von Vergleichsoperatoren (`>=`,`BETWEEN`) und Präfixvergleich (`begins_with()`) für Sortierschlüsselattribute. Sobald Sie einen Ungleichheitsoperator verwendet haben, können Sie ihm keine weiteren Sortierschlüsselbedingungen hinzufügen — die Ungleichheit muss die letzte Bedingung in Ihrem Schlüsselbedingungsausdruck sein.

#### Codebeispiel
<a name="w2aac19c13c49c23b9c11c11b5b1"></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`);
}
```

**Regeln für Ungleichheitsoperatoren: ** Sie können Vergleichsoperatoren (`>`, `>=``<`,`<=`) `BETWEEN` für Bereichsabfragen und `begins_with()` für den Präfixabgleich verwenden. Die Ungleichheit muss die letzte Bedingung in Ihrer Abfrage sein.

**Gültige Muster: **
+ Gleichheitsbedingungen gefolgt von Ungleichheit: `round = 'SEMIFINALS' AND bracket = 'UPPER' AND matchId > 'match-001'`
+ Ungleichheit beim ersten Attribut: `round BETWEEN 'QUARTERFINALS' AND 'SEMIFINALS'`
+ Präfixübereinstimmung als Endbedingung: `round = 'SEMIFINALS' AND begins_with(bracket, 'U')`

**Ungültige Muster: **
+ Hinzufügen von Bedingungen nach einer Ungleichung: `round > 'QUARTERFINALS' AND bracket = 'UPPER'`
+ Verwendung mehrerer Ungleichungen: `round > 'QUARTERFINALS' AND bracket > 'L'`

**Wichtig**  
`begins_with()`wird als Ungleichheitsbedingung behandelt, sodass keine weiteren Sortierschlüsselbedingungen darauf folgen können.

### Schritt 6: Fragen Sie den PlayerMatchHistoryIndex globalen Sekundärindex mit einem Sortierschlüssel mit mehreren Attributen ab
<a name="GSI.DesignPattern.MultiAttributeKeys.QueryPlayerHistory"></a>

In diesem Beispiel wird der abgefragt PlayerMatchHistoryIndex , der einen einzelnen Partitionsschlüssel (`player1Id`) und einen Sortierschlüssel mit mehreren Attributen (`matchDate`\+`round`) hat. Dies ermöglicht eine turnierübergreifende Analyse, indem alle Spiele für einen bestimmten Spieler abgefragt werden, ohne die Turnier-IDs zu kennen — wohingegen für die Basistabelle separate Abfragen pro Kombination aus Turnier und Region erforderlich wären.

#### Codebeispiel
<a name="w2aac19c13c49c23b9c11c13b5b1"></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`);
```

## Variationen des Musters
<a name="GSI.DesignPattern.MultiAttributeKeys.PatternVariations"></a>

### Time-series Daten mit Schlüsseln mit mehreren Attributen
<a name="GSI.DesignPattern.MultiAttributeKeys.TimeSeries"></a>

Optimieren Sie für Zeitreihenabfragen mit hierarchischen Zeitattributen

#### Codebeispiel
<a name="w2aac19c13c49c23b9c13b3b5b1"></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
```

**Vorteile: Die ** natürliche Zeithierarchie (Jahr → Monat → Tag → Zeitstempel) ermöglicht effiziente Abfragen zu jeder Zeitgranularität ohne Datumsanalyse oder Manipulation. Der Global Secondary Index indexiert automatisch alle Messwerte anhand ihrer natürlichen Zeitattribute.

### E-commerce Bestellungen mit Schlüsseln mit mehreren Attributen
<a name="GSI.DesignPattern.MultiAttributeKeys.ECommerce"></a>

Verfolgen Sie Bestellungen mit mehreren Dimensionen

#### Codebeispiel
<a name="w2aac19c13c49c23b9c13b5b5b1"></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
```

### Hierarchische Organisationsdaten
<a name="GSI.DesignPattern.MultiAttributeKeys.Hierarchical"></a>

Modellieren Sie Organisationshierarchien

#### Codebeispiel
<a name="w2aac19c13c49c23b9c13b7b5b1"></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
```

### Schlüssel mit mehreren Attributen mit geringer Dichte
<a name="GSI.DesignPattern.MultiAttributeKeys.Sparse"></a>

Kombinieren Sie Schlüssel mit mehreren Attributen, um einen GSI mit geringer Dichte zu erstellen

#### Codebeispiel
<a name="w2aac19c13c49c23b9c13b9b5b1"></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
```

### SaaS-Mehrmandantenfähigkeit
<a name="GSI.DesignPattern.MultiAttributeKeys.SaaS"></a>

Multi-tenant SaaS-Plattform mit Kundenisolierung

#### Codebeispiel
<a name="w2aac19c13c49c23b9c13c11b5b1"></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'
    }
}));
```

**Vorteile: ** Effiziente Abfragen im Kontext zwischen Mieter und Kunde und natürliche Datenorganisation.

### Finanztransaktionen
<a name="GSI.DesignPattern.MultiAttributeKeys.Financial"></a>

Banksystem, das Kontotransaktionen mithilfe von GSIs verfolgt

#### Codebeispiel
<a name="w2aac19c13c49c23b9c13c13b5b1"></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'
    }
}));
```

## Vollständiges Beispiel
<a name="GSI.DesignPattern.MultiAttributeKeys.CompleteExample"></a>

Das folgende Beispiel zeigt Schlüssel mit mehreren Attributen von der Einrichtung bis zur Bereinigung:

### Codebeispiel
<a name="w2aac19c13c49c23b9c15b5b1"></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);
```

**Minimales Code-Gerüst **

### Codebeispiel
<a name="w2aac19c13c49c23b9c15b9b1"></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' }
```

## Weitere Ressourcen
<a name="GSI.DesignPattern.MultiAttributeKeys.AdditionalResources"></a>
+ [Bewährte Methoden für DynamoDB ](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/best-practices.html)
+ [Arbeiten mit Tabellen und Daten ](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithTables.html)
+ [Globale Sekundärindizes ](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html)
+ [Abfrage- und Scanvorgänge ](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html)