リレーショナル DB
テーブル(表)とリレーション(関係)でデータを表現する。スキーマ + SQL + トランザクションの三位一体で、 50 年以上現役の枯れた技術。
基本概念
- テーブル: 列(カラム)と行(レコード)の表
- 列: 名前 + 型 + 制約
- 行: 1 件のデータ
- 主キー(PRIMARY KEY): 行を一意に識別する列
- 外部キー(FOREIGN KEY): 別テーブルの主キーへの参照
- スキーマ: テーブル定義の集まり
- 制約(CONSTRAINT): NOT NULL / UNIQUE / CHECK / FOREIGN KEY
テーブル例
+-----+-----------------+--------+
| id | email | age |
+-----+-----------------+--------+
| 1 | alice@ex.com | 30 |
| 2 | bob@ex.com | 25 |
| 3 | carol@ex.com | NULL |
+-----+-----------------+--------+
主要 RDB の比較
| PostgreSQL | MySQL | SQLite | SQL Server | |
|---|---|---|---|---|
| 用途 | 万能・現代の第一候補 | Web 全般 / WordPress | 1 ファイル / 組み込み | 企業 / Microsoft 環境 |
| JSON | 強い (jsonb) | 普通 | 普通 | 普通 |
| 拡張 | 圧倒的(pgvector / postgis) | 少ない | 少ない | 商用 |
| ライセンス | BSD(自由) | GPL / 商用 | Public Domain | 商用 |
| トランザクション | 強い | InnoDB で OK | OK | 強い |
主要なデータ型
| カテゴリ | 例 | 用途 |
|---|---|---|
| 整数 | SMALLINT / INT / BIGINT | ID、カウント |
| シリアル | SERIAL / BIGSERIAL(Postgres) | 自動採番 |
| 小数 | NUMERIC(p,s) / DECIMAL | 金額(精度厳密) |
| 浮動小数 | REAL / DOUBLE PRECISION | 科学計算(誤差あり) |
| 文字列 | TEXT / VARCHAR(n) / CHAR(n) | 文字列。Postgres は TEXT 推奨 |
| 真偽 | BOOLEAN | true / false |
| 日時 | DATE / TIME / TIMESTAMP / TIMESTAMPTZ | TIMESTAMPTZ 推奨(タイムゾーン付き) |
| UUID | UUID | 分散システムで衝突しない ID |
| JSON | JSON / JSONB | 柔軟データ。JSONB 推奨 |
| 配列 | TEXT[] / INTEGER[] | Postgres のみ |
| バイナリ | BYTEA / BLOB | 画像・バイナリ(普通は別ストレージ) |
主キー
- 1 行を一意に識別する列(または列の組)
- NULL 不可、自動的に UNIQUE
- 自動でインデックスが作られる
主キーの選び方
| 選択 | 長所 | 短所 |
|---|---|---|
| BIGSERIAL(連番) | 軽量、ソート可 | 連番が露出 / 分散しにくい |
| UUID v4 | 分散で衝突しない、URL で出ても安全 | 16B、ランダム → インデックス局所性悪い |
| UUID v7(時系列含む) | UUID 互換 + 順序保持 | 新しいので対応が必要 |
| ULID / NanoID | 短く時系列も含む | 標準ではない |
| 業務キー(メール等) | 意味がある | 変更されると地獄 |
外部キー
他のテーブルの主キーを参照することで「このユーザの投稿」のような関係を表現する。
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE
);
CREATE TABLE posts (
id BIGSERIAL PRIMARY KEY,
title TEXT NOT NULL,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE
);
ON DELETE / ON UPDATE
- CASCADE: 親が消えたら子も消す
- SET NULL: 子の FK を NULL に
- SET DEFAULT: デフォルト値に
- RESTRICT / NO ACTION: 子があれば親を消せない
制約
CREATE TABLE products (
id BIGSERIAL PRIMARY KEY,
sku TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
price NUMERIC(10, 2) NOT NULL CHECK (price >= 0),
stock INT NOT NULL DEFAULT 0 CHECK (stock >= 0),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
- NOT NULL: NULL 禁止
- UNIQUE: 重複禁止
- CHECK: 任意の式
- DEFAULT: 値省略時のデフォルト
- FOREIGN KEY: 別テーブル参照
- EXCLUDE(Postgres): 範囲・空間の重複禁止
NULL のとっつきにくさ
- NULL は「不明」。0 や空文字とは違う
NULL = NULLは NULL(false ではない)- 判定は
IS NULL/IS NOT NULL - 集計(SUM / COUNT)は NULL を無視(
COUNT(*)だけ含む) - NOT NULL を最初から付けるのが運用上は楽
リレーションのパターン
1 対 1
ユーザとプロフィール。プロフィール側に user_id UNIQUE を付ける。
1 対 多
ユーザと投稿。投稿側に user_id を持たせる。最も多い形。
多 対 多
投稿とタグ。中間テーブルを作る:
CREATE TABLE post_tags (
post_id BIGINT NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
tag_id BIGINT NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
PRIMARY KEY (post_id, tag_id)
);
SQL の基本コマンド分類
| 分類 | 例 |
|---|---|
| DDL(定義) | CREATE / ALTER / DROP |
| DML(操作) | SELECT / INSERT / UPDATE / DELETE |
| DCL(権限) | GRANT / REVOKE |
| TCL(トランザクション) | BEGIN / COMMIT / ROLLBACK |
詳細は SQL セクション へ。
RDB の強み
- 結合(JOIN)で柔軟にデータを組み合わせられる
- ACID トランザクションで整合性を保証
- スキーマでデータ品質が保たれる
- SQLという標準言語で 50 年の知見が蓄積
- 豊富なツール / GUI / 監視
RDB の弱み(と対処)
- 水平スケールしにくい → レプリカ + シャーディング(CockroachDB / Vitess / Citus)
- 固定スキーマが窮屈 → JSONB 列を使う
- 巨大トラフィック → キャッシュ / Read Replica
典型的な接続コード
import postgres from "postgres"
const sql = postgres(process.env.DATABASE_URL!)
const users = await sql`SELECT id, email FROM users WHERE age >= ${18}`
// ^ 自動エスケープ
ORM を介すなら Prisma、生 SQL なら postgres / pg / mysql2 / better-sqlite3。
失敗しやすいポイント
| 症状 | 原因 / 対処 |
|---|---|
| JOIN が遅い | FK 列にインデックスなし → インデックス |
| 整合性が壊れる | FK / NOT NULL / CHECK で制約を強く |
| サマータイム / TZ で時刻ズレ | TIMESTAMPTZ + UTC で保存 |
| NULL の扱いミス | = でなく IS NULL、COALESCE でデフォルト |
| 連番 ID が露出 | UUID v7 / nanoid に変更 |
| 主キー無し | レプリカ / Realtime で困る、必ず付ける |
迷ったら
Postgres を選び、TIMESTAMPTZ で UTC、主キーは UUID v7 か BIGSERIAL、 JSON は JSONB。これで 90% のプロジェクトはスタートできる。