CheatSheet
日本語 icon日本語English iconEnglish
チートシートとはカンニングペーパーのことです。それが転じて、本来覚えることをまとめておいたものです。
要点をすぐに参照できるようにまとめてみました。

PostgreSQL

エンジニアのためのWebチートシート

PostgreSQLは、高い信頼性と豊富な機能を持つオープンソースのリレーショナルデータベースです。 JSONB、配列型、ウィンドウ関数、CTEなど強力な機能を備えています。 psqlコマンド、データ型、テーブル定義、JSONB操作、インデックス、運用などをチートシートにまとめました。

psql コマンド

接続 & 基本操作

  • psql でデータベースに接続する方法です。

    # 接続
    psql -U username -d dbname
    psql -U username -h localhost -p 5432 -d dbname
    psql "postgresql://user:pass@host:5432/dbname"
    sudo -u postgres psql
    
    # 接続情報の確認
    \conninfo
    
    # データベース切替
    \c other_db
    
    # 終了
    \q

メタコマンド(情報表示)

  • データベースの構造を確認するメタコマンドです。

    \l            # データベース一覧
    \dt           # テーブル一覧
    \dt+          # テーブル一覧(詳細)
    \d table_name # テーブル構造
    \d+ table_name # テーブル構造(詳細)
    \di           # インデックス一覧
    \dv           # ビュー一覧
    \ds           # シーケンス一覧
    \df           # 関数一覧
    \dn           # スキーマ一覧
    \du           # ロール/ユーザー一覧
    \dx           # 拡張機能一覧
    \h SELECT     # SQLコマンドヘルプ
    \?            # psqlコマンド一覧

便利機能

  • psql の実用的な操作コマンドです。

    \i /path/to/file.sql   # SQLファイル実行
    \x                      # 拡張表示トグル
    \timing                 # 実行時間の表示
    \! ls -la               # シェルコマンド実行
    \e                      # エディタでクエリ編集
    # 出力をファイルに保存
    \o output.txt
    SELECT * FROM users;
    \o
    
    # CSV エクスポート / インポート
    \copy users TO '/path/to/users.csv' CSV HEADER
    \copy users FROM '/path/to/users.csv' CSV HEADER

データ型

PostgreSQL の主要なデータ型の一覧です。

説明
smallint / integer / bigint

2/4/8バイト整数

numeric(p,s) / decimal(p,s)

正確な小数(金額など)

real / double precision

浮動小数点数(4/8バイト)

text / varchar(n) / char(n)

文字列(text 推奨)

boolean

true / false / null

timestamptz / timestamp

日時(timestamptz 推奨)

date / time / interval

日付 / 時刻 / 期間

jsonb / json

JSONデータ(jsonb 推奨、インデックス対応)

uuid

UUID(gen_random_uuid() で生成)

type[]

配列型(任意の型で利用可能)

inet / cidr / macaddr

ネットワークアドレス

bytea

バイナリデータ

配列

  • 配列型の定義と操作です。

    CREATE TABLE posts (
      id SERIAL PRIMARY KEY,
      tags TEXT[]
    );
    
    INSERT INTO posts (tags)
    VALUES (ARRAY['postgresql', 'database']);
    INSERT INTO posts (tags)
    VALUES ('{sql,nosql}');
    -- 包含チェック
    SELECT * FROM posts WHERE tags @> ARRAY['postgresql'];
    SELECT * FROM posts WHERE 'sql' = ANY(tags);
    
    -- 配列操作
    SELECT array_length(ARRAY[1,2,3], 1);  -- 3
    SELECT ARRAY[1,2] || ARRAY[3,4];       -- {1,2,3,4}
    SELECT unnest(ARRAY['a','b','c']);
    SELECT array_agg(name) FROM users;

JSON / JSONB 型

  • jsonb 型の基本操作です。json よりも jsonb を推奨します。

    CREATE TABLE events (
      id SERIAL PRIMARY KEY,
      data JSONB NOT NULL DEFAULT '{}'
    );
    
    INSERT INTO events (data) VALUES
    ('{"type": "click", "page": "/home", "count": 5}');
    -- 値の取得
    SELECT data -> 'type' FROM events;      -- "click"(JSON型)
    SELECT data ->> 'type' FROM events;     -- click(テキスト型)
    SELECT data #>> '{user,name}' FROM events; -- ネストアクセス
    SELECT (data ->> 'count')::int FROM events; -- 型キャスト
    
    -- json: テキスト保存、高速な挿入
    -- jsonb: バイナリ保存、高速な検索、インデックス対応

テーブル定義

CREATE TABLE

  • テーブルの作成です。GENERATED ALWAYS AS IDENTITY が推奨されます。

    -- GENERATED ALWAYS AS IDENTITY(推奨)
    CREATE TABLE users (
      id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
      name TEXT NOT NULL,
      email TEXT UNIQUE NOT NULL,
      age INT,
      created_at TIMESTAMPTZ DEFAULT NOW()
    );
    -- SERIAL(レガシー)
    CREATE TABLE posts (
      id SERIAL PRIMARY KEY,
      title TEXT NOT NULL,
      body TEXT,
      published BOOLEAN DEFAULT false,
      created_at TIMESTAMPTZ DEFAULT NOW()
    );
    DROP TABLE IF EXISTS users CASCADE;
    TRUNCATE TABLE users RESTART IDENTITY CASCADE;
    ALTER TABLE users RENAME TO members;

ALTER TABLE

  • カラムの追加、変更、削除です。

    ALTER TABLE users ADD COLUMN phone TEXT;
    ALTER TABLE users ALTER COLUMN name TYPE VARCHAR(200);
    ALTER TABLE users DROP COLUMN phone;
    ALTER TABLE users RENAME COLUMN name TO full_name;
    -- NOT NULL 制約
    ALTER TABLE users ALTER COLUMN email SET NOT NULL;
    ALTER TABLE users ALTER COLUMN email DROP NOT NULL;
    
    -- デフォルト値
    ALTER TABLE users ALTER COLUMN created_at SET DEFAULT NOW();
    ALTER TABLE users ALTER COLUMN created_at DROP DEFAULT;

制約

  • 主キー、外部キー、CHECK制約などです。

    -- 主キー
    CREATE TABLE users (
      id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY
    );
    
    -- 外部キー
    CREATE TABLE orders (
      id SERIAL PRIMARY KEY,
      user_id INT REFERENCES users(id) ON DELETE CASCADE
    );
    ALTER TABLE users ADD CONSTRAINT uq_email UNIQUE (email);
    ALTER TABLE users ADD CONSTRAINT chk_age CHECK (age >= 0);
    
    -- 複合主キー
    CREATE TABLE user_roles (
      user_id INT REFERENCES users(id),
      role_id INT REFERENCES roles(id),
      PRIMARY KEY (user_id, role_id)
    );
    ALTER TABLE users
    ADD CONSTRAINT uq_name_email UNIQUE (name, email);
    ALTER TABLE users DROP CONSTRAINT uq_email;

PostgreSQL固有の構文

RETURNING & UPSERT

  • RETURNING

    INSERT INTO users (name, email)
    VALUES ('Alice', 'alice@example.com')
    RETURNING id, name;
    
    UPDATE users SET name = 'Bob' WHERE id = 1
    RETURNING *;
    
    DELETE FROM users WHERE id = 1
    RETURNING id, name;
  • UPSERT (ON CONFLICT)

    -- 衝突時に更新
    INSERT INTO users (email, name)
    VALUES ('alice@example.com', 'Alice')
    ON CONFLICT (email)
    DO UPDATE SET name = EXCLUDED.name;
    
    -- 衝突時に何もしない
    INSERT INTO users (email, name)
    VALUES ('alice@example.com', 'Alice')
    ON CONFLICT (email) DO NOTHING;
    -- 条件付き UPSERT
    INSERT INTO users (email, name, updated_at)
    VALUES ('alice@example.com', 'Alice', NOW())
    ON CONFLICT (email)
    DO UPDATE SET
      name = EXCLUDED.name,
      updated_at = EXCLUDED.updated_at
    WHERE users.name != EXCLUDED.name;

DISTINCT ON & ILIKE

  • DISTINCT ON / ILIKE

    -- DISTINCT ON(各グループの最初の行を取得)
    SELECT DISTINCT ON (department)
      department, name, salary
    FROM employees
    ORDER BY department, salary DESC;
    
    -- ILIKE(大文字小文字を無視した LIKE)
    SELECT * FROM users WHERE name ILIKE '%alice%';
  • FILTER / GENERATE_SERIES

    SELECT '42'::integer;       -- 型キャスト
    SELECT NOW()::date;
    SELECT '{"a":1}'::jsonb;
    
    SELECT
      COUNT(*) AS total,
      COUNT(*) FILTER (WHERE active) AS active_count,
      AVG(salary) FILTER (WHERE department = 'eng') AS eng_avg
    FROM employees;
    SELECT generate_series(1, 10);
    SELECT generate_series(
      '2024-01-01'::date, '2024-12-31'::date,
      '1 month'::interval
    );

LATERAL JOIN & 集合演算

  • LATERAL JOIN

    -- 各ユーザーの直近3件の注文を取得
    SELECT u.name, recent.*
    FROM users u
    LEFT JOIN LATERAL (
      SELECT total, created_at
      FROM orders WHERE user_id = u.id
      ORDER BY created_at DESC LIMIT 3
    ) recent ON true;
  • UNION / INTERSECT / EXCEPT

    SELECT name FROM employees
    UNION SELECT name FROM contractors;
    
    SELECT name FROM employees
    INTERSECT SELECT name FROM managers;
    
    SELECT name FROM employees
    EXCEPT SELECT name FROM managers;
  • STRING_AGG / ARRAY_AGG

    SELECT department,
      STRING_AGG(name, ', ' ORDER BY name)
    FROM employees GROUP BY department;
    
    SELECT department,
      ARRAY_AGG(name ORDER BY name)
    FROM employees GROUP BY department;

JSON / JSONB

JSONB の演算子一覧です。

演算子説明
->

キー/インデックスで取得(JSON型で返す)

>>

キー/インデックスで取得(テキストで返す)

#>

パスで取得(JSON型)例: data #> '{a,b}'

#>>

パスで取得(テキスト型)

?

キーが存在するか

@>

左が右を含むか

||

JSONB の連結(マージ)

-

キーまたは配列要素の削除

#-

パス指定で削除

JSONB クエリ & 更新

  • JSONB データの検索と更新です。

    SELECT * FROM events WHERE data ? 'type';
    SELECT * FROM events WHERE data ->> 'type' = 'click';
    SELECT * FROM events WHERE data @> '{"type": "click"}';
    SELECT * FROM events WHERE data #>> '{user,name}' = 'Alice';
    SELECT * FROM events WHERE (data ->> 'count')::int > 10;
    -- 値の更新
    UPDATE events
    SET data = jsonb_set(data, '{count}', '10')
    WHERE id = 1;
    
    -- キーの追加 / 削除
    UPDATE events SET data = data || '{"new_key": "value"}' WHERE id = 1;
    UPDATE events SET data = data - 'old_key' WHERE id = 1;

JSONB 関数

  • JSONB の主要な関数です。

    SELECT jsonb_set('{"a":1,"b":2}'::jsonb, '{a}', '100');
    -- {"a": 100, "b": 2}
    
    SELECT jsonb_strip_nulls('{"a":1,"b":null}'::jsonb);
    -- {"a": 1}
    
    SELECT * FROM jsonb_each('{"a":1,"b":2}'::jsonb);
    SELECT * FROM jsonb_array_elements('[1,2,3]'::jsonb);
    SELECT jsonb_object_keys('{"a":1,"b":2}'::jsonb);
    SELECT to_jsonb(row) FROM (
      SELECT name, email FROM users LIMIT 1
    ) row;
    
    SELECT jsonb_agg(to_jsonb(u)) FROM users u;
    
    SELECT jsonb_build_object(
      'name', name, 'email', email
    ) FROM users;

ウィンドウ関数 & CTE

ウィンドウ関数

  • ROW_NUMBER / RANK

    SELECT name, salary,
      ROW_NUMBER() OVER (ORDER BY salary DESC) AS row_num
    FROM employees;
    
    SELECT name, salary,
      RANK() OVER (ORDER BY salary DESC) AS rank,
      DENSE_RANK() OVER (ORDER BY salary DESC) AS dense_rank
    FROM employees;
  • PARTITION BY / LAG / LEAD

    SELECT department, name, salary,
      ROW_NUMBER() OVER (
        PARTITION BY department ORDER BY salary DESC
      ) AS dept_rank
    FROM employees;
    SELECT date, revenue,
      LAG(revenue) OVER (ORDER BY date) AS prev,
      LEAD(revenue) OVER (ORDER BY date) AS next
    FROM daily_sales;
  • 累計・移動平均 / NTILE

    SELECT date, revenue,
      SUM(revenue) OVER (ORDER BY date) AS running_total,
      AVG(revenue) OVER (
        ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
      ) AS weekly_avg
    FROM daily_sales;
    SELECT name, salary,
      NTILE(4) OVER (ORDER BY salary) AS quartile
    FROM employees;

CTE(共通テーブル式)

  • WITH句を使った可読性の高いサブクエリです。

    WITH active_users AS (
      SELECT * FROM users
      WHERE last_login > NOW() - INTERVAL '30 days'
    )
    SELECT * FROM active_users WHERE country = 'JP';
    -- 複数 CTE
    WITH
      high_spenders AS (
        SELECT user_id, SUM(total) AS spent
        FROM orders GROUP BY user_id
        HAVING SUM(total) > 100000
      ),
      recent_users AS (
        SELECT * FROM users
        WHERE created_at > NOW() - INTERVAL '1 year'
      )
    SELECT r.name, h.spent
    FROM recent_users r JOIN high_spenders h ON r.id = h.user_id;
    -- CTE で DELETE + INSERT
    WITH deleted AS (
      DELETE FROM sessions WHERE expired_at < NOW()
      RETURNING *
    )
    INSERT INTO session_archive SELECT * FROM deleted;

再帰CTE

  • 階層データやツリー構造をたどる再帰クエリです。

    -- 階層構造(組織図など)
    WITH RECURSIVE subordinates AS (
      SELECT id, name, manager_id, 1 AS depth
      FROM employees WHERE id = 1
      UNION ALL
      SELECT e.id, e.name, e.manager_id, s.depth + 1
      FROM employees e
      INNER JOIN subordinates s ON s.id = e.manager_id
    )
    SELECT * FROM subordinates;
    -- 連番生成
    WITH RECURSIVE nums AS (
      SELECT 1 AS n UNION ALL
      SELECT n + 1 FROM nums WHERE n < 100
    )
    SELECT * FROM nums;
    -- カテゴリツリー(パス構築)
    WITH RECURSIVE cat_tree AS (
      SELECT id, name, parent_id, name::text AS path
      FROM categories WHERE parent_id IS NULL
      UNION ALL
      SELECT c.id, c.name, c.parent_id, ct.path || ' > ' || c.name
      FROM categories c JOIN cat_tree ct ON ct.id = c.parent_id
    )
    SELECT * FROM cat_tree ORDER BY path;

インデックス & パフォーマンス

PostgreSQL がサポートするインデックス種類の一覧です。

種類用途
B-tree

デフォルト。等値・範囲・ソートに最適

Hash

等値検索のみ(B-treeで代替可能な場合が多い)

GIN

JSONB、配列、全文検索に最適

GiST

幾何データ、範囲型、全文検索

BRIN

時系列・ログデータ(非常にコンパクト)

SP-GiST

IPアドレス、電話番号など非均衡データ

実践インデックス

  • 用途別のインデックス作成パターンです。

    CREATE INDEX idx_users_email ON users (email);
    CREATE UNIQUE INDEX idx_users_email ON users (email);
    CREATE INDEX idx_orders_user_date ON orders (user_id, created_at);
    
    -- 部分インデックス(条件付き)
    CREATE INDEX idx_active_users
    ON users (email) WHERE active = true;
    -- GIN(JSONB・配列用)
    CREATE INDEX idx_data_gin ON events USING GIN (data);
    CREATE INDEX idx_tags_gin ON posts USING GIN (tags);
    
    -- BRIN(時系列用)
    CREATE INDEX idx_logs_brin ON logs USING BRIN (created_at);
    
    -- 並行作成(テーブルロックなし)
    CREATE INDEX CONCURRENTLY idx_name ON table_name (column);
    DROP INDEX IF EXISTS idx_name;
    REINDEX INDEX idx_name;

EXPLAIN & 分析

  • EXPLAIN

    EXPLAIN SELECT * FROM users WHERE email = 'test@example.com';
    
    EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
    SELECT * FROM users WHERE email = 'test@example.com';
    
    EXPLAIN (ANALYZE, FORMAT JSON)
    SELECT * FROM users WHERE id = 1;
  • 統計・監視

    SELECT * FROM pg_stat_user_tables;
    SELECT * FROM pg_stat_user_indexes;
    
    SELECT pid, state, query, query_start
    FROM pg_stat_activity WHERE state = 'active';
    SELECT pg_size_pretty(pg_total_relation_size('users'));
    SELECT pg_size_pretty(pg_database_size(current_database()));
    
    SELECT pid, NOW() - query_start AS duration, query
    FROM pg_stat_activity
    WHERE state = 'active' ORDER BY duration DESC;

ユーザー & 権限

ロール管理

  • ユーザー/ロールの作成と管理です。

    CREATE ROLE myuser WITH LOGIN PASSWORD 'secret';
    CREATE ROLE admin WITH SUPERUSER LOGIN PASSWORD 'secret';
    CREATE ROLE dev WITH LOGIN PASSWORD 'secret' CREATEDB;
    ALTER ROLE myuser WITH PASSWORD 'newsecret';
    DROP ROLE IF EXISTS myuser;
    SELECT current_user;
    SELECT current_database();
    
    \du
    SELECT rolname, rolsuper, rolcreatedb, rolcanlogin
    FROM pg_roles;

権限

  • テーブルやスキーマへの権限付与です。

    GRANT ALL PRIVILEGES ON DATABASE mydb TO myuser;
    GRANT SELECT, INSERT, UPDATE ON users TO myuser;
    GRANT ALL ON ALL TABLES IN SCHEMA public TO myuser;
    GRANT USAGE ON SCHEMA myschema TO myuser;
    GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO myuser;
    
    ALTER DEFAULT PRIVILEGES IN SCHEMA public
    GRANT SELECT ON TABLES TO readonly_user;
    
    REVOKE ALL ON DATABASE mydb FROM myuser;
    -- 読み取り専用ユーザーの作成例
    CREATE ROLE readonly WITH LOGIN PASSWORD 'secret';
    GRANT CONNECT ON DATABASE mydb TO readonly;
    GRANT USAGE ON SCHEMA public TO readonly;
    GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;
    ALTER DEFAULT PRIVILEGES IN SCHEMA public
    GRANT SELECT ON TABLES TO readonly;

トランザクション

  • BEGIN/COMMIT/ROLLBACK とセーブポイントです。

    BEGIN;
      INSERT INTO accounts (name, balance) VALUES ('Alice', 1000);
      UPDATE accounts SET balance = balance - 500 WHERE name = 'Bob';
    COMMIT;
    
    BEGIN;
      DELETE FROM users WHERE id = 1;
    ROLLBACK;
    -- セーブポイント
    BEGIN;
      INSERT INTO orders (...) VALUES (...);
      SAVEPOINT my_savepoint;
      UPDATE inventory SET stock = stock - 1 WHERE id = 1;
      ROLLBACK TO my_savepoint;
    COMMIT;
    SET TRANSACTION ISOLATION LEVEL READ COMMITTED;  -- デフォルト
    SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    
    BEGIN READ ONLY;
      SELECT * FROM users;
    COMMIT;

運用 & バックアップ

バックアップ & リストア

  • pg_dump / pg_restore によるバックアップと復元です。

    pg_dumpall -U postgres > all_databases.sql
    pg_dump -d mydb -f backup.sql           # プレーンSQL
    pg_dump -Fc -d mydb -f backup.dump      # カスタム形式
    pg_dump -Fd -d mydb -f backup_dir -j 4  # 並列
    pg_dump -s -d mydb -f schema_only.sql   # スキーマのみ
    pg_dump -a -d mydb -f data_only.sql     # データのみ
    pg_dump -d mydb -t users -f users.sql   # 特定テーブル
    # リストア
    psql -d mydb < backup.sql               # プレーンSQL
    pg_restore -d mydb backup.dump          # カスタム形式
    pg_restore -d mydb backup_dir -j 4      # 並列リストア
    
    createdb newdb
    pg_restore -d newdb backup.dump

VACUUM & メンテナンス

  • 統計情報の更新と不要領域の回収です。

    VACUUM users;
    VACUUM ANALYZE users;
    VACUUM FULL users;    -- テーブル再構築(排他ロック)
    ANALYZE users;        -- 統計情報のみ更新
    REINDEX TABLE users;
    REINDEX DATABASE mydb;
    CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
    CREATE EXTENSION IF NOT EXISTS pg_trgm;
    
    SHOW shared_buffers;
    SHOW work_mem;
    SHOW max_connections;
    CREATE SCHEMA IF NOT EXISTS myschema;
    DROP SCHEMA IF EXISTS myschema CASCADE;
    SET search_path TO myschema, public;