SQLのデータ型とは、列に入れられる値の種類と範囲を決めるもので、大きく「数値・文字列・日付時刻・真偽・バイナリ・JSONなどの構造化データ」に分かれます。 型の名前は MySQL と PostgreSQL でよく似ていますが、範囲・上限・エラーになる条件には違いがあります。

この記事では、まず両データベースの型を対応表で一覧にし、そのうえで MySQL 8.0.46 と PostgreSQL 16.15 に同じ値を入れて、エラー文や丸め方を実際に確かめた結果を載せています。桁あふれ(Out of range)、文字数超過(Data too long/value too long)、DECIMAL と FLOAT の誤差、TINYINT(1) と BOOLEAN の違い、CHAR の空白埋め、そして検索の多い「VARCHAR と TEXT のどちらを選ぶか」を、インデックス・上限・DEFAULT の可否の表にまとめました。最後に、金額・ID・日時・フラグといった用途ごとの選び方も整理します。

MySQL 8.0 は2026年4月にサポートが終了しました。この記事の MySQL 向けSQLは 8.4/9.7 でも同じ手順で使えます(違いがある箇所は本文で明記します)。検証は手元の 8.0.46 で行いました。

SQLデータ型の早見表

よく使う型だけを先にまとめます。迷ったらこの表の「まず選ぶ型」から始めると進めやすくなります。

入れたい値 MySQL でまず選ぶ型 PostgreSQL でまず選ぶ型 ひとこと
整数(件数・数量) INT INTEGER 約±21億まで。超えそうなら BIGINT
連番のID BIGINT AUTO_INCREMENT BIGINT GENERATED ALWAYS AS IDENTITY 最初から8バイトにしておくと後で広げずに済む
金額・割合など誤差を出したくない小数 DECIMAL(p,s) NUMERIC(p,s) 10進数のまま正確に保存
計測値など近似でよい小数 DOUBLE DOUBLE PRECISION 2進数の近似値
短い文字列(名前・コード) VARCHAR(n) VARCHAR(n) または TEXT n は文字数
長い文章 TEXT/MEDIUMTEXT TEXT MySQL の TEXT は約6.5万バイトまで
日付 DATE DATE
日時 DATETIME TIMESTAMPTZ タイムゾーンの扱いが違う
真偽(フラグ) BOOLEAN(実体は TINYINT(1)) BOOLEAN MySQL は 2 なども入る
バイナリ BLOB/VARBINARY(n) BYTEA
JSON JSON JSONB
UUID BINARY(16) または CHAR(36) UUID PostgreSQL は専用の型がある

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「SQLデータ型一覧|MySQL・PostgreSQL比較とVARCHARとTEXT」

検証環境

項目 MySQL PostgreSQL
バージョン 8.0.46 16.15
文字コード utf8mb4 UTF8
設定 sql_mode は既定(STRICT_TRANS_TABLES を含む) 既定

MySQL の結果は、既定の厳格モード(STRICT_TRANS_TABLES)でのものです。厳格モードを外した環境では、エラーにならず値が切り詰められて警告になる箇所があります。範囲やバイト数の数値は、それぞれの公式ドキュメントの記載に基づいています。

数値型の対応表と桁あふれ

整数型

MySQL PostgreSQL バイト数 範囲(符号あり)
TINYINT (なし。SMALLINT を使う) 1 -128 〜 127
SMALLINT SMALLINT 2 -32,768 〜 32,767
MEDIUMINT (なし。INTEGER を使う) 3 -8,388,608 〜 8,388,607
INT/INTEGER INTEGER/INT 4 -2,147,483,648 〜 2,147,483,647
BIGINT BIGINT 8 -9,223,372,036,854,775,808 〜 9,223,372,036,854,775,807
INT UNSIGNED など (なし) 同じ 0 〜 4,294,967,295(INT の場合)
AUTO_INCREMENT GENERATED ... AS IDENTITY/SERIAL — 連番

PostgreSQL には TINYINT と UNSIGNED がありません。 PostgreSQL 16.15 で試すと次のエラーになりました。

ERROR:  type "tinyint" does not exist
ERROR:  syntax error at or near "UNSIGNED"

MySQL から PostgreSQL へ移すときは、TINYINT は SMALLINT に、INT UNSIGNED は BIGINT に置き換えると範囲が収まります。0以上に限りたいときは CHECK (列 >= 0) を足します。

MySQL で INT(11) のようにかっこで書く数字は表示幅で、入る範囲には関係しません。8.0.46 で CREATE TABLE t_w (a INT(11), b TINYINT(1), c BIGINT(20)) を実行すると Warning 1681: Integer display width is deprecated and will be removed in a future release. が出て、SHOW CREATE TABLE では int・tinyint(1)・bigint と表示されました。新しく書くSQLでは INT、BIGINT とだけ書けば十分です。

桁あふれを再現する(Out of range)

-- MySQL 8.0
CREATE TABLE t_num (
  a TINYINT, b TINYINT UNSIGNED, c SMALLINT, d INT, e BIGINT,
  f DECIMAL(10,2), g FLOAT, h DOUBLE
);
INSERT INTO t_num (a) VALUES (127);          -- 入る
INSERT INTO t_num (a) VALUES (128);          -- 範囲外
INSERT INTO t_num (b) VALUES (-1);           -- UNSIGNED に負の数
INSERT INTO t_num (d) VALUES (2147483648);   -- INT の上限 + 1
INSERT INTO t_num (f) VALUES (100000000);    -- DECIMAL(10,2) の整数部は8桁まで

MySQL 8.0.46 の実行結果です。

ERROR 1264 (22003) at line 5: Out of range value for column 'a' at row 1
ERROR 1264 (22003) at line 7: Out of range value for column 'b' at row 1
ERROR 1264 (22003) at line 9: Out of range value for column 'd' at row 1
ERROR 1264 (22003) at line 13: Out of range value for column 'f' at row 1

PostgreSQL 16.15 では、型の名前が入ったメッセージになります。

-- PostgreSQL 16
CREATE TABLE t_num (
  c SMALLINT, d INTEGER, e BIGINT,
  f NUMERIC(10,2), g REAL, h DOUBLE PRECISION
);
INSERT INTO t_num (c) VALUES (32768);
INSERT INTO t_num (d) VALUES (2147483648);
INSERT INTO t_num (e) VALUES (9223372036854775808);
INSERT INTO t_num (f) VALUES (100000000);
ERROR:  smallint out of range
ERROR:  integer out of range
ERROR:  bigint out of range
ERROR:  numeric field overflow
DETAIL:  A field with precision 10, scale 2 must round to an absolute value less than 10^8.
起きたこと MySQL 8.0.46 PostgreSQL 16.15
整数が範囲外 ERROR 1264: Out of range value for column '列名' ERROR: integer out of range など
DECIMAL の整数部が桁あふれ ERROR 1264: Out of range value ERROR: numeric field overflow
小数部が桁数を超える(1.005 → 小数2桁) 四捨五入して 1.01。Note 1265: Data truncated 四捨五入して 1.01。通知なし

小数部が多いだけならエラーにはならず、どちらも四捨五入して保存されます。 入力した値と保存された値が変わるので、小数の桁数(scale)は入ってくる値に合わせて決めます。

小数型:DECIMALとFLOATの誤差

MySQL PostgreSQL バイト数 精度 種類
DECIMAL(p,s)/NUMERIC(p,s) NUMERIC(p,s)/DECIMAL(p,s) 可変 MySQL は最大65桁。PostgreSQL は小数点の前131,072桁・後16,383桁まで 10進数で正確
FLOAT REAL 4 約6〜7桁 2進数の近似
DOUBLE DOUBLE PRECISION 8 約15桁 2進数の近似

DECIMAL(10,2) は「全体で10桁、そのうち小数部が2桁」という意味です。整数部は8桁(99,999,999.99)までになります。

0.1 と 0.2 を3つの型に入れて合計すると、DECIMAL だけが 0.3 ちょうどになります。

INSERT INTO t_num (f, g, h) VALUES (0.1, 0.1, 0.1), (0.2, 0.2, 0.2);
SELECT SUM(f) dec_sum, SUM(g) float_sum, SUM(h) double_sum FROM t_num;
SELECT SUM(f) = 0.3 dec_eq, SUM(g) = 0.3 float_eq, SUM(h) = 0.3 double_eq FROM t_num;

MySQL 8.0.46 の実行結果です。

+---------+---------------------+---------------------+
| dec_sum | float_sum           | double_sum          |
+---------+---------------------+---------------------+
|    0.30 | 0.30000000447034836 | 0.30000000000000004 |
+---------+---------------------+---------------------+
+--------+----------+-----------+
| dec_eq | float_eq | double_eq |
+--------+----------+-----------+
|      1 |        0 |         0 |
+--------+----------+-----------+

PostgreSQL 16.15 でも同じ傾向でした(REAL の合計は 0.3 と表示されますが、= 0.3 の比較は偽です)。

 dec_sum | real_sum |     double_sum      
---------+----------+---------------------
    0.30 |      0.3 | 0.30000000000000004
 dec_eq | real_eq | double_eq 
--------+---------+-----------
 t      | f       | f

桁数の多い値を入れたときの違いも見ておきます。1234567.89 を3つの型に入れて取り出した結果です。

型 MySQL 8.0.46 PostgreSQL 16.15
DECIMAL(10,2)/NUMERIC(10,2) 1234567.89 1234567.89
FLOAT/REAL 1234570 1.2345679e+06
DOUBLE/DOUBLE PRECISION 1234567.89 1234567.89

4バイトの FLOAT/REAL は有効桁数が6〜7桁なので、9桁の値は途中から丸められます。 金額や数量のように1円・1個の違いに意味がある値は DECIMAL/NUMERIC に、センサーの計測値や統計値のように近似でよい値は DOUBLE に入れる、という分け方が基本です。

文字列型の対応表:CHAR・VARCHAR・TEXT

MySQL PostgreSQL 上限 特徴
CHAR(n) CHAR(n) MySQL は255文字。PostgreSQL は10,485,760文字 固定長。足りない分は空白で埋める
VARCHAR(n) VARCHAR(n) MySQL は行全体で65,535バイト(utf8mb4 で最大16,383文字)。PostgreSQL は10,485,760文字 可変長。n は文字数
(n の省略は不可) VARCHAR(n なし) 約1GB PostgreSQL では TEXT と同じ扱い
TINYTEXT TEXT 255バイト
TEXT TEXT MySQL は65,535バイト。PostgreSQL は約1GB
MEDIUMTEXT TEXT 16,777,215バイト(約16MB)
LONGTEXT TEXT 4,294,967,295バイト(約4GB)
ENUM('a','b') CREATE TYPE ... AS ENUM または CHECK — 決まった値だけを許す

VARCHAR(n)のnは文字数

VARCHAR(5) には、半角でも全角でも5文字まで入ります。 バイト数ではありません。

CREATE TABLE t_str (c CHAR(5), v VARCHAR(5));
INSERT INTO t_str VALUES ('あいうえお', 'あいうえお');   -- 5文字(15バイト):入る
INSERT INTO t_str (v) VALUES ('abcdef');               -- 6文字
INSERT INTO t_str (v) VALUES ('あいうえおか');           -- 6文字

MySQL 8.0.46:

ERROR 1406 (22001) at line 31: Data too long for column 'v' at row 1
ERROR 1406 (22001) at line 32: Data too long for column 'v' at row 1

PostgreSQL 16.15:

ERROR:  value too long for type character varying(5)
ERROR:  value too long for type character varying(5)

「あいうえお」は UTF-8 で15バイトですが、どちらのデータベースでも VARCHAR(5) に入りました。文字数とバイト数を確かめた結果です。

値 文字数 バイト数 VARCHAR(5) に入るか
'ab' 2 2 入る
'あいうえお' 5 15 入る
'abcdef' 6 6 エラー
'あいうえおか' 6 18 エラー

文字数を数える関数は MySQL が CHAR_LENGTH()、PostgreSQL が char_length()、バイト数は MySQL が LENGTH()、PostgreSQL が octet_length() です。MySQL の LENGTH() はバイト数を返す点に気をつけてください。

はみ出した分が空白だけのときは、どちらもエラーにならず空白が切り落とされました。'abcde '(5文字+空白3つ)を VARCHAR(5) に入れると、MySQL は Note 1265: Data truncated for column 'v' を出して abcde を保存し、PostgreSQL は通知なしで abcde を保存します。また PostgreSQL では、'abcdefgh'::varchar(5) のように明示的に型変換すると、エラーにならず abcde に切り詰められます。

CHARは空白で埋められる

CHAR(5) と VARCHAR(5) に 'ab' と 'ab '(末尾に空白2つ)を入れて比べました。

入れた値 型 MySQL:文字数/CONCAT('[',列,']') PostgreSQL:文字数/バイト数/連結結果
'ab' CHAR(5) 2/[ab] 2/5/[ab]
'ab ' CHAR(5) 2/[ab] 2/5/[ab]
'ab' VARCHAR(5) 2/[ab] 2/2/[ab]
'ab ' VARCHAR(5) 4/[ab ] 4/4/[ab ]
  • CHAR は内部では空白で5文字に埋めて保存されます(PostgreSQL の octet_length が 5)。取り出すときや比較・連結のときは、末尾の空白が無いものとして扱われます。
  • CHAR では、入力した末尾の空白が残りません。 'ab ' を入れても 'ab' と区別がつかなくなります。
  • VARCHAR は入れた空白をそのまま保持します。

CHAR が合うのは、国コードや固定桁の区分コードのように長さが必ず同じ値です。それ以外は VARCHAR を選ぶと、空白の扱いで迷うことが減ります。

VARCHARとTEXTの違い(インデックス・上限・DEFAULT)

MySQL では VARCHAR と TEXT で使える機能に差があり、PostgreSQL では両者にほとんど差がありません。 ここがいちばん違いの出るところです。両方で同じ操作を試した結果を表にしました。

確認したこと MySQL 8.0.46 VARCHAR(255) MySQL 8.0.46 TEXT PostgreSQL 16.15 VARCHAR(255) PostgreSQL 16.15 TEXT
上限 行全体で65,535バイト(utf8mb4 で最大16,383文字) 65,535バイト 10,485,760文字まで指定可 約1GB
DEFAULT 'none'(リテラル) できる エラー 1101 できる できる
DEFAULT ('none')(式) できる できる(8.0.13 以降) できる できる
通常のインデックス できる エラー 1170(長さの指定が必要) できる できる
先頭N文字のインデックス 列(100) できる できる 構文なし(式インデックスで代用) 構文なし(式インデックスで代用)
UNIQUE 制約 できる 先頭N文字に対してのみ できる できる
インデックスに入る値の大きさ 3,072バイトまで 3,072バイトまで 1行あたり約2,704バイトまで 1行あたり約2,704バイトまで

MySQLで確かめたこと

-- TEXT にリテラルの DEFAULT は付けられない
CREATE TABLE t_txt (id INT PRIMARY KEY, v VARCHAR(255) DEFAULT 'none', t TEXT DEFAULT 'none');
ERROR 1101 (42000) at line 52: BLOB, TEXT, GEOMETRY or JSON column 't' can't have a default value

かっこで囲んで「式」にすると作成できます(MySQL 8.0.13 以降。8.4/9.7 でも同じ書き方です)。

CREATE TABLE t_txt (
  id INT PRIMARY KEY,
  v VARCHAR(255) DEFAULT 'none',
  t TEXT DEFAULT ('none')
);
INSERT INTO t_txt (id) VALUES (1);
SELECT * FROM t_txt;
+----+------+------+
| id | v    | t    |
+----+------+------+
|  1 | none | none |
+----+------+------+

インデックスは、TEXT では先頭何文字を使うかの指定が必要です。

CREATE INDEX ix_v ON t_txt (v);        -- VARCHAR:作成できる
CREATE INDEX ix_t ON t_txt (t);        -- TEXT:エラー
CREATE INDEX ix_t ON t_txt (t(100));   -- TEXT:先頭100文字なら作成できる
ERROR 1170 (42000) at line 56: BLOB/TEXT column 't' used in key specification without a key length

VARCHAR でも、長すぎる列にはそのままインデックスを作れません。VARCHAR(4000) は utf8mb4 で最大16,000バイトになるためです。

CREATE TABLE t_big (v VARCHAR(4000), INDEX (v));
ERROR 1071 (42000) at line 59: Specified key was too long; max key length is 3072 bytes

上限も確かめました。

試したこと 結果(MySQL 8.0.46)
VARCHAR(16384) ERROR 1074: Column length too big for column 'v' (max = 16383); use BLOB or TEXT instead
VARCHAR(16383) の1列だけ 作成できる
VARCHAR(16383) と VARCHAR(10) の2列 ERROR 1118: Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535.
TEXT に「あ」を21,845文字(65,535バイト) 入る
TEXT に「あ」を21,846文字(65,538バイト) ERROR 1406: Data too long for column 't' at row 1

MySQL の TEXT の上限は「65,535文字」ではなく「65,535バイト」です。 日本語(1文字3バイト)なら約2.1万文字で上限に達します。それより長い文章は MEDIUMTEXT(約16MB)を選びます。VARCHAR は1行の合計65,535バイトを他の列と分け合いますが、TEXT は本体が行の外に置かれるため、この合計にはほとんど含まれません。

PostgreSQLで確かめたこと

CREATE TABLE t_txt (
  id INT PRIMARY KEY,
  v  VARCHAR(255) DEFAULT 'none',
  v2 VARCHAR,                  -- 長さを省略できる
  t  TEXT DEFAULT 'none'       -- リテラルの DEFAULT も可
);
CREATE INDEX ix_v ON t_txt (v);
CREATE INDEX ix_t ON t_txt (t);          -- TEXT にもそのまま作成できる
ALTER TABLE t_txt ADD UNIQUE (t);        -- UNIQUE も可

すべてエラーなく実行できました。長さなしの VARCHAR 列には100万文字の文字列も入りました。一方、指定できる長さには上限があります。

CREATE TABLE t_v1 (v VARCHAR(10485761));
ERROR:  length for type varchar cannot exceed 10485760

TEXT にインデックスがある状態で、圧縮の効きにくい64,000文字の値を入れると、次のエラーになりました。

ERROR:  index row size 3456 exceeds btree version 4 maximum 2704 for index "ix_t"
HINT:  Values larger than 1/3 of a buffer page cannot be indexed.
Consider a function index of an MD5 hash of the value, or use full text indexing.

PostgreSQL では TEXT にインデックスを作れますが、インデックスに入れられる1件の大きさには上限があります。 長い文章の列で完全一致を調べたいときは、ヒントのとおり md5(列) の式インデックスを使い、文章の中身を検索したいときは全文検索用のインデックスを使います。

VARCHARとTEXTの選び方

条件 MySQL PostgreSQL
名前・メールアドレス・コードなど短い値 VARCHAR(n) VARCHAR(n) または TEXT
検索や並べ替え、UNIQUE に使う VARCHAR(n)(インデックスをそのまま作れる) どちらでも可
既定値を付けたい VARCHAR(n) が手軽 どちらでも可
長さの上限をデータベース側で守りたい VARCHAR(n) VARCHAR(n)、または TEXT + CHECK (char_length(列) <= n)
本文・メモなど長さが読めない文章 TEXT/MEDIUMTEXT TEXT

PostgreSQL の公式ドキュメントは、CHAR・VARCHAR・TEXT の間に性能上の差はないと説明しています。PostgreSQL では「上限を決めたいかどうか」、MySQL では「インデックスや既定値を使うかどうか、65,535バイトに収まるかどうか」で選ぶと判断しやすくなります。

日付・時刻型の対応表

MySQL PostgreSQL バイト数 範囲 タイムゾーン
DATE DATE MySQL 3/PostgreSQL 4 MySQL は 1000-01-01 〜 9999-12-31。PostgreSQL は紀元前4713年 〜 5874897年 なし
DATETIME TIMESTAMP MySQL 5+小数秒分/PostgreSQL 8 MySQL は 1000-01-01 00:00:00 〜 9999-12-31 23:59:59 変換しない(入れた値のまま)
TIMESTAMP TIMESTAMPTZ MySQL 4+小数秒分/PostgreSQL 8 MySQL は 1970-01-01 00:00:01 UTC 〜 2038-01-19 03:14:07 UTC。PostgreSQL は紀元前4713年 〜 294276年 UTC で保存し、接続のタイムゾーンで表示
TIME TIME MySQL 3+小数秒分/PostgreSQL 8 時刻のみ なし
YEAR (なし。SMALLINT を使う) 1 1901 〜 2155 —
(なし) INTERVAL 16 期間(「3日と2時間」など) —

PostgreSQL には DATETIME という名前の型がありません(16.15 で ERROR: type "datetime" does not exist)。MySQL の DATETIME に当たるのは TIMESTAMP です。

実行して確かめた違いです。

試したこと MySQL 8.0.46 PostgreSQL 16.15
存在しない日付 '2026-02-30' ERROR 1292: Incorrect date value: '2026-02-30' for column 'd' ERROR: date/time field value out of range: "2026-02-30"
'0000-00-00' ERROR 1292: Incorrect date value(既定の sql_mode) ERROR: date/time field value out of range
ミリ秒つき '12:34:56.789' を小数秒なしの日時列へ DATETIME は 12:34:57 に四捨五入。DATETIME(6) なら 12:34:56.789000 TIMESTAMP は既定でマイクロ秒まで保持し 12:34:56.789
2038年1月19日 03:14:08(UTC)を超える値 TIMESTAMP 列は ERROR 1292: Incorrect datetime value(検証環境は JST のため 12:14:08 で発生) TIMESTAMP/TIMESTAMPTZ とも保存できる

MySQL の DATETIME は、小数秒の桁数を DATETIME(3) や DATETIME(6) のように指定しないと秒未満が丸められます。 ミリ秒まで残したい列は桁数を付けて定義します。

「どの型で保存し、どこでタイムゾーンを変換するか」の設計は、既存記事「タイムゾーン設計」で詳しく扱っています。

真偽値:TINYINT(1)とBOOLEAN

項目 MySQL PostgreSQL
型 BOOLEAN/BOOL(TINYINT(1) の別名) BOOLEAN(専用の型、1バイト)
入る値 -128 〜 127 の整数 true・false・NULL のみ
TRUE/FALSE の実体 1/0 真偽値

MySQL で BOOLEAN と書いて作った列は、実際には TINYINT(1) になります。

CREATE TABLE t_bool (id INT, flag BOOLEAN);
SHOW CREATE TABLE t_bool\G
Create Table: CREATE TABLE `t_bool` (
  `id` int DEFAULT NULL,
  `flag` tinyint(1) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

整数の列なので、2 も入ります。このとき、条件の書き方で結果が変わります。

INSERT INTO t_bool VALUES (1, TRUE), (2, FALSE), (3, 2), (4, NULL);
SELECT id, flag, flag = TRUE AS eq_true, flag IS TRUE AS is_true FROM t_bool;
+------+------+---------+---------+
| id   | flag | eq_true | is_true |
+------+------+---------+---------+
|    1 |    1 |       1 |       1 |
|    2 |    0 |       0 |       0 |
|    3 |    2 |       0 |       1 |
|    4 | NULL |    NULL |       0 |
+------+------+---------+---------+

flag = TRUE は「flag = 1」と同じ意味なので、2 の行は含まれません。 WHERE flag や flag IS TRUE は「0 以外」を真とみなすので、2 の行も含まれます(WHERE flag は id 1・3、WHERE flag = TRUE は id 1 だけが返りました)。0 と 1 だけに限りたいときは CHECK (flag IN (0, 1)) を付けておくと、どの書き方でも同じ結果になります。

PostgreSQL の BOOLEAN は専用の型です。

CREATE TABLE t_bool (id INT, flag BOOLEAN);
INSERT INTO t_bool VALUES (1, TRUE), (2, FALSE), (3, 'yes'), (4, NULL), (5, '1'), (6, 'off');
INSERT INTO t_bool VALUES (7, 2);
SELECT id FROM t_bool WHERE flag = 1;
ERROR:  column "flag" is of type boolean but expression is of type integer
HINT:  You will need to rewrite or cast the expression.
ERROR:  operator does not exist: boolean = integer

'yes'・'1'・'off' のような文字列は真偽値として受け付けられ(表示は t/f)、整数の 1 や 2 はエラーになりました。MySQL から移したSQLに flag = 1 があれば、flag = TRUE か flag に書き換えます。

バイナリ・JSON・UUIDなどの対応表

用途 MySQL PostgreSQL 補足
固定長バイナリ BINARY(n) BYTEA
可変長バイナリ VARBINARY(n)、BLOB(65,535バイト)、MEDIUMBLOB(約16MB)、LONGBLOB(約4GB) BYTEA(約1GB)
JSON JSON JSON(テキストのまま)、JSONB(バイナリ形式) PostgreSQL は通常 JSONB
UUID BINARY(16)+UUID_TO_BIN()、または CHAR(36) UUID(16バイト)
配列 (なし。JSON か子テーブル) INTEGER[]、TEXT[] など
列挙 ENUM('draft','open') CREATE TYPE ... AS ENUM
ビット列 BIT(n) BIT(n)、BIT VARYING(n)

各型を使った CREATE TABLE の例と、実行結果です。

-- MySQL 8.0
CREATE TABLE t_misc (
  id      BINARY(16) PRIMARY KEY,
  id_text CHAR(36),
  doc     JSON,
  img     BLOB,
  st      ENUM('draft','open','closed')
);
INSERT INTO t_misc VALUES (
  UUID_TO_BIN('6ccd780c-baba-1026-9564-5b8c656024db'),
  '6ccd780c-baba-1026-9564-5b8c656024db',
  '{"tags":["a","b"],"n":1}', x'89504E47', 'open');
SELECT BIN_TO_UUID(id) id, LENGTH(id) id_bytes, LENGTH(id_text) text_bytes,
       doc->>'$.tags[0]' tag0, HEX(img) img, st FROM t_misc;
+--------------------------------------+----------+------------+------+----------+------+
| id                                   | id_bytes | text_bytes | tag0 | img      | st   |
+--------------------------------------+----------+------------+------+----------+------+
| 6ccd780c-baba-1026-9564-5b8c656024db |       16 |         36 | a    | 89504E47 | open |
+--------------------------------------+----------+------------+------+----------+------+
-- PostgreSQL 16
CREATE TABLE t_misc (
  id   UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  doc  JSONB,
  raw  JSON,
  img  BYTEA,
  tags TEXT[]
);
INSERT INTO t_misc (id, doc, raw, img, tags) VALUES (
  '6ccd780c-baba-1026-9564-5b8c656024db',
  '{"tags":["a","b"],"n":1}', '{"tags":["a","b"],"n":1}',
  '\x89504E47', ARRAY['a','b']);
SELECT id, pg_column_size(id) id_bytes, doc->'tags'->>0 tag0, doc, raw, img, tags FROM t_misc;
                  id                  | id_bytes | tag0 |             doc              |           raw            |    img     | tags  
--------------------------------------+----------+------+------------------------------+--------------------------+------------+-------
 6ccd780c-baba-1026-9564-5b8c656024db |       16 | a    | {"n": 1, "tags": ["a", "b"]} | {"tags":["a","b"],"n":1} | \x89504e47 | {a,b}

読み取れるポイントです。

  • UUID は、文字列(36バイト)で持つより16バイトで持つほうが小さくなります。 PostgreSQL は UUID 型がそのまま16バイト、MySQL は BINARY(16) と UUID_TO_BIN()/BIN_TO_UUID() を組み合わせます。
  • JSONB はキーの順番を並べ替えて保存し、JSON は入力した文字列をそのまま保存します。 上の結果でも doc(JSONB)は n が先に来ています。
  • 形式の合わない値は、どちらも型がエラーにしてくれます。
入れた値 MySQL 8.0.46 PostgreSQL 16.15
JSON 列に '{bad json}' ERROR 3140: Invalid JSON text: "Missing a name for object member." at position 1 ERROR: invalid input syntax for type json
UUID 列に 'not-a-uuid' (CHAR(36) はそのまま入る) ERROR: invalid input syntax for type uuid: "not-a-uuid"
ENUM 列に定義のない値 ERROR 1265: Data truncated for column 'st' at row 1 ENUM 型なら ERROR: invalid input value for enum st_t: "x"

UUID のバージョンの選び方は既存記事「UUID」で、JSONB の演算子やインデックスは既存記事「PostgreSQL jsonb操作一覧」で、MySQL の JSON 関数は「MySQL JSON型の使い方」で扱っています。

用途別のデータ型の選び方

用途 MySQL PostgreSQL 理由
金額・単価 DECIMAL(12,2) など。整数の通貨なら BIGINT も可 NUMERIC(12,2) など 2進数の近似を避けて、10進数のまま計算する
割合・税率 DECIMAL(5,4) NUMERIC(5,4) 0.1 のような値を正確に持てる
数量・件数 INT INTEGER 約21億まで
主キー(連番) BIGINT AUTO_INCREMENT BIGINT GENERATED ALWAYS AS IDENTITY あとから型を広げる作業を避けられる
主キー(UUID) BINARY(16) UUID 文字列より小さく、比較も速い
外部から受け取るID・コード VARCHAR(n) VARCHAR(n)/TEXT 先頭ゼロや英字が混ざっても保持できる
電話番号・郵便番号 VARCHAR(n) VARCHAR(n) 計算しない値。先頭の0を残す
作成日時・更新日時 DATETIME(6)(UTC で保存すると決める)または TIMESTAMP(6) TIMESTAMPTZ ある瞬間を一意に表せる
誕生日・締切日 DATE DATE 時刻を持たない
フラグ BOOLEAN+CHECK (列 IN (0,1)) BOOLEAN 2値だけに限定する
状態(3種類以上) VARCHAR(20)+CHECK、または ENUM TEXT+CHECK、または ENUM 型 値の追加がしやすいのは CHECK
本文・メモ TEXT/MEDIUMTEXT TEXT 長さが読めない
設定など形が変わるデータ JSON JSONB 構造をあとから変えられる
画像・ファイル本体 外部ストレージに置き、パスを VARCHAR で持つ 同じ 行が小さく保たれ、バックアップも軽くなる

迷ったときに確かめる項目です。

確認項目 見るところ
最大値は型の範囲に収まるか 件数が増える列は BIGINT、小数は整数部の桁数(p − s)
文字数の上限は適切か 入ってくる最長の値+余裕。MySQL の TEXT は約6.5万バイトまで
誤差が出てもよい値か 出したくないなら DECIMAL/NUMERIC
秒未満が必要か MySQL は DATETIME(3)/(6) を指定
NULL を許すか NOT NULL と DEFAULT をセットで考える
検索・並べ替え・UNIQUE に使うか MySQL の TEXT は先頭N文字のインデックスになる

MySQLとPostgreSQLの間で型を移すときの対応

MySQL の型 PostgreSQL での置き換え 気をつける点
TINYINT SMALLINT
TINYINT(1)/BOOLEAN BOOLEAN 0/1 以外の値が入っていないか先に確認する
INT UNSIGNED BIGINT 必要なら CHECK (列 >= 0)
BIGINT UNSIGNED NUMERIC(20,0) BIGINT の範囲を超える値があるとき
AUTO_INCREMENT GENERATED ... AS IDENTITY
DOUBLE DOUBLE PRECISION
DATETIME TIMESTAMP タイムゾーンを持たせるなら TIMESTAMPTZ
TIMESTAMP TIMESTAMPTZ
TINYTEXT/MEDIUMTEXT/LONGTEXT TEXT
BLOB 系 BYTEA
JSON JSONB キーの順番は保持されない
ENUM TEXT+CHECK、または ENUM 型

テーブルを作ったあとで型を変える手順は、既存記事「ALTER TABLEでカラム追加・型変更」で扱っています。

よくある質問

Q. VARCHAR(255) とよく書かれるのはなぜですか? A. MySQL の VARCHAR は、長さを記録するための領域が「255バイトまでは1バイト、それを超えると2バイト」になるため、区切りのよい数字として広まりました。utf8mb4 では1文字が最大4バイトなので、VARCHAR(255) でも長さの記録には2バイト使われます。数字そのものに特別な意味はなく、入ってくる値の最大文字数に合わせて決めれば十分です。

Q. INT と BIGINT のどちらを選べばよいですか? A. 約21億(2,147,483,647)を超える見込みが少しでもある列は BIGINT にします。行数の多いテーブルの主キーや、その主キーを参照する外部キーは、最初から BIGINT にしておくと、あとで型を広げる作業が発生しません。差は1行あたり4バイトです。

Q. PostgreSQL では VARCHAR(n) と TEXT のどちらがよいですか? A. 機能と性能の差はほとんどありません。この記事の検証でも、TEXT に既定値・インデックス・UNIQUE をそのまま付けられました。上限をデータベース側で守りたい列は VARCHAR(n)、上限を決めにくい列は TEXT という分け方が使いやすいです。

Q. FLOAT と DOUBLE はどう使い分けますか? A. FLOAT(PostgreSQL では REAL)は4バイトで有効桁数が約6〜7桁、DOUBLE は8バイトで約15桁です。この記事の検証では、1234567.89 を FLOAT に入れると MySQL で 1234570 と表示されました。近似値を扱う場合も、桁数に余裕のある DOUBLE を選ぶと扱いやすくなります。

まとめ

  • 整数は INT(約±21億)を基本に、増え続ける主キーは BIGINT。PostgreSQL には TINYINT と UNSIGNED がない
  • 誤差を出したくない小数は DECIMAL/NUMERIC。FLOAT・DOUBLE は 0.1 + 0.2 が 0.3 ちょうどにならない
  • VARCHAR(n) の n は文字数。超えると MySQL は Data too long、PostgreSQL は value too long になる
  • MySQL の TEXT は 65,535 バイトまでで、リテラルの DEFAULT と長さ指定なしのインデックスは使えない。PostgreSQL の TEXT は VARCHAR とほぼ同じに使える
  • MySQL の BOOLEAN は TINYINT(1) の別名で 2 も入る。PostgreSQL の BOOLEAN は true/false だけ
  • 日時は、MySQL の DATETIME が PostgreSQL の TIMESTAMP、MySQL の TIMESTAMP が TIMESTAMPTZ に対応する。秒未満が必要なら MySQL では桁数を指定する
  • UUID は16バイトで持つ(PostgreSQL は UUID 型、MySQL は BINARY(16))。JSON は PostgreSQL なら JSONB が基本

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「SQLデータ型一覧|MySQL・PostgreSQL比較とVARCHARとTEXT」

株式会社RJCでは、SQLのデータ型の選び方のような設計の基礎を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?