STEP 4:データ型の選択と設計

🔢 STEP 4: データ型の選び方

適切なデータ型を選んで、効率的なデータベースを設計しよう

📋 このステップで学ぶこと
  • 数値型(INT、BIGINT、DECIMAL)の使い分け
  • 文字列型(VARCHAR、TEXT、CHAR)の選択基準
  • 日付時刻型(DATE、DATETIME、TIMESTAMP)の違い
  • データ型選択の判断基準
  • 実践:適切なデータ型の選定演習

学習時間の目安: 3時間 | 前提知識: STEP 1-3修了

🎯 1. データ型とは?

STEP 3で学んだER図の次のステップ

STEP 3では、ER図を使ってエンティティと属性を視覚化する方法を学びました。例えば、以下のようなエンティティを書きました。

┌─────────────────┐ │ 顧客 (Customer) │ ├─────────────────┤ │ PK: customer_id │ │ customer_name │ │ email │ │ age │ │ created_at │ └─────────────────┘

しかし、これだけでは実際のテーブルは作れません。なぜなら、各属性に「どんな種類のデータを入れるか」が決まっていないからです。

🤔 「customer_name」にはどんなデータが入る?
  • 文字? → 文字列型を使う
  • 何文字まで? → VARCHAR(100)VARCHAR(255)
🤔 「age」にはどんなデータが入る?
  • 数字? → 整数型を使う
  • 0〜150くらい? → TINYINTで十分?INTが必要?

このように、各属性に適切な「箱の種類」を決めるのが、このSTEP 4の目的です。

データ型の定義

📝 データ型とは

データ型(Data Type)=カラムに格納できるデータの種類とサイズを定義するもの

データ型は「箱の種類」だと考えるとわかりやすいです。
数字を入れる箱(INT、DECIMAL など)
文字を入れる箱(VARCHAR、TEXT など)
日付を入れる箱(DATE、DATETIME など)

なぜデータ型の選択が重要なのか?

適切なデータ型を選ぶことで、以下のメリットがあります。

💾 ストレージの節約

必要最小限のサイズを選ぶことで、無駄なスペースを使わない

⚡ パフォーマンス向上

適切な型は検索や計算が高速。文字列型で数値を扱うと遅くなる。

✅ データの整合性

型が決まっていれば、不正なデータが入らない

🔧 保守性の向上

型を見れば、どんなデータか一目でわかる

⚠️ 間違ったデータ型を選ぶと…
  • 無駄なストレージを消費する(VARCHAR(1000) で名前を格納など)
  • パフォーマンス低下(不適切な型で検索が遅くなる)
  • データの不整合(文字列型に数値を入れて計算ミス)
  • 後からの変更が困難(既存データの移行が大変)

データ型の3大カテゴリ

🎯 覚えるべき3つのカテゴリ

1. 数値型

整数、小数点を含む数値(INT、BIGINT、DECIMAL など)

2. 文字列型

テキストデータ(VARCHAR、CHAR、TEXT など)

3. 日付時刻型

日付、時刻、タイムスタンプ(DATE、DATETIME、TIMESTAMP など)

他にも、バイナリ型(画像、ファイル)、JSON型、ENUM型などがありますが、この3つが最も基本です。それぞれ詳しく見ていきましょう。

🔢 2. 数値型の使い分け

整数型(INT系)

整数を格納するための型です。サイズによっていくつか種類があります。

📊 整数型の種類と範囲(MySQL)
バイト数 範囲(符号付き) 範囲(符号なし)
TINYINT 1 -128 〜 127 0 〜 255
SMALLINT 2 -32,768 〜 32,767 0 〜 65,535
MEDIUMINT 3 -8,388,608 〜 8,388,607 0 〜 16,777,215
INT 4 約-21億 〜 約21億 0 〜 約42億
BIGINT 8 約-922京 〜 約922京 0 〜 約1844京

整数型の選び方

「どの整数型を選べばいいの?」という疑問に答えるため、用途別に整理しました。

TINYINT(1バイト)- 0〜255

用途:非常に小さな範囲の整数
例:年齢(0〜150)、月(1〜12)、ステータス(0〜9)、フラグ(0 or 1)

SMALLINT(2バイト)- 0〜65,535

用途:小さな範囲の整数
例:在庫数(数千個まで)、ページ番号、カウンター

INT(4バイト)- 0〜約42億 ⭐最も一般的

用途:一般的な整数(迷ったらINT
例:ID(主キー)、金額(円単位)、カウント数、年(西暦)

BIGINT(8バイト)- 0〜約1844京

用途:非常に大きな整数
例:大量データの ID、タイムスタンプ(ミリ秒単位)、SNSのいいね数

実数型(小数点を含む数値)

小数点を含む数値には、DECIMALFLOATDOUBLEがあります。

📊 実数型の比較
特徴 用途
DECIMAL 固定小数点数、正確な値を保持 金額、会計データ
FLOAT 浮動小数点数(単精度)、近似値、4バイト 科学計算(多少の誤差OK)
DOUBLE 浮動小数点数(倍精度)、近似値、8バイト 高精度な科学計算
💡 金額は必ずDECIMALを使う!

FLOATやDOUBLEは近似値なので、計算誤差が発生します。金額や会計データには、必ずDECIMAL型を使いましょう。

— ❌ 悪い例(誤差が発生する可能性) price FLOAT — ✅ 良い例(正確に値を保持) price DECIMAL(10, 2) — DECIMAL(10, 2) = 全体10桁、小数点以下2桁 — 例:99999999.99 まで格納可能

符号なし(UNSIGNED)

整数型には、UNSIGNED(符号なし)をつけることができます。マイナスの値が不要な場合に使います。

— 符号付き(デフォルト) age INT — -2,147,483,648 〜 2,147,483,647 — 符号なし(マイナスなし) age INT UNSIGNED — 0 〜 4,294,967,295
💡 UNSIGNED を使うべき場面
  • ✅ ID(主キー):マイナスのIDはありえない
  • ✅ 年齢、在庫数、カウント数:マイナスにならない
  • ✅ 範囲を2倍にしたい場合

📝 3. 文字列型の選択基準

主な文字列型

文字列を格納する型には、CHARVARCHARTEXTがあります。

📊 文字列型の比較
特徴 用途
CHAR(n) 固定長文字列、常にnバイト使用 郵便番号、国コードなど固定長
VARCHAR(n) 可変長文字列、実際の長さ分だけ使用 名前、メールアドレス、タイトル
TEXT 長い文字列、最大約64KB 記事本文、説明文、コメント
MEDIUMTEXT より長い文字列、最大約16MB 非常に長い文章

CHAR vs VARCHAR

CHARとVARCHARの違いを具体例で見てみましょう。

📝 CHAR(固定長)の例
country_code CHAR(2) — ‘JP’, ‘US’ など 【特徴】 ・常に2バイト使用(’JP’も’US’も同じサイズ) ・高速(サイズが固定なので計算しやすい) ・短い固定長データに最適 【用途】 ・国コード(2文字) ・都道府県コード(2文字) ・MD5ハッシュ(32文字固定)
📝 VARCHAR(可変長)の例
name VARCHAR(100) — 実際の長さに応じてサイズが変わる 【特徴】 ・実際の文字数分だけ使用(’山田’ は4バイト程度) ・無駄なスペースがない ・長さが変動するデータに最適 【用途】 ・名前(人によって長さが違う) ・メールアドレス ・住所 ・商品名

VARCHAR のサイズ選定

💡 VARCHAR(n) の n をどう決める?

基本原則:「必要十分な長さ」を設定する

— ❌ 悪い例 name VARCHAR(1000) — 過剰に大きい email VARCHAR(50) — 小さすぎる(長いメールアドレスが入らない) — ✅ 良い例 name VARCHAR(100) — 日本人の名前なら十分 email VARCHAR(255) — メールアドレスの最大長 title VARCHAR(200) — タイトルとして妥当 description TEXT — 長い説明文
📐 一般的なサイズの目安
  • 名前:VARCHAR(100)
  • メールアドレス:VARCHAR(255)
  • 電話番号:VARCHAR(20)
  • 郵便番号:CHAR(7) または VARCHAR(10)(ハイフンあり)
  • URL:VARCHAR(2048)(URLの最大長)
  • タイトル:VARCHAR(200)

TEXT型を使うタイミング

📝 TEXT型を使う場面
  • 記事本文(ブログ、ニュースなど)
  • 長い説明文
  • コメント、レビュー
  • JSON データ(大きい場合)
⚠️ TEXTの注意点
  • インデックスが貼れない(または制限がある)
  • ソートが遅い
  • デフォルト値を設定できない

📅 4. 日付時刻型の違い

主な日付時刻型

日付や時刻を格納する型には、DATEDATETIMETIMESTAMPがあります。

📊 日付時刻型の比較
フォーマット 範囲 用途
DATE YYYY-MM-DD 1000年〜9999年 誕生日、期限
TIME HH:MM:SS -838〜838時間 営業時間、所要時間
DATETIME YYYY-MM-DD HH:MM:SS 1000年〜9999年 投稿日時、予約日時
TIMESTAMP YYYY-MM-DD HH:MM:SS 1970年〜2038年 作成日時、更新日時

DATETIME vs TIMESTAMP

DATETIMEとTIMESTAMPは見た目は似ていますが、動作が異なります。

📝 DATETIME の特徴
published_at DATETIME 【特徴】 ・入力した値をそのまま保存 ・タイムゾーンの影響を受けない ・範囲が広い(西暦1000年〜9999年) ・8バイト使用 【用途】 ・イベント開催日時(その時刻で固定したい) ・予約日時 ・記事の公開予定日時
📝 TIMESTAMP の特徴
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP 【特徴】 ・UTC(協定世界時)で保存、読み取り時にタイムゾーン変換 ・自動更新が可能(ON UPDATE CURRENT_TIMESTAMP) ・範囲が狭い(1970年〜2038年)← 2038年問題 ・4バイト使用(軽量) 【用途】 ・作成日時(created_at) ・更新日時(updated_at) ・ログのタイムスタンプ

どちらを使うべきか?

💡 選択基準

TIMESTAMPを使う場合:

  • ✅ 作成日時、更新日時(自動更新したい)
  • ✅ ログのタイムスタンプ
  • ✅ グローバルなシステム(タイムゾーン変換が必要)

DATETIMEを使う場合:

  • ✅ イベント日時(タイムゾーン変換したくない)
  • ✅ 予約日時
  • ✅ 2038年以降のデータを扱う可能性
📝 一般的なパターン
CREATE TABLE posts ( post_id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(200), content TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, — 自動設定 updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, — 自動更新 published_at DATETIME — 手動設定 );
⚠️ TIMESTAMPの2038年問題

TIMESTAMPは、2038年1月19日以降の日付を扱えません
理由:UNIX時間(1970年1月1日からの秒数)を32ビット整数で表現しているため。

対策:2038年以降のデータを扱う可能性がある場合はDATETIMEを使う

🎯 5. データ型選択の判断基準

データ型を選ぶときの質問フロー

データ型を選ぶときは、以下の質問に答えていくと適切な型が見つかります。

🔍 ステップ1:どんな種類のデータ?
  • 数値 → ステップ2へ
  • 文字列 → ステップ3へ
  • 日付・時刻 → ステップ4へ
🔍 ステップ2:数値の場合
  • 小数点が必要?(金額、会計)→ DECIMAL
  • 小数点が必要?(科学計算、誤差OK)→ FLOAT/DOUBLE
  • 整数のみ? → 範囲に応じて TINYINT / SMALLINT / INT / BIGINT
🔍 ステップ3:文字列の場合
  • 長さが固定?(国コード、郵便番号など)→ CHAR(n)
  • 長さが変動?(名前、メールなど)→ VARCHAR(n)
  • 長い文章?(記事本文、説明文)→ TEXT
🔍 ステップ4:日付時刻の場合
  • 日付のみ?(誕生日など)→ DATE
  • 時刻のみ?(営業時間など)→ TIME
  • 日時で自動更新したい? → TIMESTAMP
  • 日時で手動設定? → DATETIME

実践例:ユーザーテーブルの設計

実際に要件を見ながら、データ型を選んでみましょう。

📝 要件に応じたデータ型選択
CREATE TABLE users ( — ID: 自動採番、マイナスなし → INT UNSIGNED AUTO_INCREMENT user_id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, — ユーザー名: 可変長、最大50文字 → VARCHAR(50) username VARCHAR(50) NOT NULL UNIQUE, — メールアドレス: 可変長、最大255文字 → VARCHAR(255) email VARCHAR(255) NOT NULL UNIQUE, — パスワードハッシュ: 固定長(60文字のbcrypt)→ CHAR(60) password_hash CHAR(60) NOT NULL, — 誕生日: 日付のみ → DATE birth_date DATE, — 性別: 小さな範囲の値(0=不明, 1=男性, 2=女性)→ TINYINT UNSIGNED gender TINYINT UNSIGNED, — 自己紹介: 長文 → TEXT bio TEXT, — ポイント残高: 整数(マイナスなし)→ INT UNSIGNED points INT UNSIGNED DEFAULT 0, — 登録日時: 自動設定 → TIMESTAMP created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, — 更新日時: 自動更新 → TIMESTAMP updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, — 最終ログイン: 日時(手動更新)→ DATETIME last_login_at DATETIME );

⚙️ 6. テーブル作成時のオプション

CREATE TABLE文を書くとき、データ型以外にも設定すべき項目があります。初心者がつまずきやすいポイントを解説します。

AUTO_INCREMENT(自動採番)

📝 AUTO_INCREMENTとは

AUTO_INCREMENT=データを挿入するたびに、自動的に1ずつ増える番号を振ってくれる機能

主キー(ID)に使うことで、INSERT時にIDを指定する必要がなくなります。

— AUTO_INCREMENTの例 CREATE TABLE users ( user_id INT AUTO_INCREMENT PRIMARY KEY, — 自動で1, 2, 3…と増える username VARCHAR(50) ); — INSERTするとき、user_idは指定しなくてOK INSERT INTO users (username) VALUES (‘山田太郎’); — user_id = 1 INSERT INTO users (username) VALUES (‘佐藤花子’); — user_id = 2 INSERT INTO users (username) VALUES (‘鈴木一郎’); — user_id = 3
✅ AUTO_INCREMENTのメリット
  • IDの管理が楽(プログラムで番号を生成する必要がない)
  • 重複しない一意の値が自動で付与される
  • 削除しても番号は再利用されない(1, 2, 3を削除しても次は4)

ENGINE=InnoDB(ストレージエンジン)

📝 ENGINEとは

ENGINE=MySQLがデータを保存・管理する仕組み(エンジン)を指定する

車で言えば、「ガソリンエンジン」か「電気モーター」かを選ぶようなものです。

🔧 主なストレージエンジン
エンジン 特徴 用途
InnoDB トランザクション対応、外部キー対応 現在の標準(迷ったらこれ)
MyISAM 高速な読み取り、トランザクション非対応 読み取り専用のログテーブルなど
✅ InnoDBを使う理由
  • トランザクション:複数の処理を「全部成功」か「全部取り消し」にできる
  • 外部キー制約:テーブル間の整合性を保証できる
  • 行レベルロック:同時アクセスでも高速に処理できる
  • クラッシュリカバリ:障害時にデータを復旧できる
— ENGINE=InnoDB の指定 CREATE TABLE orders ( order_id INT AUTO_INCREMENT PRIMARY KEY, customer_id INT NOT NULL, total_amount DECIMAL(10, 2), FOREIGN KEY (customer_id) REFERENCES customers(customer_id) — 外部キー ) ENGINE=InnoDB; — InnoDBを使用(外部キーが使える)

DEFAULT CHARSET=utf8mb4(文字コード)

📝 CHARSETとは

CHARSET(文字コード)=文字をどのように保存するかを決める設定

日本語や絵文字を正しく保存するために重要です。

⚠️ utf8 ではなく utf8mb4 を使う理由

MySQLの「utf8」は3バイト文字までしか対応していません。
絵文字(😀🎉👍)は4バイト文字なので、「utf8」では保存できません。

utf8mb44バイト文字に対応しているので、絵文字も正しく保存できます。

— 絵文字も保存できる設定 CREATE TABLE posts ( post_id INT AUTO_INCREMENT PRIMARY KEY, content TEXT ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; — これで絵文字を含む投稿も保存できる INSERT INTO posts (content) VALUES (‘今日は楽しかった!😊🎉’);
💡 テーブル作成の標準テンプレート

迷ったら、以下のテンプレートを使いましょう:

CREATE TABLE テーブル名 ( id INT AUTO_INCREMENT PRIMARY KEY, — 自動採番の主キー — ここにカラムを定義 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; — ENGINE=InnoDB:トランザクション・外部キー対応 — CHARSET=utf8mb4:日本語・絵文字対応

📝 STEP 4 のまとめ

✅ このステップで学んだこと
  • 整数型:TINYINT、SMALLINT、INT、BIGINT(サイズに応じて選択)
  • 実数型:金額はDECIMAL、科学計算はFLOAT/DOUBLE
  • 文字列型:固定長はCHAR、可変長はVARCHAR、長文はTEXT
  • 日付時刻型:自動更新はTIMESTAMP、手動設定はDATETIME
  • AUTO_INCREMENT:主キーに使うと自動で連番が振られる
  • ENGINE=InnoDB:トランザクション・外部キー対応の標準エンジン
  • CHARSET=utf8mb4:日本語・絵文字を正しく保存できる文字コード
💡 迷ったらこれ!

・整数はINT
・文字列はVARCHAR
・日時はTIMESTAMP
から始めて、必要に応じて調整しましょう!

🎯 次のステップへ

次のSTEP 5では、主キーと外部キーについて詳しく学び、テーブル間の関係を実装する方法を学びます!

📝 練習問題

問題 1 基礎

「年齢」を格納するカラムに最適なデータ型を選んでください。

【解答】TINYINT UNSIGNED

理由:
・年齢は0〜150程度の範囲
・TINYINT UNSIGNED は 0〜255 をカバー(十分)
・1バイトで済むので効率的
・マイナスの年齢はないので UNSIGNED

問題 2 基礎

商品の価格(円単位、小数点あり)を格納するのに最適なデータ型を選んでください。

【解答】DECIMAL(10, 2)

理由:
・金額は正確な値が必要(誤差が許されない)
・DECIMAL は固定小数点数で正確
・DECIMAL(10, 2) = 最大99,999,999.99円
・FLOATやDOUBLEは誤差が発生するので不適切

問題 3 基礎

メールアドレスを格納するのに最適なデータ型を選んでください。

【解答】VARCHAR(255)

理由:
・メールアドレスは可変長(人によって長さが違う)
・RFC標準では最大254文字(255で十分)
・CHAR は無駄なスペースを使うので不適切
・TEXT は大きすぎる&インデックスに制限

問題 4 応用

郵便番号(例:123-4567)を格納するのに最適なデータ型を2つ挙げてください。

【解答】

方法1:CHAR(8)(ハイフンあり「123-4567」)
方法2:CHAR(7)(ハイフンなし「1234567」)

理由:どちらも固定長なので CHAR が適切。VARCHAR(10) でも可能だが、固定長データには CHAR の方が効率的。

問題 5 応用

ブログ記事の本文を格納するのに最適なデータ型を選んでください。

【解答】TEXT または MEDIUMTEXT

理由:
・記事本文は長いテキスト(数千〜数万文字)
・TEXT: 最大65,535文字(約64KB)← 通常のブログ記事なら十分
・MEDIUMTEXT: 最大16,777,215文字(約16MB)← 非常に長い記事
・一般的には TEXT で十分

問題 6 応用

「作成日時」と「更新日時」を自動管理したい場合、どのように定義しますか?

【解答】
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

理由:
・TIMESTAMP は自動更新が可能
・created_at: レコード作成時に自動設定
・updated_at: レコード更新時に自動更新

問題 7 発展

なぜ金額を格納するのに FLOAT ではなく DECIMAL を使うべきですか?

【解答】

FLOAT/DOUBLE は浮動小数点数で「近似値」を保存するため、計算誤差が発生するからです。

— ❌ FLOAT の場合(近似値) 0.1 + 0.2 = 0.30000001192092896(誤差!) — ✅ DECIMAL の場合(正確な値) 0.1 + 0.2 = 0.3(正確)

金額や会計データなど、誤差が許されないデータには必ず DECIMAL を使いましょう。

問題 8 発展

以下のテーブル設計の問題点を3つ以上指摘してください。

CREATE TABLE products ( product_id VARCHAR(50), product_name VARCHAR(10000), price FLOAT, stock_quantity VARCHAR(100), created_at VARCHAR(255) );
【問題点】
  1. product_id VARCHAR(50)
    → IDは通常 INT UNSIGNED AUTO_INCREMENT が適切
  2. product_name VARCHAR(10000)
    → 商品名に10000文字は過剰。VARCHAR(200)程度で十分
  3. price FLOAT
    → 金額は DECIMAL を使うべき(誤差が発生する)
  4. stock_quantity VARCHAR(100)
    → 在庫数は整数なので INT UNSIGNED が適切
  5. created_at VARCHAR(255)
    → 日時は TIMESTAMP または DATETIME を使うべき

❓ よくある質問

Q1: VARCHAR の長さは、どこまで細かく設定すべきですか?

「必要十分な長さ」を設定しましょう。厳密に最小限にする必要はありませんが、極端に大きくするのも避けましょう。

後から拡張するのは簡単ですが、縮小は既存データに影響するので、最初は少し余裕を持たせてOKです。

Q2: CHAR と VARCHAR、どちらを使うか迷ったらどうすればいいですか?

迷ったら VARCHAR を使いましょう。CHAR は固定長なので、長さが変動するデータには無駄が多いです。

CHAR を使うのは、国コード(2文字)、郵便番号(7文字)、MD5ハッシュ(32文字)など、長さが完全に固定のデータのみです。

Q3: INT と BIGINT、どちらを選ぶべきですか?

基本的には INT で十分です。INT UNSIGNED は 0〜約42億 の範囲を持ち、ほとんどのケースで十分です。

BIGINT を使うのは、42億を超える可能性がある場合(SNSのいいね数、大量データのIDなど)のみです。

Q4: TIMESTAMP と DATETIME、結局どちらを使えばいいですか?

用途によって使い分けましょう。

  • created_at、updated_at(自動更新したい)→ TIMESTAMP
  • イベント日時、予約日時(固定したい)→ DATETIME

Q5: データ型は後から変更できますか?

可能ですが、データ量が多いと大変です。ALTER TABLE でデータ型を変更できますが、データ量が多いと時間がかかり、テーブルロックが発生する場合もあります。

そのため、最初に適切な型を選ぶことが重要です。

📝

学習メモ

データベース設計・データモデリング - Step 4

📋 過去のメモ一覧
#artnasekai #学習メモ
LINE