データベースのcreated_at・updated_at・deleted_atとは?新人エンジニア向けに活用方法を解説

こんにちは。ゆうせいです。

新人研修中に受講者から以下の質問をいただきました。

created_at・updated_at・deleted_atなどのフィールドはなんのためにあるのですか?

今回は、データベースでよく見かけるcreated_at、updated_at、deleted_atについて解説します。

システム開発をしていると、テーブルに次のようなカラムが入っていることがあります。

created_at updated_at deleted_at

最初に見たときは、こう思うかもしれません。

「全部、日時っぽいけど何が違うの?」

「created_atだけあればよくない?」

「deleted_atって削除された日時?でも削除したらデータは消えるのでは?」

とても良い疑問です。

created_at、updated_at、deleted_atは、データがいつ作られ、いつ更新され、いつ削除扱いになったのかを記録するための重要なカラムです。

たとえるなら、学校の図書館にある本の管理カードのようなものです。

「いつ登録された本か」

「いつ情報が修正されたか」

「いつ廃棄扱いになったか」

このような履歴がわかると、あとから確認しやすいですよね。

データベースでも同じです。

created_atとは何か

created_atとは、そのデータが作成された日時を表すカラムです。

createdは「作成された」という意味です。

atは「時点」を表します。

つまり、created_atは「作成された時点」という意味になります。

たとえば、ユーザー登録機能があるとします。

山田さんが2026年6月1日 10時30分に会員登録した場合、usersテーブルには次のようなデータが入ります。

user_idnamecreated_at
1山田2026-06-01 10:30:00

created_atを見ると、山田さんがいつ登録されたユーザーなのかがわかります。

この情報は、意外といろいろな場面で役立ちます。

活用場面内容
新規登録ユーザーの分析今月何人登録したかを調べられる
並び替え新しい順、古い順で一覧表示できる
問い合わせ調査いつ作られたデータなのか確認できる
初回特典の判定登録から何日以内かを判定できる

created_atは、データの誕生日のようなものです。

人に誕生日があるように、データにも「生まれた日時」があります。

updated_atとは何か

updated_atとは、そのデータが最後に更新された日時を表すカラムです。

updatedは「更新された」という意味です。

つまり、updated_atは「最後に更新された時点」という意味です。

たとえば、商品テーブルを考えてみましょう。

car_idnamepricecreated_atupdated_at
1プリウス25000002026-06-01 10:30:002026-06-01 10:30:00

最初に登録した時点では、created_atとupdated_atが同じ日時になることが多いです。

その後、価格を250万円から240万円に変更したとします。

car_idnamepricecreated_atupdated_at
1プリウス24000002026-06-01 10:30:002026-06-05 15:20:00

created_atは変わりません。

なぜなら、作成日時は変わらないからです。

一方で、updated_atは更新日時に変わります。

updated_atを見ると、「このデータは最後にいつ直されたのか」がわかります。

updated_atは、ノートの最終更新日のようなものです。

ノートを最初に買った日はcreated_atです。

最後に書き込みをした日はupdated_atです。

deleted_atとは何か

deleted_atとは、そのデータが削除扱いになった日時を表すカラムです。

deletedは「削除された」という意味です。

つまり、deleted_atは「削除された時点」という意味です。

ただし、ここが少し大切です。

deleted_atを使う場合、データを本当にDELETE文で消すとは限りません。

deleted_atに日時を入れて、「削除されたものとして扱う」ことが多いです。

この考え方を論理削除と呼びます。

論理削除とは、データそのものはテーブルに残したまま、削除済みとして扱う方法です。

反対に、DELETE文でデータを完全に消す方法を物理削除と呼びます。

削除方法意味イメージ
物理削除データを完全に消すノートのページを破って捨てる
論理削除削除日時を入れて削除扱いにするノートに赤字で「使用しない」と書く

たとえば、carsテーブルに次のデータがあるとします。

car_idnamepricedeleted_at
1プリウス2400000NULL
2古い車両3000002026-06-10 09:00:00

deleted_atがNULLのデータは、まだ削除されていないデータです。

deleted_atに日時が入っているデータは、削除済みとして扱います。

一覧画面に表示したいのは、基本的にdeleted_atがNULLのデータだけです。

SELECT car_id, name, price, created_at, updated_at, deleted_at FROM cars WHERE deleted_at IS NULL;




このSQLによって、削除済みではない車だけを取得できます。

3つのカラムの違い

created_at、updated_at、deleted_atの違いを表で整理しましょう。

カラム名意味変更されるタイミングたとえ
created_at作成日時データを作成したときデータの誕生日
updated_at更新日時データを変更したときノートの最終記入日
deleted_at削除日時論理削除したとき使用停止になった日

この3つを入れておくと、データの状態がかなり追いやすくなります。

「いつ作られたのか」

「いつ変更されたのか」

「削除済みなのか」

この3つがわかるだけで、運用中の調査がかなり楽になります。

MySQLでのテーブル定義例

carsテーブルを例にして、created_at、updated_at、deleted_atを入れたテーブル定義を見てみましょう。

CREATE TABLE cars (
    car_id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    price INT NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at DATETIME NULL
);




それぞれの意味を見ていきます。

定義意味
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP作成時に現在日時を自動で入れる
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP作成時と更新時に現在日時を自動で入れる
deleted_at DATETIME NULL削除されていなければNULL、削除時に日時を入れる

CURRENT_TIMESTAMPは、現在日時を表します。

DEFAULT CURRENT_TIMESTAMPを指定すると、INSERT時に自動で現在日時が入ります。

ON UPDATE CURRENT_TIMESTAMPを指定すると、UPDATE時に自動で現在日時が更新されます。

自動販売機でたとえるなら、ボタンを押した瞬間に自動で時刻が記録されるようなものです。

INSERT時の動き

新しい車を登録するときは、次のようにINSERTします。

INSERT INTO cars (name, price) VALUES ('プリウス', 2500000);




created_atとupdated_atは、MySQLが自動で入れてくれます。

car_idnamepricecreated_atupdated_atdeleted_at
1プリウス25000002026-06-01 10:30:002026-06-01 10:30:00NULL

登録直後は、created_atとupdated_atが同じ日時になります。

作成した瞬間が、最後に更新した瞬間でもあるからです。

UPDATE時の動き

価格を変更するとします。

UPDATE cars SET price = 2400000 WHERE car_id = 1 AND deleted_at IS NULL;




updated_atにON UPDATE CURRENT_TIMESTAMPを指定している場合、updated_atは自動で更新されます。

car_idnamepricecreated_atupdated_atdeleted_at
1プリウス24000002026-06-01 10:30:002026-06-05 15:20:00NULL

ここで大切なのは、WHERE deleted_at IS NULLを入れている点です。

削除済みのデータを間違って更新しないためです。

実務では、論理削除を使うテーブルに対して、更新や検索をするときにdeleted_at IS NULLを忘れないようにしましょう。

DELETEではなく論理削除する

論理削除では、DELETE文を使わずにUPDATE文を使います。

UPDATE cars SET deleted_at = CURRENT_TIMESTAMP WHERE car_id = 1 AND deleted_at IS NULL;




このSQLを実行すると、データはテーブルに残ります。

ただし、deleted_atに日時が入るため、削除済みとして扱われます。

car_idnamepricedeleted_at
1プリウス24000002026-06-10 09:00:00

この状態になったデータは、通常の一覧では表示しません。

SELECT car_id, name, price FROM cars WHERE deleted_at IS NULL;




削除済みデータも管理者だけ見たい場合は、条件を変えます。

SELECT car_id, name, price, deleted_at FROM cars WHERE deleted_at IS NOT NULL;




deleted_atがあると、「削除済み一覧」も作れます。

ゴミ箱機能のようなものですね。

deleted_atを使うメリット

deleted_atを使う大きなメリットは、データを復元できることです。

物理削除でDELETEしてしまうと、基本的にデータは消えます。

バックアップがなければ戻すのは難しいです。

一方で、論理削除ならdeleted_atをNULLに戻せば復元できます。

UPDATE cars SET deleted_at = NULL WHERE car_id = 1;




このSQLで、削除済み扱いだった車を復元できます。

スマートフォンの写真アプリにある「最近削除した項目」に似ています。

完全に消す前に、一定期間残しておけるので安心です。

メリット内容
復元できる間違って削除しても戻せる
削除履歴が残るいつ削除したか確認できる
監査に使える運用上の調査に役立つ
関連データとの整合性を保ちやすい急にデータが消えて参照できなくなる問題を減らせる

監査とは、あとから問題が起きたときに、何がいつ起きたのか確認することです。

システム運用では、「誰がいつ何をしたのか」を追えることがとても重要です。

deleted_atを使うデメリット

deleted_atは便利ですが、デメリットもあります。

一番多いミスは、検索時にdeleted_at IS NULLを忘れることです。

削除済みデータまで一覧に出てしまいます。

デメリット内容
検索条件が増える毎回deleted_at IS NULLを意識する必要がある
データ量が増える削除済みデータもテーブルに残る
一意制約が難しくなる場合がある削除済みデータと同じ値を再登録したいときに工夫が必要
本当に消したいデータには向かない場合がある個人情報などは保存し続けることが問題になる場合がある

たとえば、メールアドレスにUNIQUE制約を付けているusersテーブルを考えてみます。

論理削除されたユーザーのメールアドレスが残っていると、同じメールアドレスで再登録できない場合があります。

このような場合は、設計段階でルールを決めておく必要があります。

論理削除は便利ですが、何でもかんでも使えばよいわけではありません。

created_at・updated_at・deleted_atのよくある使い方

実務では、次のような使い方をよくします。

やりたいこと使うカラムSQL例
新しい順に表示するcreated_atORDER BY created_at DESC
最近更新されたデータを探すupdated_atORDER BY updated_at DESC
削除されていないデータだけ表示するdeleted_atWHERE deleted_at IS NULL
削除済みデータを探すdeleted_atWHERE deleted_at IS NOT NULL
今月作られたデータを集計するcreated_atWHERE created_at >= '2026-06-01'

たとえば、車を新しい順に表示するSQLです。

SELECT car_id, name, price, created_at FROM cars WHERE deleted_at IS NULL ORDER BY created_at DESC;




最近更新された車を表示するSQLです。

SELECT car_id, name, price, updated_at FROM cars WHERE deleted_at IS NULL ORDER BY updated_at DESC;




削除済みの車だけを確認するSQLです。

SELECT car_id, name, price, deleted_at FROM cars WHERE deleted_at IS NOT NULL ORDER BY deleted_at DESC;




JavaのDAOで使う場合

JavaでDAOを作る場合も、deleted_at IS NULLを忘れないことが大切です。

たとえば、削除されていない車だけを取得するDAOのSQLは次のようになります。

SELECT car_id, name, price, created_at, updated_at, deleted_at FROM cars WHERE deleted_at IS NULL ORDER BY created_at DESC;




論理削除する場合は、DELETEではなくUPDATEを使います。

UPDATE cars SET deleted_at = CURRENT_TIMESTAMP WHERE car_id = ? AND deleted_at IS NULL;




?には、PreparedStatementでcar_idを入れます。

PreparedStatementとは、SQLに安全に値を渡すための仕組みです。

値を文字列で直接つなげるより安全です。

新人エンジニアのうちは、次の考え方を覚えてください。

処理SQL
登録INSERT
更新UPDATEとupdated_at
論理削除UPDATEでdeleted_atに現在日時を入れる
検索WHERE deleted_at IS NULLを入れる

Javaで扱うDTOの例

created_at、updated_at、deleted_atをJavaで扱うなら、DTOやEntityに日時用のフィールドを用意します。

package com.example.demo;

import java.time.LocalDateTime;

public class CarDto {
	private Long carId;
	private String name;
	private Integer price;
	private LocalDateTime createdAt;
	private LocalDateTime updatedAt;
	private LocalDateTime deletedAt;

	public Long getCarId() {
		return carId;
	}

	public void setCarId(Long carId) {
		this.carId = carId;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getPrice() {
		return price;
	}

	public void setPrice(Integer price) {
		this.price = price;
	}

	public LocalDateTime getCreatedAt() {
		return createdAt;
	}

	public void setCreatedAt(LocalDateTime createdAt) {
		this.createdAt = createdAt;
	}

	public LocalDateTime getUpdatedAt() {
		return updatedAt;
	}

	public void setUpdatedAt(LocalDateTime updatedAt) {
		this.updatedAt = updatedAt;
	}

	public LocalDateTime getDeletedAt() {
		return deletedAt;
	}

	public void setDeletedAt(LocalDateTime deletedAt) {
		this.deletedAt = deletedAt;
	}
}




Javaでは、日時をLocalDateTimeで扱うことが多いです。

LocalDateTimeとは、日付と時刻を表すJavaのクラスです。

たとえば、2026年6月1日 10時30分のような情報を扱えます。

ResultSetから日時を取り出す例

JDBCでResultSetからDATETIMEを取り出す場合は、Timestampを経由してLocalDateTimeに変換することがあります。

package com.example.demo;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;

public class Snippet {

    private static final String DB_URI =
            "jdbc:mysql://localhost:3306/sip_a?"
            + "characterEncoding=utf8&useSSL=false"
            + "&serverTimezone=GMT%2B9"
            + "&rewriteBatchedStatements=true";
    private static final String DB_USER = "newuser";
    private static final String DB_PASS = "0";

    // DB接続を取得
    private Connection getConnection() throws SQLException {
        return DriverManager.getConnection(DB_URI, DB_USER, DB_PASS);
    }

    // 車の全件リストを取得
    public List<CarDto> getCarList() {

        List<CarDto> carList = new ArrayList<>();
        String sql = "SELECT * FROM cars";

        try (Connection con = getConnection();
                PreparedStatement ps = con.prepareStatement(sql);
                ResultSet rs = ps.executeQuery()) {

            while (rs.next()) {
                CarDto carDto = new CarDto();
                carDto.setCarId(rs.getLong("car_id"));
                carDto.setName(rs.getString("name"));
                carDto.setPrice(rs.getInt("price"));

                // ★ 元のスニペット:Timestampをnullチェックしてからセット
                Timestamp createdAt = rs.getTimestamp("created_at");
                if (createdAt != null) {
                    carDto.setCreatedAt(createdAt.toLocalDateTime());
                }

                Timestamp updatedAt = rs.getTimestamp("updated_at");
                if (updatedAt != null) {
                    carDto.setUpdatedAt(updatedAt.toLocalDateTime());
                }

                Timestamp deletedAt = rs.getTimestamp("deleted_at");
                if (deletedAt != null) {
                    carDto.setDeletedAt(deletedAt.toLocalDateTime());
                }

                carList.add(carDto);
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }

        return carList;
    }

    public static void main(String[] args) {
        Snippet snippet = new Snippet();
        List<CarDto> carList = snippet.getCarList();

        if (carList.isEmpty()) {
            System.out.println("データが見つかりませんでした");
        } else {
            carList.forEach(System.out::println);
        }
    }
}




deleted_atはNULLになることが多いため、NULLチェックが重要です。

NULLとは、値が存在しないことを表します。

deleted_atがNULLなら、削除されていないという意味です。

deleted_atを使うときの注意点

論理削除を使うときは、検索、更新、集計でdeleted_at IS NULLを意識してください。

特に一覧画面や検索機能では忘れやすいです。

悪い例です。

SELECT car_id, name, price FROM cars;




このSQLだと、削除済みの車も表示されます。

良い例です。

SELECT car_id, name, price FROM cars WHERE deleted_at IS NULL;




削除済みデータを除外できます。

たとえるなら、図書館の本棚に「廃棄予定の本」を並べないようにするイメージです。

倉庫には残っていても、利用者の目に見える棚には出しません。

created_at・updated_at・deleted_atにインデックスは必要か

インデックスとは、データ検索を速くするための仕組みです。

本の索引に似ています。

索引があれば、目的のページをすばやく探せますよね。

データベースでも同じです。

検索条件や並び替えによく使うカラムには、インデックスを付けることがあります。

たとえば、deleted_atでよく検索するなら、インデックスを検討します。

CREATE INDEX idx_cars_deleted_at ON cars (deleted_at);




created_atで新しい順に並べることが多いなら、created_atのインデックスも検討できます。

CREATE INDEX idx_cars_created_at ON cars (created_at);




ただし、インデックスは付ければ付けるほど良いわけではありません。

検索は速くなりやすいですが、登録や更新は少し重くなる場合があります。

学校のプリントに索引を作る作業を想像してください。

探すときは便利ですが、プリントを追加するたびに索引も直す必要がありますよね。

インデックスも同じです。

日時カラムの命名ルール

created_at、updated_at、deleted_atのように、スネークケースで書くことが多いです。

スネークケースとは、単語をアンダースコアでつなぐ書き方です。

created_at updated_at deleted_at




Javaでは、キャメルケースにすることが多いです。

createdAt updatedAt deletedAt




キャメルケースとは、2語目以降の先頭を大文字にする書き方です。

データベースではcreated_at、JavaではcreatedAtと覚えておくとよいです。

場所書き方
データベーススネークケースcreated_at
JavaキャメルケースcreatedAt

よくある設計ミス

新人エンジニアがやりがちなミスも確認しておきましょう。

ミス問題点
deleted_at IS NULLを忘れる削除済みデータが表示される
created_atを更新してしまう作成日時がわからなくなる
updated_atが更新されない最後に変更された日時が追えない
deleted_atをNOT NULLにする未削除の状態を表しにくくなる
物理削除と論理削除を混ぜるデータの扱いがわかりにくくなる

特に、created_atを更新してしまうミスには注意してください。

created_atは作成日時です。

一度決まったら基本的に変えません。

誕生日を毎年変えないのと同じです。

created_at・updated_at・deleted_atを使うべきテーブル

すべてのテーブルに必ず必要とは限りませんが、業務データには入れておくことが多いです。

テーブル例入れる理由
users登録日時や退会状態を管理したい
cars商品情報の更新や削除状態を管理したい
orders注文日時や変更履歴が重要になる
posts投稿日時、編集日時、削除状態を管理したい
commentsコメントの作成、編集、削除を管理したい

一方で、完全に一時的なデータや、ログ専用のデータでは設計が変わる場合もあります。

ただし、新人エンジニアのうちは、業務テーブルにはcreated_at、updated_at、deleted_atを入れることが多いと覚えておくとよいです。

まとめ

created_at、updated_at、deleted_atは、データの状態を管理するための重要な日時カラムです。

カラム意味主な使い方
created_at作成日時新しい順の表示、登録数の集計
updated_at更新日時最近変更されたデータの確認
deleted_at削除日時論理削除、復元、削除済み一覧

一言でまとめるなら、こうです。

created_atはデータの誕生日、updated_atは最後に直した日、deleted_atは削除扱いになった日です。

この3つを正しく使うと、データの管理がかなり楽になります。

特にdeleted_atを使う場合は、検索時にdeleted_at IS NULLを忘れないでください!

今後は、論理削除と物理削除の違い、インデックス、監査ログ、タイムゾーン、JDBCでのLocalDateTimeの扱い、Spring BootのJPA監査機能を順番に学ぶと、実務でのデータ設計がかなり理解しやすくなります。

まずは自分でcarsテーブルを作り、INSERT、UPDATE、論理削除、復元のSQLを実際に動かしてみてください。日時カラムの動きを手で確認すると、データベース設計の感覚が一気につかめます!

セイ・コンサルティング・グループでは新人エンジニア研修のアシスタント講師を募集しています。

投稿者プロフィール

山崎講師
山崎講師代表取締役
セイ・コンサルティング・グループ株式会社代表取締役。
岐阜県出身。
2000年創業、2004年会社設立。
IT企業向け人材育成研修歴業界歴20年以上。
すべての無駄を省いた費用対効果の高い「筋肉質」な研修を提供します!
この記事に間違い等ありましたらぜひお知らせください。

学生時代は趣味と実益を兼ねてリゾートバイトにいそしむ。長野県白馬村に始まり、志賀高原でのスキーインストラクター、沖縄石垣島、北海道トマム。高じてオーストラリアのゴールドコーストでツアーガイドなど。現在は野菜作りにはまっている。