PostgreSQL: PostgreSQLデータ型:完全ガイド

最終更新:2026-08-26

適切なデータ型の選択は、データベース設計の基盤です。ストレージ効率、クエリパフォーマンス、データ精度を決定します。

1. 学習内容


2. 開発者の実体験

(1) 悩み:データ型について混乱

BobはEコマースデータベースのテーブル設計時に、一連の難しい選択に直面しました:

(2) 解決策:正確な型の選択

PostgreSQLは豊富な専用データ型を提供しており、正確に選択することでストレージを節約しつつ精度を保証します:

SQL
-- Eコマースデータベースの正しい型選択
CREATE TABLE smart_products (
    id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,  -- 将来を見据えたID
    name VARCHAR(200) NOT NULL,                          -- 制限付き文字列
    description TEXT,                                    -- 無制限テキスト
    price DECIMAL(10, 2) NOT NULL,                       -- 正確な金額
    weight_kg REAL,                                      -- 近似値で十分
    is_available BOOLEAN DEFAULT true,                   -- はい/いいえフラグ
    created_at TIMESTAMPTZ DEFAULT NOW(),                -- タイムゾーン対応
    source_ip INET,                                      -- IPアドレス型
    attributes JSONB DEFAULT '{}'::jsonb                 -- 柔軟なスキーマ
);

(3) 得られた成果


3. データ型決定フロー

100%
graph TB
    START[どのようなデータ?] --> NUM{数値?}
    NUM -->|はい| INT{正確な<br/>精度が必要?}
    INT -->|はい、金額/レート| DEC[DECIMAL / NUMERIC]
    INT -->|いいえ、測定値| FLOAT[REAL / DOUBLE PRECISION]
    NUM -->|整数| RANGE{値の範囲?}
    RANGE -->|-32768〜32767| SMALL[SMALLINT]
    RANGE -->|-21億〜21億| INT2[INTEGER]
    RANGE -->|より大きい| BIG[BIGINT]
    NUM -->|自動採番ID| AUTO[SERIAL / IDENTITY]
    START --> STR{テキスト?}
    STR -->|固定長| CHAR[CHAR]
    STR -->|可変長(制限付き)| VAR[VARCHAR n]
    STR -->|無制限| TEXT2[TEXT]
    START --> TIME{日付/時刻?}
    TIME -->|日付のみ| DATE2[DATE]
    TIME -->|時刻のみ| TIME2[TIME]
    TIME -->|タイムスタンプ| TZ{タイムゾーン?}
    TZ -->|はい、グローバルアプリ| TSTZ[TIMESTAMPTZ]
    TZ -->|いいえ、ローカルのみ| TS[TIMESTAMP]
    TIME -->|期間| IV[INTERVAL]
    START --> SPEC{特殊?}
    SPEC -->|はい/いいえ| BOOL[BOOLEAN]
    SPEC -->|UUID| UUID2[UUID]
    SPEC -->|IPアドレス| IP[INET / CIDR]
    SPEC -->|列挙リスト| ENUM2[ENUM]
    SPEC -->|JSONドキュメント| JSON[JSONB]

4. 整数型

型 ストレージ 範囲 典型的な用途
SMALLINT 2バイト -32,768 〜 32,767 年齢、評価、ステータスコード
INTEGER(INT) 4バイト -2,147,483,648 〜 2,147,483,647 一般的な整数、主キーID
BIGINT 8バイト ±9,223,372,036,854,775,807 大規模テーブルのID、金額(セント単位)

▶ サンプル:整数型の選択

SQL
-- SMALLINT:小範囲の値
CREATE TABLE ratings (
    user_id INTEGER REFERENCES users(id),
    product_id INTEGER REFERENCES products(id),
    score SMALLINT CHECK (score BETWEEN 1 AND 5),  -- 1-5はSMALLINTの範囲内で十分
    PRIMARY KEY (user_id, product_id)
);

-- INTEGER:ほとんどのID
CREATE TABLE categories (
    id SERIAL PRIMARY KEY,  -- SERIAL = INTEGER + 自動採番シーケンス
    name VARCHAR(100) NOT NULL
);

-- BIGINT:高成長テーブル
CREATE TABLE audit_log (
    id BIGSERIAL PRIMARY KEY,  -- BIGSERIAL = BIGINT + 自動採番
    action VARCHAR(50),
    details JSONB,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

Output:

TEXT 📖 参照専用
CREATE TABLE
💡 ヒント: INTEGERかBIGINTか迷ったら?主キーIDで10億行を超える見込みがある場合は、最初からBIGINTにしましょう。1行あたり4バイトの追加(100万行あたりたった4MB)で済みますが、後で大規模テーブルのALTER TABLE書き換えという高コストな操作を避けられます。


5. 自動採番シーケンス

(1) SERIAL vs IDENTITY

方法 構文 標準 手動挿入? 推奨
SERIAL id SERIAL PRIMARY KEY PG固有 可 レガシー互換性
BIGSERIAL id BIGSERIAL PRIMARY KEY PG固有 可 レガシー互換性
IDENTITY id INT GENERATED ALWAYS AS IDENTITY SQL標準 OVERRIDINGが必要 ✅ 新規プロジェクト

▶ サンプル:IDENTITY列(推奨)

SQL
-- SQL標準のIDENTITY列(PostgreSQL 10+)
CREATE TABLE orders_v2 (
    id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    user_id INTEGER REFERENCES users(id),
    total DECIMAL(12, 2) DEFAULT 0
);

-- idを指定せずに挿入(自動生成)
INSERT INTO orders_v2 (user_id, total) VALUES (1, 99.99);

-- idを手動で挿入しようとすると失敗(GENERATED ALWAYSの場合)
-- INSERT INTO orders_v2 (id, user_id, total) VALUES (100, 1, 50.00);

-- 時々手動上書きが必要な場合はGENERATED BY DEFAULTを使用
CREATE TABLE logs (
    id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    message TEXT
);

Output:

TEXT 📖 参照専用
INSERT 0 1

6. 浮動小数点と正確な数値

(1) REAL / DOUBLE vs DECIMAL

型 ストレージ 精度 最適な用途
REAL 4バイト 有効数字6桁 科学計算、センサー
DOUBLE PRECISION 8バイト 有効数字15桁 科学計算、統計
DECIMAL(p,s) 可変 正確 金額、レート(推奨)
NUMERIC(p,s) 可変 正確 DECIMALと同じ

▶ サンプル:浮動小数点の精度の落とし穴

SQL
-- 浮動小数点の精度トラップ:0.1 + 0.2 != 0.3
SELECT 0.1::REAL + 0.2::REAL = 0.3::REAL AS float_equal;
-- 出力:f(偽!)

-- DECIMALは精度損失なし
SELECT 0.1::DECIMAL + 0.2::DECIMAL = 0.3::DECIMAL AS decimal_equal;
-- 出力:t(真!)

Output:

TEXT 📖 参照専用
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

▶ サンプル:金額にDECIMALを使用

SQL
-- DECIMAL(精度, スケール)
-- 精度 = 総桁数、スケール = 小数点以下の桁数
-- DECIMAL(10,2) = 最大99,999,999.99

CREATE TABLE products_precise (
    id SERIAL PRIMARY KEY,
    name VARCHAR(200),
    price DECIMAL(10, 2) NOT NULL,       -- 最大99,999,999.99
    tax_rate DECIMAL(5, 4) DEFAULT 0.0875, -- 0.0875 = 8.75%
    discount NUMERIC(5, 2) DEFAULT 0.00   -- 最大999.99%
);

-- 金額計算は正確
SELECT name,
       price,
       price * tax_rate AS tax_amount,
       price + (price * tax_rate) AS price_with_tax
FROM products_precise;

Output:

TEXT 📖 参照専用
 count 
-------
     5
(1 row)
精度の選択 DECIMALパラメータ 最大値 最適な用途
小規模Eコマース DECIMAL(8,2) 999,999.99 日次取引量100万未満
大規模Eコマース DECIMAL(12,2) 99,999,999,999.99 グローバルEコマース
暗号資産 DECIMAL(20,8) 非常に大きい BTCの8桁精度

7. 文字列型

型 ストレージ 最大長 最適な用途
CHAR(n) 固定長、スペースパディング n ハッシュ、ISOコード
VARCHAR(n) 可変長 n 長さ制限のあるテキスト
TEXT 可変長 無制限 長さ制限のないテキスト

▶ サンプル:文字列型の比較

SQL
-- CHAR:固定長(スペースでパディング)
SELECT LENGTH('abc'::CHAR(5));
-- 出力:5(5文字にパディング)

-- VARCHAR:制限付き可変長
SELECT LENGTH('abc'::VARCHAR(5));
-- 出力:3(そのまま保存、最大5)

-- TEXT:可変長、制限なし
SELECT LENGTH('abc'::TEXT);
-- 出力:3(制限なし)

-- パフォーマンステスト:PGではVARCHARとTEXTは同一
-- (MySQLのようにVARCHARがTEXTより速いということはない)

Output:

TEXT 📖 参照専用
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)
💡 ヒント: PostgreSQLでは、VARCHARとTEXTのクエリおよびストレージパフォーマンスは同一です。TEXTに長さ制限がないからといってPGが遅くなることはありません。どちらを選ぶかは、データベースに長さ制限を強制させる必要があるかどうかだけです。


8. 日付と時刻型

型 ストレージ 範囲 最適な用途
DATE 4バイト 紀元前4713年〜西暦5874897年 日付のみ(誕生日、祝日)
TIME 8バイト 00:00:00〜24:00:00 時刻のみ(営業時間)
TIMESTAMP 8バイト 紀元前4713年〜西暦294276年 日付+時刻(タイムゾーンなし)
TIMESTAMPTZ 8バイト 同上 日付+時刻+タイムゾーン(✅推奨)
INTERVAL 16バイト ±1億7800万年 時間の長さ

▶ サンプル:TIMESTAMP vs TIMESTAMPTZ

SQL
-- タイムゾーンをUTCに設定
SET timezone = 'UTC';

-- 異なる型で同じ時刻を挿入
INSERT INTO test_times (ts_no_tz, ts_with_tz) VALUES
    ('2026-07-13 10:00:00', '2026-07-13 10:00:00+00');

-- 東京のタイムゾーンに変更
SET timezone = 'Asia/Tokyo';

-- TIMESTAMP(タイムゾーンなし)は同じリテラルを表示
SELECT ts_no_tz FROM test_times;
-- 出力:2026-07-13 10:00:00(変更なし、どのタイムゾーンか不明!)

-- TIMESTAMPTZはローカルタイムゾーンに変換
SELECT ts_with_tz FROM test_times;
-- 出力:2026-07-13 19:00:00+09(10:00 UTC = 19:00 東京)

Output:

TEXT 📖 参照専用
INSERT 0 1

▶ サンプル:日付/時刻関数

SQL
-- 現在の日付と時刻
SELECT NOW();                    -- 2026-07-13 10:30:00.123456+00
SELECT CURRENT_DATE;             -- 2026-07-13
SELECT CURRENT_TIMESTAMP;        -- NOW()と同じ

-- INTERVALを使った日付演算
SELECT NOW() + INTERVAL '7 days';     -- 1週間後
SELECT NOW() - INTERVAL '3 months';   -- 3ヶ月前

-- 年齢計算
SELECT AGE(TIMESTAMP '1990-05-15');   -- 36年1ヶ月28日

-- 部分抽出
SELECT EXTRACT(YEAR FROM NOW());      -- 2026
SELECT EXTRACT(MONTH FROM NOW());     -- 7
SELECT EXTRACT(DOW FROM NOW());       -- 1(月曜日、0=日曜日)

Output:

TEXT 📖 参照専用
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

9. 特殊型

(1) UUID

SQL
-- uuid-ossp拡張を有効化
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- 異なるUUIDバージョンを生成
SELECT uuid_generate_v4();  -- ランダムUUID(最も一般的)
SELECT uuid_generate_v1();  -- 時刻ベースUUID
UUIDバージョン 生成方法 最適な用途
v1 タイムスタンプ+MACアドレス 時刻順序付け
v4 ランダム ほとんどのシナリオ(推奨)
v7 タイムスタンプ+ランダム(新標準) 時刻順序付け+ランダム

(2) INET / CIDR(ネットワークアドレス)

▶ サンプル:IPアドレスの保存とクエリ

SQL
-- INET:単一IPまたはIP範囲
CREATE TABLE access_log (
    id BIGSERIAL PRIMARY KEY,
    source_ip INET NOT NULL,
    access_time TIMESTAMPTZ DEFAULT NOW()
);

INSERT INTO access_log (source_ip) VALUES
    ('192.168.1.100'),
    ('10.0.0.5'),
    ('2001:db8::1');

-- クエリ:サブネット内の全IPを検索(VARCHARでは不可能!)
SELECT source_ip FROM access_log
WHERE source_ip << '192.168.1.0/24'::INET;
-- << は「含まれる」を意味する

-- CIDR:ネットワーク範囲
SELECT '192.168.1.0/24'::CIDR;

Output:

TEXT 📖 参照専用
INSERT 0 1
演算子 意味 例
<< 含まれる 192.168.1.5' << '192.168.1.0/24' = true
>> 含む '192.168.1.0/24' >> '192.168.1.5' = true
= 等しい '192.168.1.5'::INET = '192.168.1.5'::INET

(3) BOOLEAN

▶ サンプル:ブーリアンの3つの表現

SQL
-- ブーリアンは複数の表現を受け付ける
SELECT true, 't', 'true', 'yes', 'on', '1';   -- すべて = TRUE
SELECT false, 'f', 'false', 'no', 'off', '0';  -- すべて = FALSE

-- WHERE句でのブーリアン
SELECT name FROM products WHERE is_available IS TRUE;
SELECT name FROM products WHERE is_available IS NOT FALSE;

Output:

TEXT 📖 参照専用
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(4) ENUM

▶ サンプル:カスタム列挙型

SQL
-- 列挙型を作成(1回作成すれば複数テーブルで再利用可能)
CREATE TYPE order_status AS ENUM (
    'pending', 'paid', 'shipped', 'delivered', 'cancelled'
);

-- テーブル定義で使用
CREATE TABLE orders_enum (
    id SERIAL PRIMARY KEY,
    status order_status DEFAULT 'pending'
);

-- 列挙値は自動的に検証される
INSERT INTO orders_enum (status) VALUES ('pending');   -- OK
-- INSERT INTO orders_enum (status) VALUES ('unknown');  -- エラー!

Output:

TEXT 📖 参照専用
INSERT 0 1
⚠️ 注意: PGのENUM型は一度作成すると、新しい値の追加には ALTER TYPE ... ADD VALUE が必要です(PG 9.1以降はトランザクション内で許可)。値が頻繁に変更される場合は、VARCHAR + CHECK制約の方が柔軟です。


10. データ型比較表

ニーズ ❌ 非推奨 ✅ 推奨 理由
金額 REAL / FLOAT DECIMAL(p,s) 浮動小数点は精度を失う
タイムスタンプ TIMESTAMP TIMESTAMPTZ グローバルアプリにはタイムゾーンが必要
IPアドレス VARCHAR INET INETは範囲クエリをサポート
はい/いいえ INTEGER(0/1) BOOLEAN より明確な意味
長文テキスト VARCHAR(9999) TEXT TEXTは制限がなく同じパフォーマンス
ステータス列挙 VARCHAR + CHECK ENUMまたはVARCHAR+CHECK 安定しているならENUM、頻繁に変更されるならCHECK
柔軟な属性 多数のNULL列 JSONB 動的フィールドを1列で
自動採番ID SERIAL IDENTITY SQL標準構文

11. 完全な例:多様な型を使用したテーブル

SQL
-- ============================================
-- 完全な例:主要な型をすべて使用したテーブル
-- センサーデータ収集システム
-- ============================================

-- 列挙型を作成
CREATE TYPE sensor_status AS ENUM ('active', 'inactive', 'maintenance');

-- 多様なデータ型でテーブルを作成
CREATE TABLE sensors (
    -- 識別
    id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    uuid UUID DEFAULT uuid_generate_v4() UNIQUE,

    -- テキスト
    name VARCHAR(100) NOT NULL,
    description TEXT,
    location_code CHAR(10),

    -- 数値
    latitude DECIMAL(9, 6) CHECK (latitude BETWEEN -90 AND 90),
    longitude DECIMAL(9, 6) CHECK (longitude BETWEEN -180 AND 180),
    altitude REAL,

    -- ネットワーク
    ip_address INET,
    subnet CIDR,

    -- ステータス
    status sensor_status DEFAULT 'active',
    is_online BOOLEAN DEFAULT false,

    -- 時間
    installed_at DATE,
    last_reading_at TIMESTAMPTZ,
    reading_interval INTERVAL DEFAULT INTERVAL '5 minutes',

    -- 柔軟なデータ
    metadata JSONB DEFAULT '{}'::jsonb,

    -- 監査
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- サンプルデータを挿入
INSERT INTO sensors (name, latitude, longitude, ip_address, status, installed_at, metadata)
VALUES (
    '温度センサー A1',
    35.6762, 139.6503,
    '192.168.1.50',
    'active',
    '2025-01-15',
    '{"model": "TX-200", "unit": "celsius", "range_min": -40, "range_max": 85}'::jsonb
);

-- 型固有の演算子を使ったクエリ
SELECT name, ip_address, metadata->>'model' AS model
FROM sensors
WHERE ip_address << '192.168.1.0/24'::INET
  AND status = 'active';

❓ よくある質問

Q DECIMALとNUMERICの違いは?
A PostgreSQLでは、DECIMALとNUMERICは完全に同等で互換性があります。SQL標準では微妙な区別がありますが、PGの実装は同一です。DECIMALが推奨されます(より直感的です)。
Q SERIAL列のシーケンスが番号をスキップするのはなぜ?
A SERIALはシーケンスオブジェクトを使用しており、失敗またはロールバックされたINSERTの後もシーケンス値はロールバックされません(同時実行の安全性を保証するための設計です)。欠番は正常であり機能に影響しません。連続した値が必須の場合は、シーケンスに頼るのではなくアプリケーション層で処理してください。
Q VARCHAR(50)をVARCHAR(100)に変更するとテーブルがロックされますか?
A PostgreSQLでは、VARCHARの長さを増やす場合はテーブルをロックしません(データの書き換えなし)ので即座に完了します。長さを縮める場合や型を変更する場合はテーブルがロックされます。
Q JSONとJSONBのどちらを選ぶべき?
A ほぼ常にJSONBです。JSONBはバイナリで保存されインデックス検索をサポートするため高速です。JSONはテキストで保存され、毎回のクエリで再解析が必要です。JSONを選ぶ唯一のケース:入力の空白/キー順序を保持する必要がある場合(例:監査ログ)。
Q TIMESTAMPTZがTIMESTAMPより推奨される理由は?
A TIMESTAMPTZは保存時にUTCに変換し、読み取り時にクライアントのタイムゾーンに戻します。つまりグローバルユーザーが各自のローカル時間を混乱なく確認できます。TIMESTAMPは与えられた値をそのまま保存・返却するため、タイムゾーンを跨ぐアプリケーションでは曖昧です。
Q 1つのテーブルで複数のデータ型を混在させるとパフォーマンスが低下しますか?
A いいえ。PGは型情報を列ごとに保存し、必要な列のみを読み取ります。専用の型を適切に使用する(例:IPにINET)ことで、型の演算子がインデックスを使用できるため、クエリパフォーマンスが実際に向上します。

📖 まとめ


📝 練習問題

  1. 基礎(★): id(SERIAL主キー)、name(VARCHAR(100))、iso_code(CHAR(2))、population(BIGINT)、gdp DECIMAL(15,2)、is_developed(BOOLEAN)を持つ countries テーブルを作成し、テストデータを3行挿入してください。

  2. 中級(★★): id(BIGINT IDENTITY主キー)、source_ip(INET)、request_time(TIMESTAMPTZ)、response_time_ms(INTEGER)、is_error(BOOLEAN)、メタデータ(JSONB)を持つ server_logs テーブルを作成し、ログレコードを2件挿入した後、10.0.0.0/8 ネットワークからの全ログをクエリしてください。

  3. 発展(★★★): 異なる小数点精度(USD/EUR:小数点2桁、JPY:小数点0桁、CNY:小数点2桁)を持つ複数通貨(USD/EUR/JPY/CNY)を扱う financial_transactions テーブルを設計してください。DECIMAL + CHECK制約を使用して、異なる通貨で異なる小数点スケールを要求するよう実装してください。CREATE TABLEのSQLと、異なる通貨のINSERT例を3つ記述してください。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%