11
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

初心者がDB設計を学ぶ方法~Excel→ERD→DB作成→ETLによる入力まで一通りやってみた~

11
Last updated at Posted at 2026-03-29

はじめに

私は化学メーカーの現場で技術開発を行っています。
これまで業務の一環で、部署の実験データを集約するデータベースの構築にも関わりました。
ただし、私が一人ですべて行ったのではなく、デジタル専門部門のサポートを受けながら行いました。

役割分担は以下の通りです。

  • 私:データベースのロジック設計や、入力フォーマットの整理を担当
  • デジタル部門の担当者:システム構築やETL処理の実装を担当

この経験を通じて、ロジックの設計方法はある程度理解できましたが、

  • 実際のデータベース構築はどのような全体像なのか
  • ETLではどのような処理を書くべきなのか

といった部分については、まだ十分に理解できていませんでした。

そこで本記事では、自己研鑽として自宅でデータベース構築を、初心者の立場で一通り実践しながら学んだ内容をまとめたいと思います。

具体的には

  • ERD設計
  • DDLによるテーブル構築
  • データ投入(ETL)
  • 簡単な可視化

までを実際に行いながら、「データベースの学び方」を整理してみました。

本記事の想定読者

本記事は以下のような方を想定しています。

  • ERDなど、データベース設計の基礎は一通り学んだ
  • 自分でデータベースを作りながら理解を深めたい

そのため、ERDの書き方などの基礎的な説明は行いません。
また、本記事は私自身の学習記録(備忘録)も兼ねていますので、その点はご了承ください。

DB構築の流れ

⓪ データベース化する題材探し
① ERDを作る(+データを準備する)
② VSCodeでデータベース環境を準備する
③ DDLでテーブル構造を作成する
④ データを投入する
⑤ データを別のアプリで可視化する

という順番に行っていきます。

⓪ データベース化する題材探し

まずは、「どんなデータベースを作るか」を決めます。

個人的には、最初の題材は以下のようなものが良いと思います。
・自分が興味を持てる、身近なテーマ
・ERDがあまり複雑にならないもの

特に2つ目は重要です。
最初から凝った複雑なERDを作ろうとすると、途中で挫折してしまう可能性があります。
まずは「実際にデータベースを構築すること」をゴールにし、慣れてきたらより複雑なERDのデータベース設計に挑戦するのがよいと思います。

私のデータベース

私は 「東野圭吾データベース」 を作ることにしました。
実は、私自身が東野圭吾さんの小説の大ファンで、この記事を執筆した2026年3月現在、Audible含めた107冊をすべて読破しています。

今回は東野圭吾さんの作品をDBにし、

  • 新作が出たときの更新や、作品ごとの評価点の管理
  • シリーズごとの平均評価スコアの可視化

などができるようにしてみたいと思いました。

① ERDを作る(+データを準備する)

①-1 ERDの準備

実際に、作成したERDがこちらです。

image.png

今回はデータベース構築の全体像をつかむことが目的なので、かなりシンプルなERDにしました。
また、ERDを書くにはVSCodeのDraw.ioという拡張機能を使うと便利です。

image.png

①-2 データの準備

今回は、一度Excelにデータをまとめてからデータベースへ投入する方式にしました。
もちろんSQLで直接INSERTすることも可能なので、自分の用途に合わせて選択してください。
Excelでは、ERDの各エンティティに対応する形でシートを分けて作成しました。

KH_bookのシート:東野圭吾の作品名や、評価を入れる

image.png
(一部を示しています)

ここで、ID以外のカラムを見てみましょう。
・Book_name:作品名
・Published_year:出版年(最初の出版年を表しており、文庫化などで更新することはない)
・Books_type:長編、短編集、エッセイなど
・My_evaluation:私の作品評価。5段階評価で、数字が大きいほど面白かったことを示す。

今回は、エンティティ内のカラム名と、Excel表の列名を一致させました。
(※のちに列名を変換することも可能です)
英語の大文字小文字飲の不一致も基本は対応できますが、一部例外があり注意が必要です。

また、Primary Key (PK)とForeign Key (FK)ともにシリアルにせず、自分でExcel上で附番することにしました。

同様に、ほかのエンティティも別シートに作成します。

Publisherのシート:出版社情報を入れる

image.png
(一部を示しています)

Seriesのシート:シリーズ情報を入れる

image.png
(一部を示しています)

ここまでで
・データベースの設計図であるERDの完成
・データベースに入れるExcelデータの準備
ができました。
次は、実際にデータベース環境を準備していきます。

② VSCodeでデータベース環境を準備する

②-1 フォルダの準備

データベース構築に必要なデータやコードなどを入れるフォルダ類を、自分のエクスプローラーに準備します。
今回のフォルダ構成は以下です。

東野圭吾DB
├ data
├ ddl
├ etl
└ README.md

まず、東野圭吾DBというフォルダを作り、その中に上記3つのフォルダと、READMEのファイルを格納していきます。
フォルダの役割としては

  • data:入れたい東野圭吾さんの書籍情報が入ったExcelを入れる
  • ddl:DDL処理を行うSQLプログラムのソースコードを入れる
  • etl:ETL処理を行うPythonプログラムのソースコードを入れる

です。

②-2 VSCodeでの操作

次に、VSCodeを開き作った東野圭吾DBのフォルダを開くとこのような画面になります。

image.png
(この記事執筆の時点ではデータベースが完成しており、私の画面ではほかのフォルダも入っております。)

データベースを準備するために、実際にVSCodeのターミナルを使用して操作を行いましょう。
今回はPostgreSQLを使用するため、まだの人はVSCodeの拡張機能をインストールしてください。

image.png

拡張機能のインストールができたら、VSCodeのターミナルで

psql -U postgres -P pager=off -c "CREATE DATABASE books;"

を入力し実行することにより、PostgreSQLでbooksという名前のデータベースが準備されます。(名前は適宜変更してください)

ここまでで、データベースの箱を作ることができました。

③ DDLでテーブル構造を作成する

③-1 SQLの作成

そもそも、SQLには大きく3種類があります。

種類 名前 何をするか
DDL CREATE / DROP 構造を作る
DML INSERT / UPDATE データを入れる
DQL SELECT データを見る

DDL(Data Definition Language)は、データベースの構造を定義するためのSQLです。

それでは、実際にDDLのコードを作ってVSCodeで実行し、データベースにテーブルを作ってみましょう。
作ったddlのフォルダに次のようなSQLファイル(名前:001_create_tables.sql)を作成します。

-- master: publisher
create table if not exists publisher (
  publisher_id text primary key,          -- e.g., P001
  publisher_name text not null unique
);

-- master: series
create table if not exists series (
  series_id text primary key,             -- e.g., S001
  series_name text not null unique
);

-- books (1 row = 1 work)
create table if not exists kh_books (
  book_id text primary key,               -- e.g., B001
  book_name text not null unique,         -- e.g., 白夜行
  published_year int check (published_year between 1900 and 2100),
  publisher_id text not null references publisher(publisher_id),
  series_id text references series(series_id),
  book_type text not null,                
  my_evaluation int check (my_evaluation between 1 and 5)
);

create index if not exists idx_kh_books_publisher_id on kh_books(publisher_id);
create index if not exists idx_kh_books_series_id on kh_books(series_id);

このDDLプログラムの特徴は、エンティティの従属関係にあります。
kh_booksのエンティティには、出版社IDやシリーズIDが書かれています。
すなわち、先に出版社情報が書かれたpublisherエンティティや、シリーズ情報が書かれたseriesエンティティが存在しないと、kh_booksのエンティティが存在し得ないことになります。
よって、publisher / series → kh_books の順で依存関係があり、publisherやseriesは親エンティティ、kh_booksは子エンティティとなります。

③-2 VSCodeでの操作

DDLプログラムができたら、VSCodeのターミナルで実行します。

psql -U postgres -d books -f ddl/001_create_tables.sql

テーブルができたかを確認するには、

\dt

を実行すると、テーブル一覧を確認できます。以下のようなログが出力されます。

image.png

ここまでで、
・データベースの箱ができた
・箱の中にテーブルができた
・しかし、テーブルにまだデータが入っていない
という状況です。次からは、実際にテーブルにデータを入れていきましょう。

④ データを投入する

④-1 Pythonプログラムの作成

ここではETL処理を行い、データをデータベースに投入します。
ETLとは以下の略です。

  • Extract(抽出)
  • Transform(変換)
  • Load(格納)

今回はPythonを使ってETLを実装します。
ETL流れは、Excel → CSV → Python(copy_expert) → PostgreSQLと変換されていきます。

※補足
本記事ではCSVファイルを作成してからデータを投入していますが、
PythonのCOPY機能を使うことで、CSV形式のデータを直接データベースに流し込むことも可能です。
今回は処理の分かりやすさと再利用性を考え、CSVファイルを経由する構成にしています。

from pathlib import Path
import pandas as pd
import psycopg2


# =============================
# パス設定
# =============================
PROJECT_DIR = Path(__file__).resolve().parents[1]
XLSX_PATH = PROJECT_DIR / "data" / "keigo-higashino-db.xlsx"
CSV_DIR = PROJECT_DIR / "data" / "csv"

TARGET_TABLES = ["publisher", "series", "kh_books"]


# =============================
# DB接続設定
# =============================
DB_CONFIG = {
    "dbname": "books",
    "user": "postgres",
    "password": "パスワード入力",
    "host": "localhost",
    "port": "ポート番号を入力",
}


# =============================
# 補助関数
# =============================
def normalize_colname(col: str) -> str:
    """Excel列名をDB向けに正規化する"""
    return str(col).strip().lower()


def read_excel_sheet(xlsx_path: Path, sheet_name: str) -> pd.DataFrame:
    """
    Excelの1シートを読み込む
    前提:
    - 1行目は空白
    - 2行目がヘッダー
    """
    df = pd.read_excel(
        xlsx_path,
        sheet_name=sheet_name,
        header=1,
    )

    # 完全空行・空列を削除
    df = df.dropna(how="all")
    df = df.dropna(axis=1, how="all")

    # 列名を正規化
    df.columns = [normalize_colname(c) for c in df.columns]

    # 文字列列の前後空白を削除
    for col in df.columns:
        if df[col].dtype == object:
            df[col] = df[col].apply(
                lambda x: x.strip() if isinstance(x, str) else x
            )

    # 評価列を整数化(NULLはそのまま)
    if "my_evaluation" in df.columns:
        df["my_evaluation"] = pd.to_numeric(
            df["my_evaluation"], errors="coerce"
        ).astype("Int64")

    # 出版年も整数化(NULLはそのまま)
    if "published_year" in df.columns:
        df["published_year"] = pd.to_numeric(
            df["published_year"], errors="coerce"
        ).astype("Int64")

    return df


def export_csvs() -> dict[str, Path]:
    """
    Excelから必要シートを読み込み、CSVを書き出す
    戻り値: {table_name: csv_path}
    """
    if not XLSX_PATH.exists():
        raise FileNotFoundError(f"Excelファイルが見つかりません: {XLSX_PATH}")

    CSV_DIR.mkdir(parents=True, exist_ok=True)

    xls = pd.ExcelFile(XLSX_PATH)
    sheet_map = {name.lower(): name for name in xls.sheet_names}

    csv_paths = {}

    for table in TARGET_TABLES:
        if table not in sheet_map:
            raise ValueError(
                f"必要なシート '{table}' が見つかりません。"
                f" Excel内のシート一覧: {xls.sheet_names}"
            )

        actual_sheet_name = sheet_map[table]
        df = read_excel_sheet(XLSX_PATH, actual_sheet_name)

        csv_path = CSV_DIR / f"{table}.csv"
        df.to_csv(csv_path, index=False, encoding="utf-8")
        csv_paths[table] = csv_path

        print(f"[OK] CSV出力: {csv_path}  rows={len(df)}")

    return csv_paths


def truncate_tables(conn) -> None:
    """既存データを全削除(Excelを唯一の正とするため)"""
    with conn.cursor() as cur:
        cur.execute("TRUNCATE TABLE publisher, series CASCADE;")
    conn.commit()
    print("[OK] 既存データをTRUNCATEしました")


def load_csv(conn, table_name: str, csv_path: Path) -> None:
    """CSVを1テーブルにロードする"""
    with conn.cursor() as cur:
        with open(csv_path, "r", encoding="utf-8") as f:
            cur.copy_expert(
                f"COPY {table_name} FROM STDIN WITH CSV HEADER",
                f
            )
    conn.commit()
    print(f"[OK] {table_name} にロードしました")


def validate_counts(conn) -> None:
    """件数確認"""
    with conn.cursor() as cur:
        for table in TARGET_TABLES:
            cur.execute(f"SELECT COUNT(*) FROM {table};")
            count = cur.fetchone()[0]
            print(f"[CHECK] {table}: {count} rows")


def main():
    print("=== Excel -> CSV 変換開始 ===")
    csv_paths = export_csvs()

    print("=== PostgreSQL 接続開始 ===")
    conn = psycopg2.connect(**DB_CONFIG)

    try:
        print("=== DB初期化開始 ===")
        truncate_tables(conn)

        print("=== CSVロード開始 ===")
        for table in TARGET_TABLES:
            load_csv(conn, table, csv_paths[table])

        print("=== 件数確認 ===")
        validate_counts(conn)

        print("=== ETL完了 ===")

    finally:
        conn.close()
        print("[OK] DB接続を閉じました")


if __name__ == "__main__":
    main()

今回のPythonスクリプトでは以下を行っています。

  • カラム名の正規化、不要な空白の除去等のクリーニング
  • Excel → CSV変換
  • DBの既存データ削除
  • CSVを各テーブルへロード

※データ更新を想定し、「全削除→再投入」する設計にしています。

④-2 VSCodeでの操作

作ったETLコードを実行してみましょう。
VSCodeのターミナルで、必要に応じてPythonライブラリをターミナルに入れてください

pip install pandas openpyxl psycopg2-binary

その後、以下を実行します。(sync_from_excelはソースコードのファイル名です)

python etl/sync_from_excel.py

正常に実行されると、以下のようなログが出ます。
=== Excel -> CSV 変換開始 ===
[OK] CSV出力: ...
[OK] DBロード: publisher
[OK] DBロード: series
[OK] DBロード: kh_books

次に、データベースにデータが入っているかを確認しましょう。
VSCodeのターミナルで、データベースに接続します。

psql -U postgres -d books

テーブルの中身を確認します。

SELECT * FROM kh_books;

以下のようなログが出力されます。

image.png

ここまでで、
・データベース内に実際のデータを入れることができました
・データが更新されてもデータベースを更新できるETLプログラムを整えることができました

最後に、このVSCodeのターミナルだとデータの可視化がしづらいので、外部アプリを使って可視化を行いましょう。

⑤ データを別のアプリで可視化する

VSCodeのターミナルでもデータ確認はできますが、可視化や分析には外部ツールを使うと便利です。
今回は、データベースクライアントとして DBeaver を使用します。
(pgAdminも有名ですが、操作性の観点でDBeaverを選びました)

2つのアプリの主な比較です。

項目 DBeaver pgAdmin
用途 DBを操作・分析するツール PostgreSQL専用管理ツール
対応DB ほぼ全部(PostgreSQL, MySQL, SQLite等) PostgreSQLのみ
UI シンプルで速い やや重い
SQL作業 かなり快適 普通
データ閲覧 Excelみたいに見やすい やや使いにくい

⑤-1 アプリインストール

まず、DBeaverをインストールしてみましょう。
・公式サイト:https://dbeaver.io/download/
・インストール方法:https://www.kkaneko.jp/tools/win/dbeaver.html

インストール後、アプリを開き以下の手順を行ってください。

⑤-2 PostgreSQLに接続する

① 上のメニュータブのデータベース⇒新しい接続を選択
② PostgreSQL選択
③ パスワードを入れて接続

⑤-3 データベースを選択する

接続後、対象のデータベース(今回は books)に接続します。

① 左のpostgreで右クリック⇒接続 編集
② Dastabeseの欄に、自分のデータベース名(今回はbooks)を入力してOKをクリック

image.png

⑤-4 テーブルの中身を確認する

見たいテーブルをダブルクリックし、タブを選択することで

  • プロパティ
  • データ
  • ER図

を確認することができます。

image.png

⑤-5 SQLを書いて分析する

Kh_booksのテーブルで、上にあるSQLをクリックし、SQLエディタを開きコーディングを行うことができます。
image.png

実際のSQLコードは以下です。
今回は、シリーズごとの平均スコアの集計を行ってみたいと思います。

-- =====================================
-- Series Analysis
-- 各シリーズの冊数と平均評価を計算
-- =====================================

SELECT
    s.series_name,
    COUNT(*) AS book_count,                 -- シリーズの冊数
    ROUND(AVG(k.my_evaluation), 1) AS avg_evaluation  -- 評価平均(小数1位)
FROM kh_books k
JOIN series s
    ON k.series_id = s.series_id
WHERE k.series_id IS NOT NULL               -- シリーズなし作品を除外
GROUP BY s.series_name
ORDER BY avg_evaluation DESC;               -- 評価の高い順

SQLを書いた後は、左にあるオレンジの実行ボタン▶を押すか、Ctrl+Enterで実行できます。

image.png

このように、各シリーズごとの平均スコアを可視化することができました。

作ったSQLソースコードは、エクスプローラーに保存しておくとよいでしょう。
また、Ctrl+Oで検索画面になり、開きたいSQLファイルを探すことができます。

さいごに

今回は、データベース設計の学び方として、

・ERDの作成
・DDLによるテーブル構築
・ETLによるデータ投入
・外部ツールによる可視化

まで、一連の流れを実際に手を動かしながら整理しました。

データベース設計は、本などだけではイメージしづらい部分も多いですが、
今回のように「小さなテーマで実際に構築してみる」ことで、全体像の理解が大きく進みました。

特に、

・VSCodeでのデータベースの作り方
・ETLによる処理

は、実際にやってみて初めて理解できる部分だと感じました。
本記事が、データベース設計をこれから学ぶ方の参考になれば幸いです。

11
26
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?