シェルスクリプトで複数のDBからデータを取得・結合し、CSVファイルに出力する

シェルスクリプトを使って、異なるデータベースからデータを取得し、それを結合してCSVファイルとして出力する方法です。

メインのデータにマスタ情報を結合して、CSVファイルとして出力したい状況があったんですが、結合したいデータが別のデータベースにあるため、単純な`JOIN`では対応できませんでした。（Dockerを使ってました）

そこで、結合したいデータを連想配列に格納しておくことで、後から検索できるようにしました。

## テーブル定義

例として、`employees`と`departments`という2つのテーブルを使用します。

それぞれのテーブルは以下のように定義されており、これらを結合してCSVファイルに出力したいなって想定です。

- **`employees` テーブル（社員）**
    
    
    | employee_id | employee_name | department_id |
    | --- | --- | --- |
    | 1 | 山田 太郎 | 100 |
    | 2 | 鈴木 次郎 | 200 |
    | 3 | 田中 三郎 | 100 |
- **`departments` テーブル（部門）**
    
    
    | department_id | department_name |
    | --- | --- |
    | 100 | 人事 |
    | 200 | 開発 |

## シェルスクリプトでデータを取得・結合する手順

`departments`テーブルから部門データを取得し、`employees`テーブルと結合してCSVファイルに出力するシェルスクリプトの手順です。

### 部門データを取得して連想配列に格納する

まず、`departments`テーブルから部門データを取得し、それを連想配列に格納します。

実行するSQLは`SELECT department_id, department_name FROM departments`です。

```bash
# 別DBの部門データを取得し、連想配列に格納する
declare -A department_map
while IFS=$'|' read -r department_id department_name; do
  department_map["$department_id"]="$department_name"
done < <(docker compose exec -T db-container bash -c "psql -d database_b -A -t -c \\"SELECT department_id, department_name FROM departments\\"")
```

- `declare -A department_map`:
    - 連想配列`department_map`を作成します。
- `while IFS=$'|' read -r department_id department_name; do ... done`:
    - `psql`コマンドで取得したデータをプロセス置換を使って直接読み込み、各行を`department_id`と`department_name`に分割して処理します。
    - `IFS=$'|'`で区切り文字を指定できます。
        - PostgreSQLの場合、デフォルトの区切り文字はパイプ（`\`）なので、`IFS=$'|'`としました。
- `department_map["$department_id"]="$department_name`
    - `department_id`をキーに、`department_name`を保存しています。

### 連想配列の内容を確認

```bash
# 連想配列の内容を確認
echo "$(declare -p department_map)"
```

`declare -p`コマンドを使うことで、連想配列の内容を確認できます。

`$(...)`はコマンド置換で、かっこ内で実行されたコマンドの出力をキャプチャして文字列として扱います。この場合、`declare -p department_map`の出力がキャプチャされて、最後に`echo`で出力されます。

このやり方知らなくて配列をループして一つずつ`echo`で確認してました…

### CSVファイルへの出力

`employees`テーブルからデータを取得し、部門名を連想配列から取得してCSVファイルに書き出します。

実行するSQLは`SELECT employee_id, employee_name, department_id FROM employees`です。

```bash
# CSVファイルに書き出し
{
  # ヘッダー行を書き込み
  echo "社員ID,社員名,部門ID,部門名"

  # 社員データを取得し、部門名を連想配列から取得する
  while IFS=$'|' read -r employee_id employee_name department_id; do
    department_name="${department_map[$department_id]}"
    echo "$employee_id,$employee_name,$department_id,$department_name"
  done < <(docker compose exec -T db-container bash -c "psql -d database_a -A -t -c \\"SELECT employee_id, employee_name, department_id FROM employees\\"")
} > output.csv
```

- `echo "社員ID,社員名,部門ID,部門名"`
    - CSVファイルのヘッダー行を書き込みます。
- `while IFS=$'|' read -r employee_id employee_name department_id; do ... done`
    - `employees`テーブルから取得したデータを読み込み、連想配列から`department_name`を取得してCSVに書き出します。
- `department_name="${department_map[$department_id]}"`
    - `department_id`をキーとして、対応する`department_name`を連想配列から取得します。

## スクリプト全体

スクリプトの全体は以下です。

```bash
#!/bin/bash

# 別DBの部門データを取得し、連想配列に格納する
declare -A department_map
while IFS=$'|' read -r department_id department_name; do
  department_map["$department_id"]="$department_name"
done < <(docker-compose exec -T db-container bash -c "psql -d database_b -A -t -c \\"SELECT department_id, department_name FROM departments\\"")

# 連想配列の内容を確認
echo "$(declare -p department_map)"

# CSVファイルに書き出し
{
  # ヘッダー行を書き込み
  echo "社員ID,社員名,部門ID,部門名"

  # 社員データを取得し、部門名を連想配列から取得する
  while IFS=$'|' read -r employee_id employee_name department_id; do
    department_name="${department_map[$department_id]}"
    echo "$employee_id,$employee_name,$department_id,$department_name"
  done < <(docker-compose exec -T db-container bash -c "psql -d database_a -A -t -c \\"SELECT employee_id, employee_name, department_id FROM employees\\"")
} > output.csv
```