概要

PostgreSQL は 「データの厳密性と表現力」 で選ばれる RDB で、業務系・金融系・分析基盤・Supabase などのモダン PaaS でも採用が広い。MySQL より仕様遵守が厳しく、型・スキーマ・トランザクションの挙動が予測しやすいので、データ整合性を守りたい案件で第一候補になる。

固有でまず覚えるのが、自動採番の SERIAL / BIGSERIAL と、INSERT/UPDATE/DELETE に付けられる RETURNINGINSERT ... RETURNING id で追加行の値を 1 ステートメント で取り出せる (MySQL なら lastInsertId() を別途呼ぶ流れが 1 つにまとまる)。

データ型の表現力も特徴で、TEXT / TIMESTAMPTZ (TZ 付き時刻) / BOOLEAN / TEXT[] (配列型) / JSONB (バイナリ正規化済み JSON) などが標準で揃う。JSONB-> ->> @> 演算子と GIN インデックスで半構造データを速く扱う武器で、Supabase / Hasura でも要。識別子の大文字小文字も厳密 ("Name"name は別物) など、MySQL と細部の流儀が違う。

学習目標

前提

ドリル

no 内容
01 SERIAL + RETURNING id で 1 ステートメント採番
02 TEXT[] 配列型 (SQLite では JSON テキストで代替)
03 JSONB のキー抽出 (SQLite では json_extract で代替)

採点用 DB について

ch10 と同じく、採点ランナーは SQLite を default に動かします。tests/setup.sql (SQLite 用) を一時 SQLite に流し込み、そのファイルパスを DOJO_DB_PATH に渡してきます。

PostgreSQL / MySQL での動作確認は tests/setup.pgsql.sql / tests/setup.mysql.sql を採点ランナーが優先選択します (DOJO_DB_DRIVER=pgsql 等を指定したとき)。

このため answer.phpdriver 別に分岐 することで「同じ問題を SQLite / MySQL / PostgreSQL で動かす」ことを実演します。

Docker で PostgreSQL を試したい場合

実際の PostgreSQL コンテナに接続して動作確認したい場合は、リポジトリ直下の docker/README.md を参照してください。

演習: ドリル 01 — `SERIAL` + `RETURNING` で 1 ステートメント採番

問題

notes テーブルに body を 3 件 INSERT し、INSERT 直後の id を取得して 1 件ずつ次のフォーマットで出力する。

inserted id: <id>

PostgreSQL では INSERT ... RETURNING id が使えるため INSERT と id 取得が 1 ステートメント で済む。MySQL / SQLite には RETURNING が無い (※) ので、INSERTlastInsertId() の 2 段階になる。

※ MySQL 8 / SQLite 3.35 で RETURNING がサポートされたが、ポータビリティのため本ドリルでは lastInsertId() で書く想定。

期待される出力

inserted id: 1
inserted id: 2
inserted id: 3

ヒント

なぜ PostgreSQL の RETURNING が便利か

INSERT した行の (シーケンスで採番された) idcreated_at などの DB 側で自動入力された値を 1 往復 で取得できる。MySQL の lastInsertId() は AUTO_INCREMENT カラム 1 つに限定だが、RETURNING任意のカラム を返せる:

INSERT INTO notes (body) VALUES ('hello')
RETURNING id, created_at, edit_token;

→ 「INSERT 後に SELECT で取り直す」が不要になる。

演習: ドリル 02 — タグ配列を扱う (PostgreSQL `TEXT[]`)

問題

articles テーブルから タグに "php" を含む記事の title を id 順で出力する。

PHP入門
Laravel入門

タグの格納形式は driver で異なる:

driver カラム型 中身
PostgreSQL TEXT[] (配列型) ARRAY['php','tutorial','beginner']
MySQL JSON (配列型は無いので JSON で代替) '["php","tutorial","beginner"]'
SQLite TEXT (JSON 文字列) '["php","tutorial","beginner"]'

→ WHERE 句も driver で変わる。

driver WHERE 句
PostgreSQL 'php' = ANY(tags)
MySQL JSON_CONTAINS(tags, '"php"')
SQLite EXISTS (SELECT 1 FROM json_each(tags) WHERE value = 'php')

期待される出力

PHP入門
Laravel入門

ヒント

なぜ配列型を持つ DB は便利か

タグや「複数選択値」を「中間テーブル + JOIN」せずに 1 カラム で表せる。

ただし「タグごとに属性 (created_by など) を持たせたい」なら中間テーブルが必要なので 使い分け が肝心。

演習: ドリル 03 — JSON 内の値で検索する (PostgreSQL `JSONB`)

問題

products テーブルから spec.brand'acme' の商品を id 順で抽出し、name: brand の形式で出力する。

ノートPC: acme
マウス: acme

driver で JSON 抽出のシンタックスが異なる:

driver カラム型 brand 抽出
PostgreSQL JSONB spec->>'brand'
MySQL JSON JSON_UNQUOTE(JSON_EXTRACT(spec, '$.brand'))
SQLite TEXT (JSON 文字列) json_extract(spec, '$.brand')

期待される出力

ノートPC: acme
マウス: acme

ヒント

JSONB と JSON の違い (PostgreSQL)

観点 JSON JSONB
内部表現 テキスト (入力そのまま) バイナリ (正規化)
入力時の空白 保持 削除
キーの重複 保持 最後の値で上書き
順序 保持 失われる
インデックス 不可 GIN インデックス可
検索性能 遅い (パース必須) 速い
推奨 ログのような「原文保存」用途 検索・更新する JSON は JSONB

→ 実務では基本的に JSONB を選ぶ。

なぜ JSON 型を使うか

「商品ごとに任意のスペックを持つ」ような スキーマレス な要件で、

ようなときに JSONB が便利。ただし「検索する属性が決まったら」普通のカラムに切り出した方が高速。

この章を AI に聞く

  1. 「ch12 — PostgreSQL 固有の文法と機能」のつまずきやすいポイントを、初学者向けに3つ教えてください

    💬 ChatGPT 📘 Claude ✨ Gemini

  2. 「ch12 — PostgreSQL 固有の文法と機能」について、自分の手で試せる練習問題を3つ出してください (難易度を上げながら)

    💬 ChatGPT 📘 Claude ✨ Gemini

  3. 「ch12 — PostgreSQL 固有の文法と機能」を学んだ後、次に学ぶと良いトピックは何ですか?理由も教えてください

    💬 ChatGPT 📘 Claude ✨ Gemini

🎉 受講お疲れさまでした!

ch12 — PostgreSQL 固有の文法と機能」を読み終えました。
学んだことを誰かに教えると、もっと記憶に残ります。

X で共有 Hatena LINE