Node.jsでDynamoDBを触ってみたので、備忘録として残しておきます。
プライマリーキーとして、パーティションキーのみの方法と、パーティションキー+ソートキーでユニークにする方法があります。
前者を「ハッシュキーテーブル」、後者を「複合キーテーブル」と呼ぶことにします。
以下、参考にさせていただきました。(ありがとうございました)
コンセプトから学ぶAmazon DynamoDB【インデックス俯瞰篇】
【詳解】JavascriptでDynamoDBを操作する
DynamoDB での Node.js の開始方法
Class: AWS.DynamoDB.DocumentClient
準備
Node.jsのソースコードのどこかに、以下を記載しておきます。
const AWS = require("aws-sdk");
AWS.config.update({
region: "ap-northeast-1",
});
const docClient = new AWS.DynamoDB.DocumentClient();
以降で、上記の変数docClientを使います。
それから、AWS Lambdaではなく自身のサーバ等で実行する場合は、以下と、aws configure
でIAMの認証情報を設定しておきます。
npm install aws-sdk --save-dev
ハッシュキーテーブル
プライマリキーがパーティションキーのみのハッシュキーテーブルを扱います。
こんな感じでテーブルを作成します。
テーブル名(tableName):Hashkey_Table
パーティションキー名:firstkey
登録
項目を登録します。SQLでいうところのInsertです。
var params_put = {
TableName: tableName,
Item:{
firstkey: "firstvalue_1",
"attr": "Hello_1",
},
ConditionExpression: 'attribute_not_exists(firstkey)',
};
result = await docClient.put(params_put).promise();
Itemのところに、登録したい項目の値を指定します。スキーマレスなので、任意の値(attr=”Hello_1”)も指定しています。ただし、プライマリキーは必ず指定する必要があります。
以下の指定はオプションです。指定した場合は、プライマリキーが同じ項目が登録されていた場合はエラー(throwが発生)となります。指定しなかった場合は、置き換わります。
ConditionExpression: "attribute_not_exists(firstkey)"
取得
プライマリキーを指定して、項目を取得します。
SQLと違って、1件のみ取得します。
var params_get = {
TableName: tableName,
Key: {
firstkey: "firstvalue_1"
}
};
result = await docClient.get(params_get).promise();
if( result.Item ){
console.log(JSON.stringify(result.Item));
}else{
// 見つからない場合はundefined
console.log('Not found');
}
更新
SQLでいうところのUPDATEです。
登録時に一緒に指定したattrの値を変更します。
var params_update = {
TableName: tableName,
Key: {
firstkey: "firstvalue_1"
},
ExpressionAttributeNames: {
'#attr': 'attr'
},
ExpressionAttributeValues: {
':attrValue': 'Hello_1_Update_1'
},
UpdateExpression: 'SET #attr = :attrValue',
ConditionExpression: "attribute_exists(firstkey)",
ReturnValues: "ALL_OLD" // "ALL_OLD" or "ALL_NEW"
};
result = await docClient.update(params_update).promise();
以下の指定はオプションです。指定した場合は、もし更新対象の項目が存在しなかった場合はエラー(throwが発生)となります。指定しなかった場合で、もし更新対象が項目が存在しなかった場合は、新規に登録されます。
ConditionExpression: "attribute_exists(firstkey)"
以下もオプションです。指定すると、更新した項目の全体の値が返ってきます。
ReturnValues:"ALL_NEW"
削除
指定したプライマリキーに合致する項目を削除します。
var params_delete = {
TableName: tableName,
Key: {
firstkey: "firstvalue_1"
},
// ConditionExpression: 'attribute_exists(firstkey)',
};
result = await docClient.delete(params_delete).promise();
以下の指定はオプションです。もし指定した場合は、指定したプライマリキーに合致した項目がなかった場合にエラー(throw)となります。
ConditionExpression: "attribute_exists(firstkey)"
一括取得
先ほどの取得は、1件のプライマリキーを指定して、合致する1件の項目を取得しました。
今度は、複数のプライマリキーを指定して、合致する複数の項目を取得します。
var params_batchget = {
RequestItems: {}
};
params_batchget.RequestItems[tableName] = {
Keys: [
{
firstkey: "firstvalue_2"
},
{
firstkey: "firstvalue_3"
},
]
};
result = await docClient.batchGet(params_batchget).promise();
if( Object.keys(result.UnprocessedKeys).length > 0 )
console.log('in process yet');
for( var i = 0 ; i < result.Responses[tableName].length ; i++ ){
console.log(i, JSON.stringify(result.Responses[tableName][i]));
}
レスポンスの result.Responses[tableName]
に合致した項目が配列で格納されています。
もし、result.UnprocessedKeys
に何か値が入っていると、全部の検索は完了していないため、再度取得をしないといけないようです。
全件取得
全件取得します。
var params_scan = {
TableName: tableName
};
var list = [];
while(true){
var result = await docClient.scan(params_scan).promise();
if( result.Items )
Array.prototype.push.apply(list, result.Items);
if( result.LastEvaluatedKey ){
params_scan.ExclusiveStartKey = result.LastEvaluatedKey;
}else{
break;
}
}
console.log(JSON.stringify(list));
複合キーテーブル
今度は、プライマリキーがパーティションキー+ソートキーの複合キーテーブルを扱います。
こんな感じでテーブルを作成します。
テーブル名(tableName):Complexkey_Table
パーティションキー名:firstkey
ソートキー名:secondkey
こんな感じで作ります。
登録
項目を登録します。
var params_put = {
TableName: tableName,
Item:{
firstkey: "firstvalue_1",
secondkey: "secondvalue_1",
"attr": "Hello_1",
},
ConditionExpression: 'attribute_not_exists(firstkey)',
};
result = await docClient.put(params_put).promise();
ハッシュキーテーブルと同様、Itemに項目の値を指定します。
プライマリキーとソートキーでユニークとなるため、両方を指定する必要があります。
取得
項目を取得します。
1つの項目が返る前提ですので、ユニークに特定するため、プライマリキーとソートキーの両方を指定します。
var params_get = {
TableName: tableName,
Key: {
firstkey: "firstvalue_1",
secondkey: "secondvalue_1",
}
};
result = await docClient.get(params_get).promise();
if( result.Item ){
console.log(JSON.stringify(result.Item));
}else{
// 見つからない場合はundefined
console.log('Not found');
}
一括取得
複数のプライマリキーを指定して、合致する複数の項目を一度に取得します。
var params_batchget = {
RequestItems: {}
};
params_batchget.RequestItems[tableName] = {
Keys: [
{
firstkey: 'firstvalue_1',
secondkey: 'secondvalue_1',
},
{
firstkey: 'firstvalue_2',
secondkey: "secondvalue_2",
},
{
firstkey: "firstvalue_3",
secondkey: 'secondvalue_3',
},
],
};
var result = await docClient.batchGet(params_batchget).promise();
if( Object.keys(result.UnprocessedKeys).length > 0 )
console.log('in process yet');
for( var i = 0 ; i < result.Responses[tableName].length ; i++ ){
console.log(i, JSON.stringify(result.Responses[tableName][i]));
}
更新
項目の値を更新します。
ハッシュキーテーブルの時とほぼ同じです。
プライマリキーとして、パーティションキーとソートキーの両方を指定しています。
var params_update = {
TableName: tableName,
Key: {
firstkey: "firstvalue_1",
secondkey: "secondvalue_1",
},
ExpressionAttributeNames: {
'#attr': 'attr'
},
ExpressionAttributeValues: {
':attrValue': 'Hello_1_Update_1'
},
UpdateExpression: 'SET #attr = :attrValue',
ConditionExpression: 'attribute_exists(firstkey)',
ReturnValues: "ALL_OLD" // "ALL_OLD" or "ALL_NEW"
};
result = await docClient.update(params_update).promise();
削除
項目の削除です。
ハッシュキーテーブルの時とほぼ同じです。
プライマリキーとして、パーティションキーとソートキーの両方を指定しています。
var params_delete = {
TableName: tableName,
Key: {
firstkey: 'firstvalue_1',
secondkey: 'secondvalue_1',
},
// ConditionExpression: 'attribute_exists(firstkey)',
};
result = await docClient.delete(params_delete).promise();
検索
条件に合致する項目を検索します。
取得と違って、複数の項目が返ってきます。
まず最初が、パーティションキーが合致する項目の取得です。
複合キーテーブルでは、パーティションキーとソートキーでユニークとするため、同じパーティションキーで、値の異なる複数のソートキーを登録することができます。
よって、パーティションキーのみを条件に指定した場合は、同じパーティションキーの複数の項目を取得できます。ちなみに、ソートキーのみを条件とした検索はできません。
var params_query = {
TableName: tableName,
ExpressionAttributeNames: {
'#firstkey': 'firstkey',
},
ExpressionAttributeValues: {
':firstValue': "firstvalue_1",
},
KeyConditionExpression: '#firstkey = :firstValue',
};
result = await docClient.query(params_query).promise();
for( var i = 0 ; i < result.Count; i++ ){
console.log(i, JSON.stringify(result.Items[i]));
}
if( result.LastEvaluatedKey ){
// 全取得できなかったので、追加検索
params_query.ExclusiveStartKey = result.LastEvaluatedKey;
// 再検索。LastEvaluatedKeyが返ってこなくなるまで繰り返し
}
result.Count
には取得した項目の件数、result.Items
に取得した項目が配列で取得できます。
次は、ソートキーにさらに条件を付けて絞り込んで検索します。
var params_query = {
TableName: tableName,
ExpressionAttributeNames: {
'#firstkey': 'firstkey',
'#secondkey': 'secondkey',
},
ExpressionAttributeValues: {
':firstValue': 'firstvalue_1',
':secondValue': 'secondvalue_1_',
},
KeyConditionExpression: '#firstkey = :firstValue AND begins_with(#secondkey, :secondValue)',
};
result = await docClient.query(params_query).promise();
for( var i = 0 ; i < result.Count; i++ ){
console.log(i, JSON.stringify(result.Items[i]));
}
if( result.LastEvaluatedKey ){
// 全取得できなかったので、追加検索
params_query.ExclusiveStartKey = result.LastEvaluatedKey;
// 再検索。LastEvaluatedKeyが返ってこなくなるまで繰り返し
}
上記では1例として、以下を指定しています。
begins_with(#secondkey, :secondValue)
これは、ソートキーが、指定した値secondvalue_1_
で始まる文字列の場合に合致したとみなされます。
(参考) DynamoDB でのクエリの操作
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Query.html
全項目取得
通常は、パーティションキーの指定が必須のため、例えば、項目を全取得することはできません。
しかし、queryではなく、scanを使えば、パーティションキーの指定は必須ではないため、全取得等、パーティションキーをまたいだ処理ができます。
ただし、scanは、いったんクライアント側に取得してからフィルタリングするため、メモリ使用量や性能にご注意ください。また、ProjectionExpressionで、返される項目を限定することができます。
var params_scan = {
TableName: tableName,
FilterExpression: "secondkey = :secondValue",
ExpressionAttributeValues: {
":secondValue": "secondvalue_1"
},
ProjectionExpression: "firstkey, secondkey",
};
result = await docClient.scan(params_scan).promise();
for( var i = 0 ; i < result.Count; i++ ){
console.log(i, JSON.stringify(result.Items[i]));
}
if( result.LastEvaluatedKey ){
// 全取得できなかったので、追加検索
params_scan.ExclusiveStartKey = result.LastEvaluatedKey;
// 再検索。LastEvaluatedKeyが返ってこなくなるまで繰り返し
}
(参考)テストプログラム
const AWS = require("aws-sdk");
AWS.config.update({
region: "ap-northeast-1",
});
const docClient = new AWS.DynamoDB.DocumentClient();
const HashkeyTableName = 'Hashkey_Table';
const ComplexkeyTableName = 'Complexkey_Table';
async function hashkey_insertItem(){
var params_put = {
TableName: HashkeyTableName,
Item:{
firstkey: "firstvalue_1",
"attr": "Hello_1",
},
ConditionExpression: 'attribute_not_exists(firstkey)',
};
await docClient.put(params_put).promise();
}
async function hashkey_getItem(){
var params_get = {
TableName: HashkeyTableName,
Key: {
firstkey: "firstvalue_1"
}
};
var result = await docClient.get(params_get).promise();
return result.Item;
}
async function hashkey_getAllItem(){
var params_scan = {
TableName: HashkeyTableName,
// ProjectionExpression: "firstkey",
};
var list = [];
while(true){
var result = await docClient.scan(params_scan).promise();
if( result.Items )
Array.prototype.push.apply(list, result.Items);
if( result.LastEvaluatedKey ){
params_scan.ExclusiveStartKey = result.LastEvaluatedKey;
}else{
break;
}
}
return list;
}
async function hashkey_updateItem(){
var params_update = {
TableName: HashkeyTableName,
Key: {
firstkey: "firstvalue_1"
},
ExpressionAttributeNames: {
'#attr': 'attr'
},
ExpressionAttributeValues: {
':attrValue': 'Hello_1_Update_1'
},
UpdateExpression: 'SET #attr = :attrValue',
ConditionExpression: "attribute_exists(firstkey)",
ReturnValues: "ALL_OLD" // "ALL_OLD" or "ALL_NEW"
};
var result = await docClient.update(params_update).promise();
return result;
}
async function hashkey_deleteItem(){
var params_delete = {
TableName: HashkeyTableName,
Key: {
firstkey: "firstvalue_1"
},
ConditionExpression: 'attribute_exists(firstkey)',
ReturnValues: "ALL_OLD"
};
var result = await docClient.delete(params_delete).promise();
return result;
}
async function complexkey_insertItem(){
var params_put = {
TableName: ComplexkeyTableName,
Item:{
firstkey: "firstvalue_1",
secondkey: "secondvalue_1",
"attr": "Hello_1",
},
ConditionExpression: 'attribute_not_exists(firstkey)',
};
await docClient.put(params_put).promise();
}
async function complexkey_insertItem2(){
var params_put = {
TableName: ComplexkeyTableName,
Item:{
firstkey: "firstvalue_1",
secondkey: "secondvalue_2",
"attr": "Hello_2",
},
ConditionExpression: 'attribute_not_exists(firstkey)',
};
await docClient.put(params_put).promise();
}
async function complexkey_getItem(){
var params_get = {
TableName: ComplexkeyTableName,
Key: {
firstkey: "firstvalue_1",
secondkey: "secondvalue_1",
}
};
var result = await docClient.get(params_get).promise();
return result.Item;
}
async function complexkey_getAllItem(){
var params_scan = {
TableName: ComplexkeyTableName,
// FilterExpression: "secondkey = :secondValue",
// ExpressionAttributeValues: {
// ":secondValue": "secondvalue_1"
// },
// ProjectionExpression: "firstkey, secondkey",
};
var list = [];
while(true){
var result = await docClient.scan(params_scan).promise();
if( result.Items )
Array.prototype.push.apply(list, result.Items);
if( result.LastEvaluatedKey ){
params_scan.ExclusiveStartKey = result.LastEvaluatedKey;
}else{
break;
}
}
return list;
}
async function complexkey_updateItem(){
var params_update = {
TableName: ComplexkeyTableName,
Key: {
firstkey: "firstvalue_1",
secondkey: "secondvalue_1",
},
ExpressionAttributeNames: {
'#attr': 'attr'
},
ExpressionAttributeValues: {
':attrValue': 'Hello_1_Update_1'
},
UpdateExpression: 'SET #attr = :attrValue',
ConditionExpression: 'attribute_exists(firstkey)',
ReturnValues: "ALL_OLD" // "ALL_OLD" or "ALL_NEW"
};
var result = await docClient.update(params_update).promise();
return result;
}
async function complexkey_updateItem2(){
var params_update = {
TableName: ComplexkeyTableName,
Key: {
firstkey: "firstvalue_1",
secondkey: "secondvalue_2",
},
ExpressionAttributeNames: {
'#attr': 'attr'
},
ExpressionAttributeValues: {
':attrValue': 'Hello_2_Update_2'
},
UpdateExpression: 'SET #attr = :attrValue',
ConditionExpression: 'attribute_exists(firstkey)',
ReturnValues: "ALL_OLD" // "ALL_OLD" or "ALL_NEW"
};
var result = await docClient.update(params_update).promise();
return result;
}
async function complexkey_deleteItem(){
var params_delete = {
TableName: ComplexkeyTableName,
Key: {
firstkey: 'firstvalue_1',
secondkey: 'secondvalue_1',
},
ConditionExpression: 'attribute_exists(firstkey)',
ReturnValues: "ALL_OLD"
};
var result = await docClient.delete(params_delete).promise();
return result;
}
async function complexkey_deleteItem2(){
var params_delete = {
TableName: ComplexkeyTableName,
Key: {
firstkey: 'firstvalue_1',
secondkey: 'secondvalue_2',
},
ConditionExpression: 'attribute_exists(firstkey)',
ReturnValues: "ALL_OLD"
};
var result = await docClient.delete(params_delete).promise();
return result;
}
(async () =>{
try{
// for hashkey test
var list = await hashkey_getAllItem();
console.log("hashkey_getAllItem", list);
await hashkey_insertItem();
console.log('hashkey_insertItem');
var item = await hashkey_getItem();
console.log("hashkey_getItem", item);
var updated = await hashkey_updateItem();
console.log("hashkey_updateItem", updated);
var list = await hashkey_getAllItem();
console.log("hashkey_getAllItem", list);
var deleted = await hashkey_deleteItem();
console.log("hashkey_deleteItem", deleted);
var list = await hashkey_getAllItem();
console.log("hashkey_getAllItem", list);
// for complexkey test
var list = await complexkey_getAllItem();
console.log("complexkey_getAllItem", list);
await complexkey_insertItem();
console.log('complexkey_insertItem');
await complexkey_insertItem2();
console.log('complexkey_insertItem2');
var item = await complexkey_getItem();
console.log("complexkey_getItem", item);
var list = await complexkey_getAllItem();
console.log("complexkey_getAllItem", list);
var updated = await complexkey_updateItem();
console.log("complexkey_updateItem", updated);
var updated = await complexkey_updateItem2();
console.log("complexkey_updateItem2", updated);
var list = await complexkey_getAllItem();
console.log("complexkey_getAllItem", list);
var deleted = await complexkey_deleteItem();
console.log("complexkey_deleteItem", deleted);
var deleted = await complexkey_deleteItem2();
console.log("complexkey_deleteItem2", deleted);
var list = await complexkey_getAllItem();
console.log("complexkey_getAllItem", list);
}catch(error){
console.error(error);
}
})();
終わりに
DynamoDBには、セカンダリインデックスという機能がありますが、また今度。
以上