Query Go
適切なデータ型を選ぶ - データ型の概要 の使い方・オプション・サンプル

適切なデータ型を選ぶ - データ型の概要

SQL の主要データ型まとめ。数値・文字列・日付時刻・BOOLEAN・JSON・配列の特徴と RDBMS 差分

概念図

データ型の概要 diagram

構文

sql
column_name INT | VARCHAR(n) | TEXT | DATE | TIMESTAMP[TZ] | BOOLEAN | JSONB | ...

サンプル

数値・文字列・JSON・配列・BOOLEAN・日付時刻を一通り使った製品テーブル(PostgreSQL)

sql
CREATE TABLE products (
  id          BIGINT        PRIMARY KEY,
  sku         VARCHAR(32)   NOT NULL UNIQUE,
  name        VARCHAR(200)  NOT NULL,
  description TEXT,
  price       NUMERIC(10, 2) NOT NULL CHECK (price >= 0),
  tags        TEXT[],                             -- PostgreSQL 配列
  attributes  JSONB,                              -- PostgreSQL
  is_active   BOOLEAN       NOT NULL DEFAULT TRUE,
  released_on DATE,
  created_at  TIMESTAMPTZ   NOT NULL DEFAULT NOW()
);

数値型 (INT / BIGINT / NUMERIC)

  • INT / INTEGER: 4 バイト、約 ±21 億。ID 列の既定だが将来増加しそうなら BIGINT を検討
  • BIGINT: 8 バイト、約 ±9.2×10^18。ユーザー ID や注文 ID の主キーは最初から BIGINT が無難
  • NUMERIC / DECIMAL(p, s): 固定小数。金額・会計・税計算は必ずこれ。浮動小数 FLOAT/DOUBLE は誤差が出る
  • FLOAT / DOUBLE / REAL: 浮動小数。科学計算や集計の近似値向け。金額には使わない

文字列型 (VARCHAR / TEXT / CHAR)

VARCHAR(N) の N は最大文字数(多くの RDBMS)を意味し、超過した INSERT はエラーになります。ストレージ効率は TEXT とほぼ同じで、「制約としての上限」に意味があります。

  • PostgreSQL: VARCHAR と TEXT は実質同じ。パフォーマンス差なし。長さ制約が不要なら TEXT で十分
  • MySQL: VARCHAR は行内に格納(65535 バイトまで)、TEXT は別領域。インデックスの前方一致長に制限あり
  • SQL Server: VARCHAR(ANSI)と NVARCHAR(Unicode)を使い分ける。最大長は 8000 / MAX
  • CHAR(N): 固定長で右側をスペースパディング。ほぼ出番なし

日付時刻型 (DATE / TIMESTAMP / TIMESTAMPTZ)

  • DATE: 年月日のみ。締め日や生年月日に
  • TIME: 時分秒のみ。時刻の組み合わせはタイムゾーン問題で扱いにくいので出番は限定的
  • TIMESTAMP (WITHOUT TIME ZONE): タイムゾーン情報を持たない。「壁時計の時刻」をそのまま保存
  • TIMESTAMP WITH TIME ZONE (TIMESTAMPTZ): 内部は UTC で保存し、セッションのタイムゾーンで表示。グローバルサービスはこれを既定に

MySQL の DATETIMETIMESTAMP は意味が違い、TIMESTAMP は UTC に変換して保存・範囲は 1970〜2038。DATETIME はタイムゾーン変換なし・範囲が広いなど挙動が異なるので、必ず調べて選んでください。

sql
-- TIMESTAMPTZ のほうが運用が楽(PostgreSQL)
CREATE TABLE events (
  id BIGSERIAL PRIMARY KEY,
  occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- MySQL: DATETIME vs TIMESTAMP
CREATE TABLE events (
  id BIGINT PRIMARY KEY,
  occurred_at DATETIME(6) NOT NULL,         -- TZ 変換なし
  inserted_at TIMESTAMP   DEFAULT CURRENT_TIMESTAMP
);

BOOLEAN と RDBMS 差分

  • PostgreSQL: BOOLEAN がネイティブ。TRUE / FALSE / NULL
  • MySQL: BOOLEANTINYINT(1) のエイリアス。実体は整数
  • SQL Server: BOOLEAN 型はなく BIT(0/1)を使う
  • Oracle: SQL レベルでは長く BOOLEAN 非対応(23c で追加)。伝統的には NUMBER(1)CHAR(1)

移植性を重視するなら「BOOLEAN 相当の列は 0/1 を使った整数」にしておくと無難ですが、読みやすさから PostgreSQL は素直に BOOLEAN を使うのがおすすめです。

JSON と配列

JSON 型はスキーマが流動的なデータや外部 API レスポンスを保存するのに便利です。

  • PostgreSQL: JSON(テキストのまま)と JSONB(バイナリ・インデックス可)。基本は JSONB
  • MySQL 5.7+: JSON。関数で抽出・更新可能
  • SQL Server: 専用型はなく NVARCHAR(MAX) + JSON 関数群

PostgreSQL は 配列型TEXT[]INT[])もサポートします。タグやラベルに便利ですが、多対多の関係なら中間テーブルのほうが設計として健全です。JSON/配列を乱用するとリレーショナルの強みを殺します。

sql
-- PostgreSQL: JSONB の GIN インデックスで高速化
CREATE TABLE documents (
  id BIGSERIAL PRIMARY KEY,
  payload JSONB NOT NULL
);

CREATE INDEX idx_documents_payload ON documents USING GIN (payload);

SELECT id FROM documents WHERE payload @> '{"status":"published"}';

落とし穴

  • 金額を FLOAT で保存: 0.1 + 0.2 != 0.3 問題。必ず NUMERIC(p, s)
  • TIMESTAMP でタイムゾーン無視: 海外ユーザーが絡むと秒単位のバグに。基本は TIMESTAMPTZ
  • VARCHAR(255) 神話: MySQL の歴史的事情に由来。本当に 255 必要か、ビジネス上の上限を考える
  • JSON に何でも突っ込む: 頻出クエリの列は正規化しておかないとインデックス設計が崩壊する
  • MySQL の TIMESTAMP 範囲: 2038 年問題。長期データは DATETIME を検討

関連トピック