SQLで日付をINSERTする直接の答えは、手書きSQLならDBMSが正式にサポートする曖昧でない日付リテラルを使い、アプリからなら文字列連結ではなく型付きパラメーターを使うことです。PostgreSQL、MySQL、OracleではDATE '2026-07-12'、SQL ServerではISO 8601の'2026-07-12'をdate列へ渡せます。SQLiteには専用のDATEストレージ型がないため、保存形式を設計で決めます。
DATEという名前でも製品間で意味は同じではありません。PostgreSQL、MySQL、SQL ServerのDATEは日単位ですが、Oracle DatabaseのDATEは時・分・秒も保持します。さらに、NULLと'NULL'は別物です。引用符付きは文字列であり、欠損値ではありません。
DBMS別の判断表
| DBMS | DATEの意味 | 安全な手書き例 | 注意点 |
|---|---|---|---|
| PostgreSQL | 日付のみ | DATE '2026-07-12' | 曖昧な月日順はDateStyleに依存 |
| MySQL 8.4 | 日付のみ | DATE '2026-07-12' | 無効日付・ゼロ日付はSQLモード確認 |
| SQL Server | 日付のみ | '2026-07-12' | DATE '...'構文は使わない |
| Oracle Database | 日付と時・分・秒 | DATE '2026-07-12' | リテラルは午前0時だが列は時刻を保持 |
| SQLite | 専用型なし | '2026-07-12'をTEXT方針で保存 | 型宣言だけでは妥当性を保証しない |
基本テーブルと確認事項
例ではemployees(employee_id, employee_name, hire_date)を使います。実行前に、hire_dateの実際の型、NULL許容、DEFAULT、CHECK制約、トリガーを確認してください。「INSERTが失敗するから」という理由だけでNOT NULL制約を外すのは危険です。採用日が必須という業務ルールまで弱めてしまいます。
- 列は本当にDATEか、DATETIME/TIMESTAMPか、文字列か
- 日だけを保存するか、時刻・タイムゾーンも必要か
- 欠損をNULLで表せるか、DEFAULTを使うか
- 現在日付はサーバー日、利用者の現地日、業務日のどれか
- アプリ入力は型付きパラメーターで渡されるか
PostgreSQLでDATEをINSERTする
PostgreSQLでは、型を明示した日付リテラルが読みやすく、DateStyleに依存しにくい方法です。
INSERT INTO employees (employee_name, hire_date)
VALUES ('山田 太郎', DATE '2026-07-12');
PostgreSQL公式マニュアルは1999-01-08のようなISO 8601形式を、どのDateStyleでも解釈できる推奨形式として示しています。'01/02/03'のような文字列はMDY、DMY、YMDで意味が変わるため使わないでください。
登録した行をすぐ確認するならRETURNINGを使えます。
INSERT INTO employees (employee_name, hire_date)
VALUES ('山田 太郎', DATE '2026-07-12')
RETURNING employee_id, employee_name, hire_date;
MySQLでDATEをINSERTする
MySQL 8.4は標準SQLの型付き日付リテラルを認識します。区切りを省いた数値や緩い形式も受理する場合がありますが、移植性と監査性のため年4桁、月日2桁、ハイフン区切りに統一します。
SELECT @@SESSION.sql_mode;
MySQLでは無効日付、ゼロ日付、警告とエラーの扱いがsql_modeに影響されます。まずINSERT前の事前確認として、同じセッションでSELECT @@SESSION.sql_mode;を単独実行します。strict modeでは、不正な日付が警告ではなくINSERTエラーになる場合があります。検証用INSERTの条件を失わないよう、INSERT直後に、間へSELECTなどの非診断文を挟まずSHOW WARNINGSを実行してください。
INSERT INTO employees (employee_name, hire_date)
VALUES ('山田 太郎', DATE '2026-07-12');
SHOW WARNINGS;
ALLOW_INVALID_DATESは月が1〜12、日が1〜31かという限定的な検査へ緩めるため、存在しない日付を受理し得ます。日付品質が必要な業務データで、INSERTを通すためだけに有効化しないでください。'0000-00-00'も通常の不明日付の代わりにせず、欠損が許されるならNULLを使います。
SQL ServerでdateをINSERTする
SQL Serverのdateはyyyy-MM-ddとyyyyMMddをISO 8601形式としてサポートします。T-SQLではPostgreSQLなどのDATE '...'構文を使いません。
INSERT INTO employees (employee_name, hire_date)
VALUES (N'山田 太郎', '2026-07-12');
入力と保存値を同時に確認するならOUTPUTを使えます。
INSERT INTO employees (employee_name, hire_date)
OUTPUT INSERTED.employee_id,
INSERTED.employee_name,
INSERTED.hire_date
VALUES (N'山田 太郎', '2026-07-12');
'07/12/26'や'12-07-2026'は言語とDATEFORMATで解釈が変わります。四桁年のISO形式を使います。アプリからは文字列に整形せず、date型のパラメーターへ渡す方が確実です。
Oracle DatabaseでDATEをINSERTする
OracleのANSI日付リテラルはNLS_DATE_FORMATに依存せず、形式はYYYY-MM-DDです。
INSERT INTO employees (employee_name, hire_date)
VALUES ('山田 太郎', DATE '2026-07-12');
この日付リテラルには時刻部分がなく、保存時は午前0時です。ただしOracleのDATE列は年・月・日だけでなく時・分・秒も保持します。別の処理がSYSDATEや時刻付き値を入れていると、画面が日付だけを表示していても時刻が残ります。
SELECT employee_id,
TO_CHAR(hire_date, 'YYYY-MM-DD HH24:MI:SS') AS stored_value
FROM employees
WHERE employee_id = :employee_id;
外部入力が12/07/2026のような文字列しかない場合は、暗黙変換へ渡さずTO_DATE(value, 'DD/MM/YYYY')のように書式を明示します。ただしアプリ側でバインドできるなら、文字列変換より型付きパラメーターを優先します。
SQLiteで日付を保存する
SQLiteには日付・時刻専用のストレージクラスがありません。公式資料はISO 8601テキスト、ユリウス日、Unix timestampなどを選べると説明しています。日付だけを並べ替え・比較する用途では、固定長のYYYY-MM-DDテキストが扱いやすい方法です。
INSERT INTO employees (employee_name, hire_date)
VALUES ('山田 太郎', '2026-07-12');
列をDATEと宣言しても、他DBMSのような日付型検査が自動で付くわけではありません。アプリで実在日を検証し、保存形式を一つに固定し、必要ならスキーマのCHECK制約とSTRICTテーブルを設計してください。単純なdate(value)だけの検査で全ての暦上の不正値を防げると断定せず、採用するSQLite版でテストします。
NULL・DEFAULT・列省略の違い
| 書き方 | 意味 | 前提 |
|---|---|---|
NULL | SQLの欠損値 | 列がNULLを許可 |
'NULL' | 4文字の文字列 | DATE列では使わない |
| 列をINSERT一覧から省略 | DEFAULTを適用 | 既定値とNULL許容を確認 |
DEFAULT | その列の既定値を要求 | 構文対応とDEFAULT定義を確認 |
採用日が未定でNULLを許す設計なら、引用符なしで指定します。
INSERT INTO employees (employee_name, hire_date)
VALUES ('山田 太郎', NULL);
列を省略した場合は「NULLを入れる」とは限りません。DEFAULT CURRENT_DATEがあれば現在日が入り、既定値がなくNOT NULLならエラーになり得ます。意図が欠損なのか、既定値なのかをSQLで区別してください。
現在日付をINSERTする
現在日付は、どのタイムゾーンの「今日」かを決めてから使います。DBサーバー、接続セッション、アプリ利用者の現地時間、業務締め日の基準が違うと、深夜帯に1日ずれます。
| DBMS | 日だけを得る例 | 基準 |
|---|---|---|
| PostgreSQL | CURRENT_DATE | 現在トランザクションとセッション設定 |
| MySQL | CURRENT_DATE | セッションのタイムゾーン |
| SQL Server | CAST(SYSDATETIME() AS date) | サーバーのローカル日時 |
| Oracle | TRUNC(CURRENT_DATE) | セッションのタイムゾーン |
| SQLite | date('now') | UTC。ローカルなら'localtime'修飾 |
OracleのCURRENT_DATEはDATE値で時刻も含むため、日だけを保存したい例ではTRUNCします。SQLiteで端末のローカル日が必要ならdate('now', 'localtime')ですが、複数地域の利用者がいるシステムでは端末任せにせず、業務タイムゾーンを設計します。
アプリではPreparedStatementを使う
利用者入力をSQL文字列へ連結すると、日付書式の問題に加えてSQLインジェクションの原因になります。JDBCではPreparedStatementへ値をバインドし、日付はsetDateでSQL DATEとして送れます。
String sql =
"INSERT INTO employees (employee_name, hire_date) VALUES (?, ?)";
try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, employeeName);
ps.setDate(
2,
java.sql.Date.valueOf(
java.time.LocalDate.of(2026, 7, 12)
)
);
int rows = ps.executeUpdate();
if (rows != 1) {
throw new SQLException("想定外の更新件数: " + rows);
}
}
PreparedStatementは値をSQL構文ではなくパラメーターとして扱います。トランザクションを自動コミットにするか、明示コミットにするかはアプリ設計に従います。複数処理をまとめる場合は、例外時にロールバックし、接続を閉じる前に状態を確定してください。
トランザクション内で安全に試す
MySQLでトランザクション試験を始める前に、次の読み取り専用SELECTで対象テーブルのストレージエンジンを確認します。結果が空なら、接続中のデータベース名とテーブル名も見直してください。
SELECT ENGINE
FROM information_schema.tables
WHERE table_schema = DATABASE()
AND table_name = 'employees';
本番データへいきなり入れず、ステージングまたは検証用レコードで1件だけ試します。MySQLでROLLBACKを保護として扱えるのは、InnoDBなどのトランザクション対応エンジンで、まだCOMMITしていない変更に限ります。MyISAMなど非トランザクションテーブルへの変更はROLLBACKできません。ほかのDBMSでもトランザクション条件を確認してください。DDLを同じ試験へ混ぜると、MySQLやOracleなどで暗黙コミットが関係するため避けます。
- PostgreSQL・SQLiteは
BEGIN、MySQLは対象がInnoDBなどのトランザクション対応エンジンだと確認してからSTART TRANSACTION、SQL ServerはBEGIN TRANSACTIONで開始します。OracleはDML後に明示コミットするまでロールバックできます。 - 一意に識別できる検証レコードを1件だけINSERTします。
- 主キー、日付列、必要なら時刻表示をSELECTします。
- 想定と違えば
ROLLBACKします。MySQLではトランザクション対応エンジンに限る点を再確認します。 - 本番処理では更新件数と保存値を確認してから
COMMITします。
コミット後の取り消しは単純なROLLBACKではできません。削除・訂正が必要なら、バックアップと監査要件を確認し、主キーで対象を限定した別トランザクションとして実施します。この記事では誤って広範囲を消す危険があるため、主キー条件のないDELETE例は掲載しません。
エラーと失敗分岐
| 症状 | 主な原因 | 安全な確認 |
|---|---|---|
| 変換エラー | DBMS非対応構文、曖昧形式 | DBMS名を確認し、DB別の例へ戻す |
| 月日が逆 | MDY/DMY、二桁年 | ISO形式と型付きパラメーターへ統一 |
| 無効日付が入った | MySQL SQLモード、SQLite型検査なし | 設定、警告、制約、アプリ検証を確認 |
| 時刻が残る | Oracle DATE、datetime系列 | 時刻まで明示表示して確認 |
| NULL制約違反 | 必須列へNULL | 入力を直す。制約を即座に外さない |
| 1日ずれる | UTC/ローカル/セッション差 | 業務タイムゾーンを明示 |
| アプリでは失敗する | 文字列連結、ドライバー型不一致 | パラメーター型とドライバー資料を確認 |
エラー時はメッセージ全文、DBMSと版、列定義、実際に送ったパラメーター型、セッション設定を記録します。INSERTを通す目的でSQLモード、NLS設定、DATEFORMAT、列制約を広く変更すると、別処理まで変わります。まず入力とSQLを正し、設定変更は影響調査を伴う別作業にします。
よくある質問
日付は必ずシングルクォートで囲みますか?
文字列表現はシングルクォートで囲みますが、PostgreSQL、MySQL、OracleのDATE '2026-07-12'は、DATEキーワードと引用文字列を組み合わせた型付きリテラルです。アプリではSQLへ引用符を組み立てず、パラメーターAPIへ日付型を渡します。
2026-7-2でも保存できますか?
受理するDBMSはありますが、移植性と入力検証のため2026-07-02へ統一してください。「受理される」と「推奨形式」は別です。二桁年は世紀の解釈も生むため使いません。
DATE列に時刻を入れるとどうなりますか?
PostgreSQL、MySQL、SQL ServerのDATEでは時刻を保持しませんが、変換時の切り捨てまたはエラー挙動をDBMSごとに確認します。OracleのDATEは時刻を保持します。時刻やタイムゾーンが要件なら、DATEではなく適切なTIMESTAMP系の型を設計してください。
CSVから大量INSERTするときも同じですか?
形式の原則は同じですが、製品ごとのバルクロード機能には日付書式、エラー行、トランザクション、文字コードの設定があります。最初に少量で検証し、拒否行と警告を保存し、全件件数と日付範囲を照合してください。1行ずつSQL文字列を連結する方法は避けます。
まとめ
手書きSQLではPostgreSQL・MySQL・OracleのDATE 'YYYY-MM-DD'、SQL ServerのISO 8601文字列を使い、アプリでは型付きパラメーターを使います。NULL、DEFAULT、列省略は別の意味です。Oracle DATEの時刻、MySQLのSQLモードとストレージエンジン、SQLiteの動的型、現在日のタイムゾーンを区別してください。MySQLではInnoDBなどのトランザクション対応エンジンだと確認したうえで、1件をトランザクション内で検証してからコミットします。
公式資料
- PostgreSQL: Date/Time Types
- MySQL 8.4: Date and Time Literals
- MySQL 8.4: Date and Time Data Types
- MySQL 8.4: SHOW WARNINGS Statement
- MySQL 8.4: COMMIT and ROLLBACK Statements
- Microsoft Learn: date (Transact-SQL)
- Oracle Database: Literals
- Oracle Database: Data Types
- SQLite: Datatypes In SQLite
- Java SE 26: PreparedStatement

コメント