【SQL】INSERTで日付・日時を登録する完全ガイド|型選択・現在日時・DEFAULT・タイムゾーン・変換関数まで

【SQL】INSERTで日付・日時を登録する完全ガイド|型選択・現在日時・DEFAULT・タイムゾーン・変換関数まで SQL

SQLのINSERT文で日付・日時を登録する際、「どの型を使えばいいか」「現在日時を自動で入れるには」「文字列をどう変換するか」といった疑問が生まれます。さらに、使用するRDBMSによって関数名や書き方が異なるため、初めて触れると混乱しがちです。

本記事では、MySQL・PostgreSQL・SQL Server・Oracleを対象に、INSERTで日付・日時を登録するあらゆる方法を体系的に解説します。

この記事で分かること

  • DATE/DATETIME/TIMESTAMP/TIMESTAMPTZ の違いと選び方
  • リテラル(文字列)で日付を直接指定する書き方
  • 現在日時を自動挿入するRDBMS別の関数一覧
  • DEFAULT値でINSERT時に日時を自動設定する方法
  • 文字列→日付型への変換関数(STR_TO_DATE/TO_DATE/CONVERT)
  • タイムゾーンを考慮した日時INSERTのベストプラクティス
  • よくあるミスとその対処法
スポンサーリンク

日付・日時型の選択

まず、テーブルの列をどの型で定義するかを決めることが最重要です。型の選択を誤ると、後で変換コストがかかったり、タイムゾーン問題が発生したりします。

MySQL PostgreSQL SQL Server Oracle 用途
DATE DATE DATE DATE DATE 日付のみ(時刻不要)
時刻のみ TIME TIME TIME 時刻のみ(日付不要)
日時(TZなし) DATETIME TIMESTAMP DATETIME2 DATE 日付+時刻(タイムゾーンなし)
日時(TZあり) TIMESTAMP* TIMESTAMPTZ DATETIMEOFFSET TIMESTAMP WITH TIME ZONE グローバルサービス・国際化対応
高精度日時 DATETIME(6) TIMESTAMP(6) DATETIME2(7) TIMESTAMP マイクロ秒・ナノ秒が必要な場合

型選択のポイント:

  • 生年月日・予約日など「日付のみ」でよい → DATE
  • 更新日時・ログ日時など時刻も必要 → DATETIME(MySQL)/ TIMESTAMP(PostgreSQL)/ DATETIME2(SQL Server)
  • グローバルサービス・複数タイムゾーン対応 → タイムゾーン付き型(UTCで保存が基本)
  • MySQLの TIMESTAMP はUTCで保存してセッションTZで返す特殊な型(後述)

OracleのDATE型は特殊

OracleのDATE型は他のRDBMSと異なり、秒精度の時刻も含む(YYYY-MM-DD HH24:MI:SS)。日付だけ持ちたくても時刻が格納されます。マイクロ秒・ナノ秒が必要な場合はTIMESTAMP型を使います。

テーブル定義例:用途別の型選択
-- MySQL
CREATE TABLE orders (
    order_id    INT AUTO_INCREMENT PRIMARY KEY,
    order_date  DATE           NOT NULL,          -- 受注日(日付のみ)
    created_at  DATETIME       NOT NULL,          -- 作成日時
    updated_at  DATETIME       NOT NULL,          -- 更新日時
    shipped_at  DATETIME       NULL               -- 出荷日時(NULL可)
);

-- PostgreSQL
CREATE TABLE orders (
    order_id    SERIAL PRIMARY KEY,
    order_date  DATE                     NOT NULL,
    created_at  TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
    updated_at  TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
    shipped_at  TIMESTAMP WITH TIME ZONE NULL
);

リテラルで日付を直接指定する

最も基本的な方法が、日付を文字列リテラルとして直接書く方法です。ISO 8601形式(YYYY-MM-DD)がどのRDBMSでも共通して使えるため、原則としてこの形式を使います。

DATE型への挿入
-- DATE型(YYYY-MM-DD)
INSERT INTO orders (order_date) VALUES ('2024-03-15');

-- 複数列同時指定
INSERT INTO employees (name, hire_date, birth_date)
VALUES ('山田太郎', '2024-04-01', '1990-06-15');
DATETIME・TIMESTAMP型への挿入
-- MySQL: DATETIME
INSERT INTO logs (message, created_at)
VALUES ('処理開始', '2024-03-15 09:30:00');

-- マイクロ秒まで指定(MySQL DATETIME(6))
INSERT INTO logs (message, created_at)
VALUES ('処理開始', '2024-03-15 09:30:00.123456');

-- PostgreSQL: TIMESTAMP
INSERT INTO logs (message, created_at)
VALUES ('処理開始', '2024-03-15 09:30:00');

-- PostgreSQL: TIMESTAMP WITH TIME ZONE(タイムゾーン付き)
INSERT INTO logs (message, created_at)
VALUES ('処理開始', '2024-03-15 09:30:00+09:00');  -- 日本時間 (JST)
SQL Server と Oracle の書き方
-- SQL Server: DATETIME2(推奨)
INSERT INTO logs (message, created_at)
VALUES ('処理開始', '2024-03-15 09:30:00.0000000');

-- SQL Server: 型を明示するキャスト
INSERT INTO orders (order_date)
VALUES (CAST('2024-03-15' AS DATE));

-- Oracle: TO_DATE で書式を明示(必須)
INSERT INTO orders (order_date)
VALUES (TO_DATE('2024-03-15', 'YYYY-MM-DD'));

-- Oracle: TIMESTAMP型への挿入
INSERT INTO logs (created_at)
VALUES (TIMESTAMP '2024-03-15 09:30:00.000000');

注意:日付区切りは必ずハイフン(-)を使う

'2024/03/15' のようにスラッシュを使うとRDBMSによってはエラーになります。ISO 8601形式の '2024-03-15' を統一ルールにしましょう。

現在日時を自動挿入する

INSERT時に「今この瞬間の日時」を入れたい場合は、DBの組み込み関数を使います。RDBMSごとに関数名が異なるため、一覧で確認しておきましょう。

取得内容 MySQL PostgreSQL SQL Server Oracle
現在日時(TZなし) NOW() / SYSDATE() NOW() / CURRENT_TIMESTAMP GETDATE() SYSDATE
現在日時(高精度) NOW(6) clock_timestamp() SYSDATETIME() SYSTIMESTAMP
現在日付のみ CURDATE() / CURRENT_DATE CURRENT_DATE CAST(GETDATE() AS DATE) TRUNC(SYSDATE)
UTC日時 UTC_TIMESTAMP() NOW() AT TIME ZONE ‘UTC’ GETUTCDATE() / SYSUTCDATETIME() SYS_EXTRACT_UTC(SYSTIMESTAMP)
TZ付き日時 —(TIMESTAMP型が自動変換) NOW()(TIMESTAMPTZ列に代入) SYSDATETIMEOFFSET() SYSTIMESTAMP
MySQL — 現在日時のINSERT
-- NOW(): SQLステートメント開始時の日時(トランザクション内で固定)
INSERT INTO access_logs (user_id, action, logged_at)
VALUES (101, 'LOGIN', NOW());

-- SYSDATE(): 実行された瞬間の日時(ストアドプロシージャ内でも変化する)
INSERT INTO access_logs (user_id, action, logged_at)
VALUES (101, 'LOGIN', SYSDATE());

-- 日付のみ
INSERT INTO sales (sale_date, amount) VALUES (CURDATE(), 1000);
INSERT INTO sales (sale_date, amount) VALUES (CURRENT_DATE, 1000);  -- 同義
PostgreSQL — 現在日時のINSERT
-- NOW(): トランザクション開始時の日時(同一トランザクション内では固定)
INSERT INTO access_logs (user_id, action, logged_at)
VALUES (101, 'LOGIN', NOW());

-- clock_timestamp(): 呼び出しごとに実際の現在時刻を返す
INSERT INTO audit_logs (event, logged_at)
VALUES ('checkpoint', clock_timestamp());

-- 日付のみ
INSERT INTO sales (sale_date, amount) VALUES (CURRENT_DATE, 1000);
SQL Server — 現在日時のINSERT
-- GETDATE(): datetime型で返す(ミリ秒精度)
INSERT INTO access_logs (user_id, action, logged_at)
VALUES (101, 'LOGIN', GETDATE());

-- SYSDATETIME(): datetime2型で返す(100ナノ秒精度・推奨)
INSERT INTO access_logs (user_id, action, logged_at)
VALUES (101, 'LOGIN', SYSDATETIME());

-- UTC時刻で保存
INSERT INTO access_logs (user_id, action, logged_at)
VALUES (101, 'LOGIN', GETUTCDATE());

-- 日付のみ
INSERT INTO sales (sale_date, amount)
VALUES (CAST(GETDATE() AS DATE), 1000);
Oracle — 現在日時のINSERT
-- SYSDATE: 秒精度のサーバーローカル日時
INSERT INTO access_logs (user_id, action, logged_at)
VALUES (101, 'LOGIN', SYSDATE);

-- SYSTIMESTAMP: マイクロ秒精度 + タイムゾーン情報
INSERT INTO access_logs (user_id, action, logged_at)
VALUES (101, 'LOGIN', SYSTIMESTAMP);

-- 日付のみ(Oracleのsystimestampから時刻を切り捨て)
INSERT INTO sales (sale_date, amount) VALUES (TRUNC(SYSDATE), 1000);

NOW() と SYSDATE() の違い(MySQL):NOW() はSQLステートメント実行開始時の日時を返し、同一ステートメント内では固定されます。一方SYSDATE() は呼び出された瞬間の日時を返すため、ストアドプロシージャ内のループで呼ぶと異なる値になります。通常はNOW()を推奨します。

DEFAULT値で自動入力する

毎回INSERT文でNOW()を書かなくても、列のDEFAULT値を設定しておけばINSERT時に自動入力されます。アプリケーション側の記述が不要になるため、登録漏れのリスクが下がります。

MySQL — DEFAULT CURRENT_TIMESTAMP
CREATE TABLE articles (
    id           INT AUTO_INCREMENT PRIMARY KEY,
    title        VARCHAR(255)  NOT NULL,
    content      TEXT,
    created_at   DATETIME      NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at   DATETIME      NOT NULL DEFAULT CURRENT_TIMESTAMP
                                        ON UPDATE CURRENT_TIMESTAMP
);

-- created_at, updated_at は省略すると自動入力される
INSERT INTO articles (title, content)
VALUES ('SQLの日付操作', '本文テキスト...');
PostgreSQL — DEFAULT NOW()
CREATE TABLE articles (
    id           SERIAL PRIMARY KEY,
    title        VARCHAR(255)  NOT NULL,
    content      TEXT,
    created_at   TIMESTAMPTZ   NOT NULL DEFAULT NOW(),
    updated_at   TIMESTAMPTZ   NOT NULL DEFAULT NOW()
);

-- created_at は自動入力される
INSERT INTO articles (title, content)
VALUES ('SQLの日付操作', '本文テキスト...');

-- updated_at の自動更新はトリガーで実装(PostgreSQLはON UPDATEなし)
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_articles_updated_at
    BEFORE UPDATE ON articles
    FOR EACH ROW EXECUTE FUNCTION update_updated_at();
SQL Server — DEFAULT SYSDATETIME()
CREATE TABLE articles (
    id           INT IDENTITY(1,1) PRIMARY KEY,
    title        NVARCHAR(255) NOT NULL,
    content      NVARCHAR(MAX),
    created_at   DATETIME2     NOT NULL DEFAULT SYSDATETIME(),
    updated_at   DATETIME2     NOT NULL DEFAULT SYSDATETIME()
);

-- INSERT時に created_at は自動入力される
INSERT INTO articles (title, content)
VALUES ('SQLの日付操作', '本文テキスト...');
Oracle — DEFAULT SYSDATE
CREATE TABLE articles (
    id           NUMBER         GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    title        VARCHAR2(255)  NOT NULL,
    content      CLOB,
    created_at   DATE           DEFAULT SYSDATE NOT NULL,
    updated_at   DATE           DEFAULT SYSDATE NOT NULL
);

-- INSERT時に created_at は自動入力される
INSERT INTO articles (title, content)
VALUES ('SQLの日付操作', '本文テキスト');

DEFAULT値をOVERRIDEするINSERT:DEFAULT値が設定されていても、INSERT文で明示的に値を指定すればそちらが優先されます。特定日付での投入(過去データの移行など)も可能です。

DEFAULT値をOVERRIDEする例
-- 過去日付を明示的に指定(DEFAULT値は使われない)
INSERT INTO articles (title, content, created_at)
VALUES ('過去記事', '本文', '2023-01-15 10:00:00');

文字列から日付型への変換してINSERT

フォームからの入力値や外部データが「文字列」として渡ってくる場合、DB側で日付型に変換してINSERTします。RDBMS別の変換関数を把握しておきましょう。

RDBMS 関数 書き方例
MySQL STR_TO_DATE() STR_TO_DATE(‘2024/03/15’, ‘%Y/%m/%d’)
PostgreSQL TO_TIMESTAMP() / TO_DATE() TO_DATE(‘2024/03/15’, ‘YYYY/MM/DD’)
SQL Server CONVERT() / TRY_CONVERT() / CAST() TRY_CONVERT(DATE, ‘2024/03/15’, 111)
Oracle TO_DATE() / TO_TIMESTAMP() TO_DATE(‘2024/03/15’, ‘YYYY/MM/DD’)
MySQL — STR_TO_DATE
-- スラッシュ区切りの文字列を変換
INSERT INTO orders (order_date, customer_id)
VALUES (STR_TO_DATE('2024/03/15', '%Y/%m/%d'), 101);

-- 日本語形式 '2024年3月15日'
INSERT INTO orders (order_date, customer_id)
VALUES (STR_TO_DATE('2024年03月15日', '%Y年%m月%d日'), 101);

-- 日時文字列の変換
INSERT INTO logs (created_at)
VALUES (STR_TO_DATE('2024-03-15 09:30:00', '%Y-%m-%d %H:%i:%s'));
PostgreSQL — TO_DATE / TO_TIMESTAMP
-- TO_DATE: 日付のみ
INSERT INTO orders (order_date, customer_id)
VALUES (TO_DATE('2024/03/15', 'YYYY/MM/DD'), 101);

-- TO_TIMESTAMP: 日時(タイムスタンプ)
INSERT INTO logs (created_at)
VALUES (TO_TIMESTAMP('2024-03-15 09:30:00', 'YYYY-MM-DD HH24:MI:SS'));

-- CAST / :: によるキャスト(ISO形式ならそのまま変換可能)
INSERT INTO orders (order_date) VALUES ('2024-03-15'::DATE);
INSERT INTO logs  (created_at)  VALUES ('2024-03-15 09:30:00'::TIMESTAMP);
SQL Server — CONVERT / TRY_CONVERT
-- TRY_CONVERT: 変換失敗時にNULLを返す安全な関数(推奨)
INSERT INTO orders (order_date, customer_id)
VALUES (TRY_CONVERT(DATE, '2024/03/15', 111), 101);
-- 111 = yyyy/mm/dd 形式

-- よく使う書式コード(第3引数)
-- 101: mm/dd/yyyy  102: yyyy.mm.dd  103: dd/mm/yyyy
-- 104: dd.mm.yyyy  105: dd-mm-yyyy  111: yyyy/mm/dd  112: yyyymmdd

-- CAST(ISO形式のみ対応)
INSERT INTO orders (order_date) VALUES (CAST('2024-03-15' AS DATE));
Oracle — TO_DATE / TO_TIMESTAMP
-- TO_DATE: 書式を必ず指定する(Oracleは暗黙変換に頼るのは危険)
INSERT INTO orders (order_date, customer_id)
VALUES (TO_DATE('2024/03/15', 'YYYY/MM/DD'), 101);

-- TO_TIMESTAMP: マイクロ秒まで含む変換
INSERT INTO logs (created_at)
VALUES (TO_TIMESTAMP('2024-03-15 09:30:00.123456', 'YYYY-MM-DD HH24:MI:SS.FF6'));

-- 日時文字列を変換してINSERT
INSERT INTO events (event_name, event_dt)
VALUES ('定期メンテ', TO_DATE('20240315 0930', 'YYYYMMDD HH24MI'));

タイムゾーンを考慮した日時INSERT

グローバルなサービスや複数拠点を持つシステムでは、タイムゾーンの取り扱いが重要です。タイムゾーン問題を防ぐ最もシンプルなルールは「DBにはUTCで保存し、表示時にアプリ側でローカル時刻に変換する」です。

UTC保存の3つのメリット:

  • サマータイム(DST)の影響を受けない
  • タイムゾーンまたがりの日付計算がシンプル
  • 複数リージョンのサーバー間でデータ一貫性を保てる
MySQL — タイムゾーン付きINSERT
-- MySQLのTIMESTAMP型はUTCで保存し、@@time_zone設定で表示変換
-- セッションのタイムゾーンを確認
SELECT @@time_zone, @@global.time_zone;

-- JST(UTC+9)のセッションで日時をINSERT → 内部はUTCで保存される
SET time_zone = '+09:00';
INSERT INTO access_logs (user_id, logged_at)
VALUES (101, '2024-03-15 09:30:00');  -- 内部では 2024-03-15 00:30:00 UTC

-- UTC_TIMESTAMP() でUTC日時を明示的に保存
INSERT INTO access_logs (user_id, logged_at)
VALUES (101, UTC_TIMESTAMP());

-- CONVERT_TZ: タイムゾーン変換してINSERT
INSERT INTO access_logs (user_id, logged_at)
VALUES (101, CONVERT_TZ(NOW(), '+09:00', '+00:00'));  -- JSTをUTCに変換
PostgreSQL — TIMESTAMPTZ
-- TIMESTAMPTZ列にタイムゾーン付きで挿入
INSERT INTO access_logs (user_id, logged_at)
VALUES (101, '2024-03-15 09:30:00+09:00');  -- JSTで挿入 → 内部はUTCで保存

-- タイムゾーン名でも指定可能
INSERT INTO access_logs (user_id, logged_at)
VALUES (101, '2024-03-15 09:30:00 Asia/Tokyo');

-- NOW() はTIMESTAMPTZ列に代入するとセッションTZが付与される
INSERT INTO access_logs (user_id, logged_at)
VALUES (101, NOW());

-- 明示的にUTCで保存
INSERT INTO access_logs (user_id, logged_at)
VALUES (101, NOW() AT TIME ZONE 'UTC');
SQL Server — DATETIMEOFFSET
-- DATETIMEOFFSET型にタイムゾーンオフセット付きで挿入
INSERT INTO access_logs (user_id, logged_at)
VALUES (101, '2024-03-15 09:30:00 +09:00');

-- SYSDATETIMEOFFSET(): サーバーのTZ情報付き現在日時
INSERT INTO access_logs (user_id, logged_at)
VALUES (101, SYSDATETIMEOFFSET());

-- AT TIME ZONE で変換
INSERT INTO access_logs (user_id, logged_at)
VALUES (101,
    CONVERT(datetimeoffset, GETUTCDATE()) AT TIME ZONE 'Tokyo Standard Time'
);

NULLと日付

「日付が未定」「まだ処理していない」という状態を表すために、日付型の列にNULLを格納することがあります。

NULL の挿入と活用
-- NULL を明示的に挿入
INSERT INTO orders (order_date, shipped_date)
VALUES ('2024-03-15', NULL);  -- shipped_date は未出荷

-- NOT NULLでない列はINSERT時に省略するとNULLになる
INSERT INTO orders (order_date) VALUES ('2024-03-15');
-- shipped_date が NULL可列であれば自動的にNULL

-- COALESCE で「NULLなら現在日時」とすることも可能
INSERT INTO orders (order_date, confirmed_at)
VALUES ('2024-03-15', COALESCE(NULL, NOW()));  -- → NOW() が使われる

NOT NULL列にNULLを挿入しない:列にNOT NULL制約がある場合、NULLを挿入しようとするとエラーになります。DEFAULT値が設定されていない場合は必ず値を指定するか、DEFAULT値を追加してください。

アプリケーションからのINSERT

実務では、SQLを直接書くより、アプリケーションのORMやデータベースドライバー経由でINSERTを行うケースがほとんどです。プレースホルダーを使ったパラメーター渡しが基本です。

Python(pymysql / MySQL)
import pymysql
from datetime import date, datetime

conn = pymysql.connect(host="localhost", user="root", db="mydb", charset="utf8mb4")
cursor = conn.cursor()

# dateオブジェクトをそのまま渡す(自動でDATE型に変換)
order_date = date(2024, 3, 15)
cursor.execute(
    "INSERT INTO orders (order_date, customer_id) VALUES (%s, %s)",
    (order_date, 101)
)

# datetimeオブジェクトをそのまま渡す(DATETIME型に変換)
created_at = datetime(2024, 3, 15, 9, 30, 0)
cursor.execute(
    "INSERT INTO logs (message, created_at) VALUES (%s, %s)",
    ("処理開始", created_at)
)

# 現在日時はPython側でもDB側でも指定可能
from datetime import timezone
now_utc = datetime.now(timezone.utc)
cursor.execute(
    "INSERT INTO logs (message, created_at) VALUES (%s, %s)",
    ("UTC処理", now_utc)
)

conn.commit()
cursor.close()
conn.close()
Python(psycopg2 / PostgreSQL)
import psycopg2
from datetime import date, datetime, timezone

conn = psycopg2.connect(host="localhost", dbname="mydb", user="postgres")
cursor = conn.cursor()

# dateオブジェクトをDATE列に挿入
cursor.execute(
    "INSERT INTO orders (order_date, customer_id) VALUES (%s, %s)",
    (date(2024, 3, 15), 101)
)

# aware datetimeをTIMESTAMPTZ列に挿入(推奨)
now_utc = datetime.now(timezone.utc)
cursor.execute(
    "INSERT INTO access_logs (user_id, logged_at) VALUES (%s, %s)",
    (101, now_utc)
)

conn.commit()
cursor.close()
conn.close()
Node.js(mysql2 / MySQL)
const mysql = require("mysql2/promise");

async function insertOrder() {
  const conn = await mysql.createConnection({
    host: "localhost", user: "root", database: "mydb",
  });

  const orderDate = new Date("2024-03-15");  // JSTならUTCの差に注意
  const createdAt = new Date();              // 現在日時

  await conn.execute(
    "INSERT INTO orders (order_date, created_at) VALUES (?, ?)",
    [orderDate, createdAt]
  );

  await conn.end();
}
insertOrder();
Java(JDBC PreparedStatement)
import java.sql.*;
import java.time.LocalDate;
import java.time.LocalDateTime;

Connection conn = DriverManager.getConnection(url, user, password);
String sql = "INSERT INTO orders (order_date, created_at) VALUES (?, ?)";

try (PreparedStatement ps = conn.prepareStatement(sql)) {
    // java.time.LocalDate -> DATE列
    ps.setObject(1, LocalDate.of(2024, 3, 15));

    // java.time.LocalDateTime -> DATETIME/TIMESTAMP列
    ps.setObject(2, LocalDateTime.now());

    ps.executeUpdate();
    conn.commit();
}

アプリケーション側のベストプラクティス:

  • プレースホルダー(%s, ?, :param)を使う — SQLインジェクション防止
  • 文字列に変換してから渡さず、日付型オブジェクトをそのまま渡す — ドライバーが適切な型変換を行う
  • タイムゾーンを意識する — DBがUTCなら aware datetime(UTC付き)を渡す

よくあるミスと対処法

ミス1:日付区切りにスラッシュを使う

NG / OK パターン
-- NG: スラッシュ区切りはRDBMSによってエラー or 意図しない変換
INSERT INTO orders (order_date) VALUES ('2024/03/15');  -- NG

-- OK: ハイフン区切りのISO 8601形式
INSERT INTO orders (order_date) VALUES ('2024-03-15');  -- OK

-- やむを得ずスラッシュの場合は明示変換
INSERT INTO orders (order_date)
VALUES (STR_TO_DATE('2024/03/15', '%Y/%m/%d'));  -- MySQL

ミス2:日付を文字列型の列に保存してしまう

日付は必ずDATE/DATETIME型の列に保存する:VARCHAR列に日付を入れると、日付計算・ソート・範囲絞り込みが正しく動作しません。後から直すのも困難です。

文字列型に保存すると起きる問題
-- 文字列型の order_date でソートすると辞書順になる
-- '2024-03-15' < '2024-09-01' は正しいが
-- '2024-1-5'  < '2024-09-01' は数値的には正しいのに辞書順では 'より大きい' 判定になる可能性

-- 日付計算も不可
SELECT DATEDIFF(NOW(), order_date) FROM orders;  -- order_dateがVARCHARだとエラー

ミス3:タイムゾーン考慮忘れ

タイムゾーン問題の例(MySQL)
-- DB が UTC 設定のままアプリから JST で挿入すると...
SET time_zone = '+00:00';  -- サーバーはUTC設定

-- アプリが JST の 2024-03-15 09:00 を挿入したつもりが
INSERT INTO logs (logged_at) VALUES ('2024-03-15 09:00:00');
-- DBには '2024-03-15 09:00:00 UTC' で保存される
-- → JSTでは '2024-03-15 18:00:00' になってしまう(9時間ずれ)

-- 対策: アプリ側でUTCに変換してから渡す、またはCONVERT_TZ を使う
INSERT INTO logs (logged_at)
VALUES (CONVERT_TZ('2024-03-15 09:00:00', '+09:00', '+00:00'));
-- → '2024-03-15 00:00:00 UTC' で保存される(正しい)

ミス4:2桁年の入力

年は必ず4桁で指定する:MySQLでは ’24-03-15′ のように2桁年を書くと自動解釈されますが(70-99は1970〜1999年、00-69は2000〜2069年)、移植性がなくバグの温床になります。常に4桁で書く習慣をつけましょう。

ミス5:月末日の扱い

月末日のINSERT
-- 2024-02-31 はそもそも存在しない → エラー
INSERT INTO orders (order_date) VALUES ('2024-02-31');  -- エラー

-- 月末日を動的に取得してINSERT(MySQL: LAST_DAY)
INSERT INTO billing_periods (period_end)
VALUES (LAST_DAY('2024-02-01'));  -- → 2024-02-29

-- PostgreSQL
INSERT INTO billing_periods (period_end)
VALUES (DATE_TRUNC('month', '2024-02-01'::DATE) + INTERVAL '1 month' - INTERVAL '1 day');
-- → 2024-02-29

まとめ

INSERTで日付・日時を登録するポイントを整理します。

目的 方法
型の選択 日付のみ→DATE、日時→DATETIME/TIMESTAMP、国際化→TZ付き型
リテラル指定 ‘YYYY-MM-DD’ / ‘YYYY-MM-DD HH:MM:SS’ のISO形式を統一ルールに
現在日時の自動挿入 MySQL: NOW() / PostgreSQL: NOW() / SQL Server: SYSDATETIME() / Oracle: SYSDATE
INSERT時の自動化 DEFAULT CURRENT_TIMESTAMP で省略可能にする
文字列からの変換 MySQL: STR_TO_DATE() / PG: TO_DATE() / SQL Server: TRY_CONVERT() / Oracle: TO_DATE()
タイムゾーン DBにはUTCで保存し、表示時にアプリでローカル変換
アプリ連携 プレースホルダーを使い、日付型オブジェクトをそのまま渡す
  • 日付はRDBMS問わず ISO 8601形式(ハイフン区切り)で統一する
  • INSERT文に毎回NOW()を書く代わりに DEFAULT CURRENT_TIMESTAMPを活用する
  • グローバルサービスでは UTCで保存してタイムゾーン問題を根本から防ぐ
  • アプリからの挿入では プレースホルダー + 日付型オブジェクトを使い、SQLインジェクションを防ぐ

INSERT文の書き方全般についてはINSERT文の使い方完全解説、日付の比較については日付の比較完全ガイド、日付の範囲指定については日付の範囲指定完全ガイドも合わせてご覧ください。