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
-Übersicht
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
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
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
matchIdals 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
| 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)
| 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)
| 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
Stellen Sie vor Beginn sicher, dass Sie über Folgendes verfügen:
Konto und Berechtigungen
-
Ein aktives AWS Konto (erstellen Sie hier eines
, falls erforderlich) -
IAM-Berechtigungen für DynamoDB-Operationen:
dynamodb:CreateTabledynamodb:DeleteTabledynamodb:DescribeTabledynamodb:PutItemdynamodb:Querydynamodb: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
-
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
npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
Implementierung
Schritt 1: Erstellen Sie eine Tabelle mit GSIs mithilfe von Schlüsseln mit mehreren Attributen
Erstellen Sie eine Tabelle mit einer einfachen Basisschlüsselstruktur und GSIs, die Schlüssel mit mehreren Attributen verwenden.
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
Fügen Sie Turnierspieldaten mithilfe natürlicher Attribute hinzu. Das GSI indexiert diese Attribute automatisch, ohne dass synthetische Schlüssel erforderlich sind.
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 undregion, round für PlayerMatchHistoryIndex) roundbracket, 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
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.
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
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 nurround, 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
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
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.
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
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.
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
Time-series Daten mit Schlüsseln mit mehreren Attributen
Optimieren Sie für Zeitreihenabfragen mit hierarchischen Zeitattributen
{ 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
Verfolgen Sie Bestellungen mit mehreren Dimensionen
{ 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
Modellieren Sie Organisationshierarchien
{ 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
Kombinieren Sie Schlüssel mit mehreren Attributen, um einen GSI mit geringer Dichte zu erstellen
{ 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
Multi-tenant SaaS-Plattform mit Kundenisolierung
// 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
Banksystem, das Kontotransaktionen mithilfe von GSIs verfolgt
// 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
Das folgende Beispiel zeigt Schlüssel mit mehreren Attributen von der Einrichtung bis zur Bereinigung:
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
// 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' }