View a markdown version of this page

Multi-attribute Tastenmuster - Amazon DynamoDB

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

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

  • 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' }

Weitere Ressourcen