DynamoDB テーブルのデータを CSV ファイルとしてローカル環境に export してみた

DynamoDB テーブルのデータを CSV ファイルとしてローカル環境への export を試してみた際の記事です。
2024.06.27

はじめに

こんにちは。アノテーションの及川です。

表題のとおり、DynamoDBテーブルに保存された各項目の情報を AWS マネジメントコンソールでの手動操作以外にAWS SDK や AWS CLI を使用して、CSV ファイルとして一括で export できないかを調べていました。

調べたところ、似たような弊社ブログを確認したため、今回はこちらを元に実装して簡単に試せるかどうかを自身でも検証しました。

参考にした弊社ブログ

StreamせずにScan+CSV変換→ローカルCSVファイルに吐き出す
// main.ts

import { DynamoDB } from '@aws-sdk/client-dynamodb'
import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb'
import { stringify } from 'csv/sync'
import fs from 'fs'

const ddb = new DynamoDB({
  region: 'ap-northeast-1',
})
const ddbDoc = DynamoDBDocument.from(ddb)

const main = async (): Promise<void> => {
  let scanOutput = await ddbDoc.scan({
    TableName: 'Users',
  })
  let users = scanOutput.Items!

  while (scanOutput.LastEvaluatedKey !== undefined) {
    scanOutput = await ddbDoc.scan({
      TableName: 'Users',
      ExclusiveStartKey: scanOutput.LastEvaluatedKey,
    })
    for (const user of scanOutput.Items!) {
      users.push(user)
    }
  }

  fs.writeFileSync('ddb-items.csv', stringify(users, { header: true }))
}

main()

検証

準備

必要なパッケージ(package.json:抜粋)は下記です。

今回の検証では Node.js(TypeScript)を使用しているため、下記を参考にnpm install <パッケージ名>コマンドでインストールします。

(下記は検証時点でのバージョンを示しています)

"devDependencies": {
  "@types/node": "^20.14.8",
  "typescript": "^5.5.2"
},
"dependencies": {
  "@aws-sdk/client-dynamodb": "^3.602.0",
  "@aws-sdk/lib-dynamodb": "^3.602.0",
  "csv": "^6.3.9"
}

他には、tsc --init などで tsconfig.json ファイルも適宜作成します。

テスト用の DynamoDB テーブル情報

下記のようなテーブルを事前に用意しました。

  • テーブル名: Users
id(文字列) name(文字列) phoneNumber(文字列)
A0001 アノテ太郎 090-xxxx-xxxx
A0002 アノテ花子 080-yyyy-yyyy
A0003 アノテ次郎 070-zzzz-zzzz

実行して確認してみた①

前述の参考にした弊社ブログのソースコードを実行して作成された CSV ファイルを確認したところ、下記のようになっていました。

id,phoneNumber,name
A0003,070-zzzz-zzzz,アノテ次郎
A0001,090-xxxx-xxxx,アノテ太郎
A0002,080-yyyy-yyyy,アノテ花子

DynamoDB のテーブルに登録している項目の順番(id,name,phoneNumber)と異なるため、本事象に関係すると思われる情報を調査したところ、いくつかの参考となる情報を確認しました。

Does JavaScript guarantee object property order? - Stack Overflow

The iteration order for objects follows a certain set of rules since ES2015, but it does not (always) follow the insertion order. Simply put, the iteration order is a combination of the insertion order for strings keys, and ascending order for number-like keys:

オブジェクト

オブジェクトの順序付け
オブジェクトは順序付けられますか?つまり、オブジェクトをループするとき、追加したのと同じ順序ですべてのプロパティを取得しますか?それを保証することはできるでしょうか?

回答は、“特別な方法で順序付けられます”: 整数値のプロパティはソートされます、それ以外は作成した順になります。以下、その詳細です。

let codes = {
  "49": "Germany",
  "41": "Switzerland",
  "44": "Great Britain",
  // ..,
  "1": "USA"
};

for(let code in codes) {
  alert(code); // 1, 41, 44, 49
}

オブジェクトはユーザに対してオプションの一覧を提案をするのに使われるかもしれません。もし主にドイツのユーザをターゲットにしたサイトを作る場合、恐らく最初に 49 が出て欲しいです。

しかし、コードを実行すると完全に異なったものが見えます:

USA (1) が最初に来ます
次に Switzerland (41) などが並びます.
電話コードは昇順にソートされて表示されます。なぜなら、それらが整数だからです。なので、1, 41, 44, 49 と見えます。

ECMAScript® 2025 Language Specification

10.1.11 [[OwnPropertyKeys]] ( )
The [[OwnPropertyKeys]] internal method of an ordinary object O takes no arguments and returns a normal completion containing a List of property keys. It performs the following steps when called:

1. Return OrdinaryOwnPropertyKeys(O).

10.1.11.1 OrdinaryOwnPropertyKeys ( O )
The abstract operation OrdinaryOwnPropertyKeys takes argument O (an Object) and returns a List of property keys. It performs the following steps when called:

1. Let keys be a new empty List.
2. For each own property key P of O such that P is an array index, in ascending numeric index order, do
a. Append P to keys.
3. For each own property key P of O such that P is a String and P is not an array index, in ascending chronological order of property creation, do
a. Append P to keys.
4. For each own property key P of O such that P is a Symbol, in ascending chronological order of property creation, do
a. Append P to keys.
5. Return keys.

DyamoDB テーブルで指定した列の順番(id,name,phoneNumber)でCSVに出力させるため、コードを修正して確認してみます。

実行して確認してみた②(修正後)

下記の方針を元にして、修正しました。

  • columns 配列にCSVに含めるカラム名を定義する
  • sort メソッドにより、id を元に昇順に並び替える
  • users 配列の各ユーザーオブジェクトを columns に基づいて整形(reduce メソッドにより、配列の各要素に対して指定されたコールバック関数を実行し、単一の累積結果を生成)して、formattedUsers 配列に格納する

コード例(前述の参考にした弊社ブログのコードより、変更箇所をハイライトしています。)

import { DynamoDB } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb";
import { stringify } from "csv/sync";
import fs from "fs";

const ddb = new DynamoDB({
  region: "ap-northeast-1",
});
const ddbDoc = DynamoDBDocument.from(ddb);

const main = async (): Promise<void> => {
  let scanOutput = await ddbDoc.scan({
    TableName: "Users",
  });
  let users = scanOutput.Items!;

  while (scanOutput.LastEvaluatedKey !== undefined) {
    scanOutput = await ddbDoc.scan({
      TableName: "Users",
      ExclusiveStartKey: scanOutput.LastEvaluatedKey,
    });
    for (const user of scanOutput.Items!) {
      users.push(user);
    }
  }

  const columns = ["id", "name", "phoneNumber"];

  users.sort((a, b) => (a.id > b.id ? 1 : -1));

  const formattedUsers = users.map((user) =>
    columns.reduce(
      (acc, column) => ({ ...acc, [column]: user[column] || "" }),
      {},
    ),
  );

  fs.writeFileSync(
    "ddb-items-formattedUsers.csv",
    stringify(formattedUsers, { header: true }),
  );
};

main();

出力結果

id,name,phoneNumber
A0001,アノテ太郎,090-xxxx-xxxx
A0002,アノテ花子,080-yyyy-yyyy
A0003,アノテ次郎,070-zzzz-zzzz

まとめ

今回 DynamoDB テーブルのデータをCSVファイルとしてローカル環境に export する方法を調査する機会がありましたので弊社の類似ブログや関連する情報を調査して簡単に自身でも検証してみました。

この記事が誰かのお役に立てば幸いです。

アノテーション株式会社について

アノテーション株式会社はクラスメソッドグループのオペレーション専門特化企業です。サポート・運用・開発保守・情シス・バックオフィスの専門チームが、最新 IT テクノロジー、高い技術力、蓄積されたノウハウをフル活用し、お客様の課題解決を行っています。当社は様々な職種でメンバーを募集しています。「オペレーション・エクセレンス」と「らしく働く、らしく生きる」を共に実現するカルチャー・しくみ・働き方にご興味がある方は、アノテーション株式会社 採用サイトをぜひご覧ください。

参考資料