記事の目的
SQL の ALTER TABLE 文を使って、既存テーブルの構造(列・データ型・制約・テーブル名)を変更できるようになります。
列の追加( ADD )、削除( DROP )、型変更、列名・テーブル名のリネーム、制約の追加・削除までを、PostgreSQL・MySQL・SQL Server の製品差に注意しながら整理します。ALTER TABLE は製品差がとくに大きい構文のため、製品ごとの書き方を比較表でまとめます。
重要な注意(本記事の操作はすべて DDL です)
ALTER TABLE はテーブルの定義そのものを変える操作です。実行すると元に戻せない変更が含まれ、本番環境では他のクエリのロック待ち・データ消失・想定外のダウンタイムを引き起こすことがあります。
本記事のコードを本番環境で動かす前に、必ずバックアップを取得し、テスト環境で同じ操作を試してから実行してください。とくに DROP COLUMN ・列の型変更・ NOT NULL 制約の追加は、データ消失や長時間ロックのリスクが高い操作です。
記事のサンプルテーブルの前提
本記事では、説明用に次のような users テーブルがある前提で進めます。
- id :自動採番(主キー)
- name :氏名(文字列)
- email :メールアドレス(文字列)
実際の CREATE TABLE 文は別記で扱います。本記事では、この users テーブルに対して列を足したり、型を変えたり、リネームしたりしていきます。
最短の答え
最もよく使う4パターンを並べます。詳しい説明・製品差は後述します。
-- 列を追加(age 列を整数型で追加)
ALTER TABLE users ADD COLUMN age INTEGER;
-- 列を削除
ALTER TABLE users DROP COLUMN age;
-- PostgreSQL: age 列の型を BIGINT に変える
ALTER TABLE users ALTER COLUMN age TYPE BIGINT;
-- PostgreSQL: name 列を full_name にリネーム
ALTER TABLE users RENAME COLUMN name TO full_name;MySQL / SQL Server での書き方は後述の比較表で示します。
- 主要な RDBMS(PostgreSQL / MySQL / SQL Server)で共通の書き方
-- 列を追加(age 列を整数型で追加)
ALTER TABLE users ADD COLUMN age INTEGER;
-- 列を削除
ALTER TABLE users DROP COLUMN age;- 列の型変更とリネームは製品差が大きいので、まずは PostgreSQL での書き方を示します。
-- PostgreSQL: age 列の型を BIGINT に変える
ALTER TABLE users ALTER COLUMN age TYPE BIGINT;
-- PostgreSQL: name 列を full_name にリネーム
ALTER TABLE users RENAME COLUMN name TO full_name;ここから先は、操作の種類ごとに構文と製品差を整理します。
列を追加する(ADD COLUMN)
新しい列をテーブルに追加します。最も使う頻度が高い ALTER TABLE です。
ALTER TABLE テーブル名 ADD COLUMN 列名 データ型 [制約];- 列名・データ型は必須。
- 既定値(
DEFAULT)・NOT NULLなどの制約をその場で付けることもできます。 - 既存行があるテーブルに
NOT NULL列を追加する場合は、既存行に入れる値が必要になります。小さなテーブルではDEFAULT付きで追加できますが、大きなテーブルでは「NULL 許容で列を追加 →UPDATEで値を埋める →NOT NULL制約を追加」のように段階的に変更したほうが、ロック影響と失敗時のリカバリの面で安全な場合があります。
製品差(COLUMN キーワードの扱い)
| 製品 | 構文 |
|---|---|
| PostgreSQL | ALTER TABLE users ADD COLUMN age INTEGER; |
| MySQL | ALTER TABLE users ADD COLUMN age INT; ( COLUMN は省略可) |
| SQL Server | ALTER TABLE users ADD age INT; ( COLUMN キーワードを付けない) |
3製品共通で動かしたい場合は、上記の表のように製品ごとに書き分けます。
既定値付きの追加
-- PostgreSQL / MySQL(DEFAULT 付きで列を追加。既存行にも DEFAULT 値が入る)
ALTER TABLE users
ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP;-- SQL Server(COLUMN は省略)
ALTER TABLE users
ADD created_at DATETIME2 DEFAULT SYSUTCDATETIME();DEFAULT 付きで列を追加すると、既存行を参照したときにも既定値が入ったように扱われます。実際にテーブルを物理的に書き換えるか・どの程度のロックが発生するかは、製品・バージョン・ DEFAULT 式の内容(定数か関数か)によって異なります。大きなテーブルで実行する前に、対象環境の挙動をテスト環境で確認してください。
列を削除する(DROP COLUMN)
列の削除はデータの完全削除です。一度削除した列は元に戻せません。
ALTER TABLE users DROP COLUMN memo;製品差
| 製品 | 構文 |
|---|---|
| PostgreSQL | ALTER TABLE users DROP COLUMN memo [CASCADE]; |
| MySQL | ALTER TABLE users DROP COLUMN memo; ( COLUMN は省略可) |
| SQL Server | ALTER TABLE users DROP COLUMN memo; |
削除できないケースとよくあるエラー
- 列に外部キー制約が貼られていると、先に制約を
DROPする必要があります。PostgreSQL はCASCADEを付けると連鎖的に削除しますが、影響範囲が広がるので慎重に。 - 列にインデックスがある場合、製品によってはインデックスごと削除されます。
- 列がビューから参照されていると、製品によっては失敗します。
「本当に消してよいか」を確認してから実行する習慣をおすすめします。
列の型を変更する(製品差が大きい)
列の型を変えたい場合の書き方は、3製品で異なります。
| 製品 | 構文 |
|---|---|
| PostgreSQL | ALTER TABLE users ALTER COLUMN age TYPE BIGINT [USING 変換式]; |
| MySQL | ALTER TABLE users MODIFY COLUMN age BIGINT; |
| SQL Server | ALTER TABLE users ALTER COLUMN age BIGINT; |
-- PostgreSQL: 整数列を文字列に変える例(USING で明示的に変換)
ALTER TABLE users
ALTER COLUMN age TYPE TEXT USING age::TEXT;-- MySQL: 列定義を上書きする(NULL 許容や DEFAULT も同時に指定)
ALTER TABLE users
MODIFY COLUMN age BIGINT NOT NULL DEFAULT 0;型変更時の注意点
- 値が新しい型の範囲に収まらない場合、エラーまたは切り詰めが発生します。たとえば
BIGINT→INTへの変更は、大きな値が入っていれば失敗します。 - PostgreSQL は
USING句で「どう変換するか」を明示できます(暗黙変換できない場合は必須)。 - 大きなテーブルへの型変更は、内部でテーブル書き換えが起きてロック時間が長くなることがあります。MySQL では
ALGORITHM=INPLACE、PostgreSQL では特定の型同士の変換が高速化される条件など、各製品のドキュメントで挙動を確認してください。
列をリネームする(製品差が大きい)
列名を変える操作は、3製品で書き方がまったく違います。
| 製品 | 構文 |
|---|---|
| PostgreSQL | ALTER TABLE users RENAME COLUMN name TO full_name; |
| MySQL(8.0 以降) | ALTER TABLE users RENAME COLUMN name TO full_name; |
| SQL Server | EXEC sp_rename 'users.name', 'full_name', 'COLUMN'; ( ALTER TABLE ではなくストアドプロシージャ) |
MySQL 8.0 以降では RENAME COLUMN が使えます。古い MySQL 5.x 系 では CHANGE COLUMN を使い、ALTER TABLE users CHANGE COLUMN name full_name VARCHAR(255) NOT NULL; のように型と制約を再指定する必要があります(現行の定義を SHOW CREATE TABLE 等で確認してから実行)。
SQL Server だけは ALTER TABLE ではなく sp_rename ストアドプロシージャを使います。sp_rename は列名・テーブル名そのものは変更しますが、その名前を参照しているビュー・ストアドプロシージャ・トリガー・アプリケーション側 SQL までは自動で直してくれません。リネーム前に依存関係を確認し、必要なら関連するオブジェクトも同時に修正します。
制約の追加・変更・削除
制約の追加・削除は製品差が大きく、特に外部キーや DEFAULT 制約は個別の注意点があります。本記事では代表的な構文の入口だけを示し、詳しい挙動や運用上のコツは各制約の個別記事で扱います。
NOT NULL 制約
| 製品 | 追加 | 削除 |
|---|---|---|
| PostgreSQL | ALTER COLUMN age SET NOT NULL | ALTER COLUMN age DROP NOT NULL |
| MySQL | MODIFY COLUMN age INT NOT NULL (型ごと再指定) | MODIFY COLUMN age INT NULL |
| SQL Server | ALTER COLUMN age INT NOT NULL | ALTER COLUMN age INT NULL |
UNIQUE 制約・外部キー制約・チェック制約
CONSTRAINT 名を付けて追加すると、あとで削除しやすくなります。
-- UNIQUE 制約を追加(3 製品共通)
ALTER TABLE users ADD CONSTRAINT uq_users_email UNIQUE (email);
-- 外部キー制約を追加(3 製品共通)
ALTER TABLE orders
ADD CONSTRAINT fk_orders_user
FOREIGN KEY (user_id) REFERENCES users(id);
-- 制約を削除(PostgreSQL / SQL Server)
ALTER TABLE users DROP CONSTRAINT uq_users_email;
-- MySQL の制約削除(種類で構文が違う)
ALTER TABLE users DROP INDEX uq_users_email; -- UNIQUE / INDEX
ALTER TABLE orders DROP FOREIGN KEY fk_orders_user; -- 外部キーDEFAULT 値の変更
| 製品 | 既定値を設定 | 既定値を削除 |
|---|---|---|
| PostgreSQL | ALTER COLUMN age SET DEFAULT 0 | ALTER COLUMN age DROP DEFAULT |
| MySQL | ALTER COLUMN age SET DEFAULT 0 | ALTER COLUMN age DROP DEFAULT |
| SQL Server | 既存の DEFAULT 制約を DROP CONSTRAINT してから ADD CONSTRAINT … DEFAULT … を追加 | 同上 |
SQL Server は DEFAULT も「制約」として管理されているため、変更時は古い制約を削除して新しく追加し直すのが定石です。DEFAULT 制約に名前を明示せずに作成した場合は自動生成名が付くため、削除前に sys.default_constraints などのシステムカタログ、または SSMS で実際の制約名を確認してから DROP CONSTRAINT を実行します。
テーブルをリネームする
| 製品 | 構文 |
|---|---|
| PostgreSQL | ALTER TABLE users RENAME TO app_users; |
| MySQL | ALTER TABLE users RENAME TO app_users; または RENAME TABLE users TO app_users; |
| SQL Server | EXEC sp_rename 'users', 'app_users'; |
MySQL では複数テーブルを一気に入れ替えたい場合、 RENAME TABLE のほうが原子的に動くため安全と言われます。
-- MySQL: アプリ無停止での切り替えに使われるイディオム
RENAME TABLE users TO users_old, users_new TO users;落とし穴・注意点
以下は本番環境で実際に問題になりやすいポイントです。実行前に必ず確認してください。
データ消失のリスク
DROP COLUMN は完全な削除です。元に戻したい場合は、削除前に取得していたバックアップから復元するしかありません。CASCADE を使うと、関連する外部キー・ビュー・依存オブジェクトまで削除されます。
影響範囲は、PostgreSQL では psql の \d メタコマンド(PostgreSQL 専用)、3製品共通では INFORMATION_SCHEMA /各製品のシステムカタログで確認してから実行します。
ロックと運用への影響
大きなテーブルへの ALTER TABLE は、テーブル全体に長時間のロックを取ることがあります。挙動は製品とバージョンで大きく異なります。
- MySQL 5.6 以降:
ALGORITHM=INPLACE/LOCK=NONEをヒントとして指定できる操作があります。 - PostgreSQL:多くの
ALTERは軽い操作(メタデータ変更のみ)ですが、一部の型変更やNOT NULLの追加などはテーブル書き換えが発生し、長時間ロックの原因になります。 - SQL Server:オンラインでの再構築(
WITH (ONLINE = ON))など、Enterprise エディションでのオプションがあります。
本番運用では、メンテナンスウィンドウ・オンラインでの段階的変更・ pt-online-schema-change (MySQL)/ pg-osc などのツール利用を検討してください。
NOT NULL を後から追加するときの既存データ
既存行に NULL が入っている列に NOT NULL 制約を追加すると、エラーになります。先に UPDATE で値を埋めるか、 DEFAULT を同時に指定して既存行を埋めます。
-- PostgreSQL の例:先に既存行を埋めてから NOT NULL を追加
UPDATE users SET age = 0 WHERE age IS NULL;
ALTER TABLE users ALTER COLUMN age SET NOT NULL;トランザクション内での実行可否
- PostgreSQL:ほとんどの
ALTER TABLEがトランザクション可能で、ROLLBACKできます。複数のスキーマ変更を 1 つのトランザクションでまとめると安全です。 - MySQL:DDL 文は基本的に暗黙コミットが走り、
ROLLBACKできません。失敗しても元に戻らないため、テスト環境での事前確認が必須です。 - SQL Server:トランザクション可能ですが、長時間ロックの問題は別途あります。
MySQL の MODIFY と CHANGE の違い
MySQL には型変更系の構文が 2 つあります。混同しないように区別します。
MODIFY COLUMN:列の定義(型・制約)を変える。列名は変えない。CHANGE COLUMN:列の名前と定義を同時に変える。リネームしないときも、必ず新しい列名(古い名前と同じでも可)を指定する必要があります。
まとめ
ALTER TABLE はテーブル構造を変える DDL ですが、 列追加( ADD )・列削除( DROP )・型変更・リネーム・制約の追加/削除の5パターンを軸に押さえ、3製品の構文差を比較表で確認すれば、現場の構造変更はほぼ書けるようになります。本番環境では、バックアップ・テスト環境での事前確認・ロック影響の見積もりを必ず行ってください。
