【効率化】Bashで大量データをデータベースに一括登録

## 初めに
Next.jsでLeafletを用いた地図アプリを開発中、GeoJSONデータを地図上に表示する必要がありました。しかし、GeoJSONデータの量が5万件を超えており、これをGitHubに直接プッシュするのは避けたかったため、データを手動で挿入する方法を選びました。ただし、バックエンドにデータベース登録用の処理を記述すると、DockerのDBコンテナを起動する手間や、設定が増える煩わしさが生じます。そのため、簡単にデータをテーブルに保存できるよう、Bashスクリプトを作成しました。

自分用として記事に残す程度なので雑記事になります。

このスクリプトでは、以下の国土数値情報サイトからダウンロードしたデータを使用し、データベースに保存します。

https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-A33-2023.html
## 前提条件
以下の条件を満たしている必要があります。

①Dockerの設定
 - docker-compose.ymlにPostgreSQLのDBコンテナが記述されており、docker compose up で起動できる状態であること

②技術スタック 
 - Nest.js、Prisma、Dockerを使用していること

③Prismaのテーブル設計
 - テーブル構造は以下の通りであること


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2749225/46bdea41-913e-0052-1b6a-3b0bde3f4bba.png)


## 目次
1. scriptsの作成
2. 実行して動作確認


## 1. scriptsの作成
ルートディレクトリにscriptsディレクトリを作成してその中にimport_data.shを作成して、以下のように実装してください。

```sh:import_data.sh
#!/bin/bash

# 引数からファイル名とプロパティキーを取得
GEOJSON_FILE="safety_data/$1.geojson" # "water" または "land" など
PROPERTIES_PREFIX=$2  # "A40" または "A33" など

# PostgreSQL コンテナ名
CONTAINER_NAME="db"

# データベース情報
DB_USER="postgres"
DB_NAME="leaflet"

# コンテナにファイルが存在するか確認
if [ ! -f "$GEOJSON_FILE" ]; then
    echo "GeoJSON file not found: $GEOJSON_FILE"
    exit 1
else 
    echo "GeoJSON file found: $GEOJSON_FILE"
fi

# コンテナ立ち上げ
echo "Starting containers..."
docker compose up -d

running_containers () {
    DOCKER_STATUS=$(docker compose ps -q | xargs docker inspect -f '{{.State.Status}}')
    if echo "$DOCKER_STATUS" | grep -qv "running"; then
        return 1
    else
        return 0
    fi
}

# コンテナが起動するまで待機
while ! running_containers; do
    sleep 1
done

# PostgreSQLの起動待機
echo "Waiting for PostgreSQL to be ready..."
until docker exec $CONTAINER_NAME pg_isready -U $DB_USER ; do
    sleep 3
done

# GeoJSONデータをコンテナにコピー
echo "Copying GeoJSON file to container's safety_data directory..."
docker exec "$CONTAINER_NAME" mkdir -p /safety_data
if docker cp "$GEOJSON_FILE" "$CONTAINER_NAME:/safety_data/$1.geojson"; then
    echo "GeoJSON file successfully copied to /safety_data directory in container."
else
    echo "Failed to copy GeoJSON file to /safety_data directory in container." >&2
    exit 1
fi

# マイグレーションの実行
echo "Running migrations..."
npx prisma migrate dev --name init
sleep 5

# GeoJSONデータをPostgreSQLにインポート
echo "Importing GeoJSON data into PostgreSQL..."
docker exec -i $CONTAINER_NAME psql -U $DB_USER -d $DB_NAME <<EOF
DO \$do\$
    DECLARE
        feature JSONB;
        properties JSONB;
        geometry JSONB;
    BEGIN
        FOR feature IN 
            SELECT jsonb_array_elements(content->'features') AS feature
            FROM (SELECT pg_read_file('/safety_data/$1.geojson')::jsonb AS content) AS t
        LOOP
            properties := feature -> 'properties';
            geometry := feature -> 'geometry';
            
            INSERT INTO "Fukuoka_${1}_info"(prefecture, prefecture_code, flood_level, geometry)
            VALUES (
                properties->>'${PROPERTIES_PREFIX}_001',
                properties->>'${PROPERTIES_PREFIX}_002',
                properties->>'${PROPERTIES_PREFIX}_003',
                geometry
            );
        END LOOP;
    END
\$do\$;
EOF

echo "GeoJSON data import completed successfully."

```


## 2.実行して動作確認

ルートディレクトリに safety_data ディレクトリを作成し、そこにGeoJSONファイルを配置します。

その後、以下のコマンドをターミナルで実行します。
land と A33 はスクリプトに渡す引数です。このスクリプトは、land.geojson ファイル以外にも対応できるように引数を動的に受け取る設計になっています。
なお、A33 は、land.geojson ファイルの中身を確認するとカラム名がすべて A33 で始まっていることから、適切なカラム名を生成するために利用されています。
```
bash ./scripts/import_data land A33
```

## 終わりに

この手法により、手動でGeoJSONデータをデータベースに挿入する際の煩雑な作業を軽減できます。また、import_data.sh を活用することで、同様のデータを効率的に挿入することが可能です。
ここまで読んで下さった方ありがとうございました。