SQLで誕生日(生年月日)から年齢を計算する処理は、会員管理・顧客分析・年齢制限チェックなど、あらゆる業務システムで必要になります。しかし、RDBMS(データベース)ごとに使える関数が異なるため、正しい方法を知っておくことが重要です。
この記事では、MySQL・PostgreSQL・SQL Server・Oracle の4大RDBMSそれぞれでの年齢計算方法を基本から解説し、うるう年の落とし穴、年代別グループ集計、年齢フィルタリングなど実務で役立つパターンまで網羅します。
この記事で学べること
- 年齢計算の基本的な考え方と注意点
- MySQL:TIMESTAMPDIFF を使った年齢計算
- PostgreSQL:AGE() 関数と EXTRACT の活用
- SQL Server:DATEDIFF + 誕生日補正の正確な計算
- Oracle:MONTHS_BETWEEN を使った年齢計算
- うるう年(2/29生まれ)の扱いと境界値の落とし穴
- 年齢でグループ分け(年代別集計)する方法
- WHERE句で年齢フィルタリングする方法
- RDBMS間の比較と使い分け
- よくあるエラーと対処法
サンプルデータ
この記事では、以下の users テーブルを使って解説します。さまざまな年齢・誕生日パターンを含めています。
| id |
name |
birthday |
備考 |
| 1 |
田中太郎 |
1990-05-15 |
一般的な日付 |
| 2 |
鈴木花子 |
2000-02-29 |
うるう年生まれ |
| 3 |
佐藤一郎 |
1985-12-31 |
年末生まれ |
| 4 |
高橋美咲 |
2005-01-01 |
年始生まれ |
| 5 |
山田健太 |
1978-03-01 |
3月1日生まれ |
| 6 |
伊藤恵 |
2010-07-20 |
未成年 |
サンプルデータの作成SQL(クリックで展開)
CREATE TABLE + INSERT
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50),
birthday DATE
);
INSERT INTO users (id, name, birthday) VALUES
(1, '田中太郎', '1990-05-15'),
(2, '鈴木花子', '2000-02-29'),
(3, '佐藤一郎', '1985-12-31'),
(4, '高橋美咲', '2005-01-01'),
(5, '山田健太', '1978-03-01'),
(6, '伊藤恵', '2010-07-20');
年齢計算の基本的な考え方
プログラムで年齢を計算する際、一見「現在の年 – 生まれた年」で求められそうに思えますが、誕生日がまだ来ていない場合は1を引く必要があります。
年齢計算のロジック(疑似コード)
-- 年齢 = 現在の年 - 誕生年
-- ただし、今年の誕生日がまだ来ていなければ -1
年齢 = (今日の年) - (誕生年)
IF 今日の月日 < 誕生月日 THEN
年齢 = 年齢 - 1
END IF
各RDBMSには、この計算を正確に行う組み込み関数が用意されています。以下のセクションで、データベースごとの方法を見ていきましょう。
注意:「年 – 年」だけの単純計算は誕生日前後で1歳のズレが生じます。必ず月日まで考慮した計算方法を使いましょう。
MySQL:TIMESTAMPDIFF で年齢を計算する
MySQLでは TIMESTAMPDIFF 関数を使うことで、誕生日を月日レベルで正確に考慮した年齢計算ができます。
基本構文
MySQL
SELECT
name,
birthday,
TIMESTAMPDIFF(YEAR, birthday, CURDATE()) AS age
FROM
users;
実行結果(2026-03-05 時点)
name | birthday | age
-----------+------------+----
田中太郎 | 1990-05-15 | 35
鈴木花子 | 2000-02-29 | 26
佐藤一郎 | 1985-12-31 | 40
高橋美咲 | 2005-01-01 | 21
山田健太 | 1978-03-01 | 48
伊藤恵 | 2010-07-20 | 15
ポイント:TIMESTAMPDIFF(YEAR, 開始日, 終了日) は内部で月日を比較し、まだ誕生日が来ていなければ自動的に1を引いてくれるため、正確な満年齢が得られます。
TIMESTAMPDIFF の引数
| 引数 |
説明 |
YEAR |
年単位の差(年齢計算に使用) |
| 第2引数 |
開始日(= 誕生日) |
| 第3引数 |
終了日(= CURDATE() で今日の日付) |
特定日時点の年齢を求める
CURDATE() の代わりに任意の日付を指定すれば、過去や未来の特定日時点での年齢も算出できます。
MySQL – 特定日時点の年齢
-- 2025年4月1日時点の年齢を算出
SELECT
name,
birthday,
TIMESTAMPDIFF(YEAR, birthday, '2025-04-01') AS age_at_date
FROM
users;
PostgreSQL:AGE() 関数で年齢を計算する
PostgreSQLには AGE() という年齢計算の専用関数が用意されており、最もシンプルに年齢を取得できます。
AGE() 関数の基本
PostgreSQL – AGE() 関数
-- AGE() は interval 型で「○ years ○ mons ○ days」を返す
SELECT
name,
birthday,
AGE(CURRENT_DATE, birthday) AS age_interval
FROM
users;
実行結果
name | birthday | age_interval
-----------+------------+-------------------
田中太郎 | 1990-05-15 | 35 years 9 mons 18 days
鈴木花子 | 2000-02-29 | 26 years 4 days
佐藤一郎 | 1985-12-31 | 40 years 2 mons 2 days
...
年齢(整数)だけを取得する
AGE() の戻り値は interval 型なので、整数の年齢を取得するには EXTRACT または DATE_PART を組み合わせます。
PostgreSQL – EXTRACT + AGE()
-- 方法1: EXTRACT を使う
SELECT
name,
birthday,
EXTRACT(YEAR FROM AGE(CURRENT_DATE, birthday)) AS age
FROM
users;
-- 方法2: DATE_PART を使う(結果は同じ)
SELECT
name,
birthday,
DATE_PART('year', AGE(CURRENT_DATE, birthday)) AS age
FROM
users;
実行結果
name | birthday | age
-----------+------------+----
田中太郎 | 1990-05-15 | 35
鈴木花子 | 2000-02-29 | 26
佐藤一郎 | 1985-12-31 | 40
高橋美咲 | 2005-01-01 | 21
山田健太 | 1978-03-01 | 48
伊藤恵 | 2010-07-20 | 15
ポイント:AGE() に引数を1つだけ渡すと、自動的に CURRENT_DATE との差を計算します。AGE(birthday) と AGE(CURRENT_DATE, birthday) は同じ結果です。
SQL Server:DATEDIFF + 誕生日補正で年齢を計算する
SQL Serverの DATEDIFF 関数は、単純に年の差を返すだけで月日を考慮しません。そのため、誕生日がまだ来ていない場合の補正が必要です。
DATEDIFF だけでは不正確
SQL Server – 不正確な例
-- NG: DATEDIFF だけでは誕生日前でも +1 されてしまう
SELECT
name,
birthday,
DATEDIFF(YEAR, birthday, GETDATE()) AS wrong_age
FROM
users;
注意:DATEDIFF(YEAR, '2000-12-31', '2001-01-01') は 1 を返します。たった1日の差でも年が変わると1年としてカウントされるため、年齢計算には使えません。
正確な年齢計算(誕生日補正付き)
SQL Server – 正確な年齢計算
SELECT
name,
birthday,
DATEDIFF(YEAR, birthday, GETDATE())
- CASE
WHEN FORMAT(GETDATE(), 'MMdd')
< FORMAT(birthday, 'MMdd')
THEN 1
ELSE 0
END AS age
FROM
users;
この方法では、FORMAT で月日を 'MMdd' 形式の文字列に変換し、今日の月日と誕生日の月日を比較します。今年の誕生日がまだ来ていなければ1を引くことで、正確な満年齢が得られます。
別の書き方(MONTH / DAY で比較)
SQL Server – MONTH/DAY を使う方法
SELECT
name,
birthday,
DATEDIFF(YEAR, birthday, GETDATE())
- CASE
WHEN MONTH(GETDATE()) < MONTH(birthday)
OR (MONTH(GETDATE()) = MONTH(birthday)
AND DAY(GETDATE()) < DAY(birthday))
THEN 1
ELSE 0
END AS age
FROM
users;
Oracle:MONTHS_BETWEEN で年齢を計算する
Oracleでは MONTHS_BETWEEN 関数で2つの日付の月数差を求め、12で割って切り捨てることで年齢を算出します。
基本構文
Oracle
SELECT
name,
birthday,
TRUNC(MONTHS_BETWEEN(SYSDATE, birthday) / 12) AS age
FROM
users;
実行結果
NAME | BIRTHDAY | AGE
-----------+------------+----
田中太郎 | 1990-05-15 | 35
鈴木花子 | 2000-02-29 | 26
佐藤一郎 | 1985-12-31 | 40
高橋美咲 | 2005-01-01 | 21
山田健太 | 1978-03-01 | 48
伊藤恵 | 2010-07-20 | 15
ポイント:MONTHS_BETWEEN は日の部分も考慮して正確な月数差を返します。TRUNC(切り捨て)で小数部を除去することで、満年齢が得られます。FLOOR も同じ動作をしますが、TRUNC がOracle の慣用表現です。
EXTRACT を使った別の方法
Oracle – EXTRACT を使う方法
SELECT
name,
birthday,
EXTRACT(YEAR FROM SYSDATE) - EXTRACT(YEAR FROM birthday)
- CASE
WHEN TO_CHAR(SYSDATE, 'MMDD')
< TO_CHAR(birthday, 'MMDD')
THEN 1
ELSE 0
END AS age
FROM
users;
RDBMS別 年齢計算 比較一覧
4つのRDBMSでの年齢計算方法を比較表にまとめます。
| RDBMS |
推奨方法 |
月日考慮 |
補足 |
| MySQL |
TIMESTAMPDIFF(YEAR, birthday, CURDATE()) |
自動 |
最もシンプル。補正不要 |
| PostgreSQL |
EXTRACT(YEAR FROM AGE(birthday)) |
自動 |
AGE()が専用関数で便利 |
| SQL Server |
DATEDIFF(YEAR,...) - CASE補正 |
手動 |
DATEDIFF単独はNG。補正必須 |
| Oracle |
TRUNC(MONTHS_BETWEEN(...)/12) |
自動 |
MONTHS_BETWEENが日も考慮 |
年齢計算の落とし穴:うるう年(2月29日生まれ)
年齢計算で最も注意が必要なのがうるう年の2月29日生まれです。うるう年でない年には2月29日が存在しないため、各RDBMSでの扱いを理解しておく必要があります。
2000年2月29日生まれの場合
| 判定日 |
MySQL |
PostgreSQL |
SQL Server |
Oracle |
| 2025-02-28 |
24 |
24 |
24(補正あり) |
24 |
| 2025-03-01 |
25 |
25 |
25(補正あり) |
25 |
| 2028-02-28 |
27 |
27 |
27(補正あり) |
27 |
| 2028-02-29 |
28 |
28 |
28(補正あり) |
28 |
注意:日本の法律(年齢計算ニ関スル法律)では、2月29日生まれの人はうるう年でない年は2月28日の終了時(3月1日の0時)に加齢します。MySQL の TIMESTAMPDIFF、PostgreSQL の AGE()、Oracle の MONTHS_BETWEEN はこの挙動と一致しますが、SQL Server の DATEDIFF は手動補正が必要なので特に注意してください。
うるう年を考慮した検証SQL(MySQL)
MySQL – うるう年境界値テスト
-- 2000-02-29 生まれの人の年齢を各日付で確認
SELECT
test_date,
TIMESTAMPDIFF(YEAR, '2000-02-29', test_date) AS age
FROM (
SELECT '2025-02-28' AS test_date
UNION ALL SELECT '2025-03-01'
UNION ALL SELECT '2028-02-28'
UNION ALL SELECT '2028-02-29'
) dates;
年齢でグループ分け(年代別集計)
マーケティングや統計分析では、年齢を10歳刻みの年代(10代、20代、30代…)に分類して集計するのが定番です。
MySQL での年代別集計
MySQL – 年代別の人数集計
SELECT
CONCAT(
FLOOR(TIMESTAMPDIFF(YEAR, birthday, CURDATE()) / 10) * 10,
'代'
) AS age_group,
COUNT(*) AS count
FROM
users
GROUP BY
FLOOR(TIMESTAMPDIFF(YEAR, birthday, CURDATE()) / 10) * 10
ORDER BY
age_group;
実行結果
age_group | count
----------+------
10代 | 1
20代 | 2
30代 | 1
40代 | 2
CASE式で自由にグループを定義する
10歳刻みではなく、ビジネス要件に合わせたカスタムグループを作ることもできます。
MySQL – カスタム年齢グループ
SELECT
CASE
WHEN TIMESTAMPDIFF(YEAR, birthday, CURDATE()) < 18
THEN '未成年'
WHEN TIMESTAMPDIFF(YEAR, birthday, CURDATE()) BETWEEN 18 AND 39
THEN '若年層'
WHEN TIMESTAMPDIFF(YEAR, birthday, CURDATE()) BETWEEN 40 AND 64
THEN '中年層'
ELSE 'シニア'
END AS age_category,
COUNT(*) AS count
FROM
users
GROUP BY
age_category
ORDER BY
MIN(TIMESTAMPDIFF(YEAR, birthday, CURDATE()));
実行結果
age_category | count
-------------+------
未成年 | 1
若年層 | 3
中年層 | 2
PostgreSQL での年代別集計
PostgreSQL – 年代別集計
SELECT
(EXTRACT(YEAR FROM AGE(birthday))::int / 10) * 10
|| '代' AS age_group,
COUNT(*) AS count
FROM
users
GROUP BY
age_group
ORDER BY
age_group;
WHERE句で年齢フィルタリング
特定の年齢条件でデータを絞り込むケースも多いです。成人チェック、シニア割引対象、ターゲット層の抽出など、実務でよく使うパターンを紹介します。
成人(18歳以上)のユーザーを取得(MySQL)
MySQL – 成人のみ抽出
SELECT
name,
birthday,
TIMESTAMPDIFF(YEAR, birthday, CURDATE()) AS age
FROM
users
WHERE
TIMESTAMPDIFF(YEAR, birthday, CURDATE()) >= 18;
年齢範囲で絞り込み(BETWEEN)
MySQL – 20代のユーザー
-- 20歳以上30歳未満(20代)のユーザーを取得
SELECT
name,
birthday,
TIMESTAMPDIFF(YEAR, birthday, CURDATE()) AS age
FROM
users
WHERE
TIMESTAMPDIFF(YEAR, birthday, CURDATE()) BETWEEN 20 AND 29;
実行結果
name | birthday | age
-----------+------------+----
鈴木花子 | 2000-02-29 | 26
高橋美咲 | 2005-01-01 | 21
パフォーマンスを考慮した書き方
WHERE句で関数を使うとインデックスが効かない場合があります。大量データでパフォーマンスが問題になる場合は、誕生日の範囲に変換してインデックスを活用しましょう。
MySQL – インデックス活用パターン
-- 20歳以上30歳未満を「誕生日の範囲」に変換
SELECT
name,
birthday,
TIMESTAMPDIFF(YEAR, birthday, CURDATE()) AS age
FROM
users
WHERE
birthday BETWEEN
DATE_SUB(CURDATE(), INTERVAL 30 YEAR)
AND
DATE_SUB(CURDATE(), INTERVAL 20 YEAR);
ポイント:birthday 列に直接比較することでINDEXが効くようになり、大量レコード(数十万件以上)でもパフォーマンスが維持されます。WHERE句の関数適用は対象列が左辺にある場合にインデックスを無効化するため、可能な限りこの「逆算パターン」を使いましょう。
実務で使える年齢計算パターン
パターン1: 今月誕生日のユーザーを取得
MySQL – 今月の誕生日ユーザー
SELECT
name,
birthday,
TIMESTAMPDIFF(YEAR, birthday, CURDATE()) + 1 AS next_age
FROM
users
WHERE
MONTH(birthday) = MONTH(CURDATE());
パターン2: 年齢付きのユーザー一覧VIEW
年齢を毎回計算するのは手間なので、VIEWを作成しておくと便利です。
MySQL – VIEW作成
CREATE VIEW v_users_with_age AS
SELECT
id,
name,
birthday,
TIMESTAMPDIFF(YEAR, birthday, CURDATE()) AS age,
CONCAT(
FLOOR(TIMESTAMPDIFF(YEAR, birthday, CURDATE()) / 10) * 10,
'代'
) AS age_group
FROM
users;
-- VIEW を使って簡単にクエリ
SELECT * FROM v_users_with_age WHERE age >= 20;
パターン3: 年齢の統計情報を取得
MySQL – 年齢の統計情報
SELECT
COUNT(*) AS total_users,
AVG(TIMESTAMPDIFF(YEAR, birthday, CURDATE())) AS avg_age,
MIN(TIMESTAMPDIFF(YEAR, birthday, CURDATE())) AS min_age,
MAX(TIMESTAMPDIFF(YEAR, birthday, CURDATE())) AS max_age
FROM
users;
実行結果
total_users | avg_age | min_age | max_age
------------+---------+---------+--------
6 | 30.83 | 15 | 48
パターン4: 年齢計算を使ったJOIN
MySQL – 注文データと年齢を結合
-- 年代別の売上集計
SELECT
CONCAT(
FLOOR(TIMESTAMPDIFF(YEAR, u.birthday, CURDATE()) / 10) * 10,
'代'
) AS age_group,
COUNT(*) AS order_count,
SUM(o.amount) AS total_amount
FROM
users u
INNER JOIN orders o ON u.id = o.user_id
GROUP BY
age_group
ORDER BY
age_group;
よくあるエラーと対処法
| 問題 |
原因 |
対処法 |
| 年齢が1歳多い |
SQL ServerのDATEDIFFが年の差だけを返す |
CASE式で誕生日前後の補正を追加 |
| NULLが返される |
birthday列がNULLのレコードがある |
COALESCEでデフォルト値を設定、またはWHERE句でNULLを除外 |
| 負の値が返される |
引数の順序が逆(終了日と開始日) |
TIMESTAMPDIFF(YEAR, birthday, CURDATE()) の順序を確認 |
| 文字列型エラー |
birthday列がVARCHAR型で格納されている |
CAST(birthday AS DATE)で型変換してから計算 |
| パフォーマンス低下 |
WHERE句で関数を使いインデックスが効かない |
DATE_SUBで誕生日の範囲に変換(逆算パターン) |
NULL対策の実装例
MySQL – NULL安全な年齢計算
-- birthday が NULL の場合も安全に処理
SELECT
name,
birthday,
CASE
WHEN birthday IS NULL THEN '未登録'
ELSE CAST(
TIMESTAMPDIFF(YEAR, birthday, CURDATE())
AS CHAR
)
END AS age_display
FROM
users;
文字列型の誕生日を変換する
MySQL – VARCHAR型の誕生日を変換
-- birthday が '19900515' のような文字列の場合
SELECT
name,
TIMESTAMPDIFF(
YEAR,
STR_TO_DATE(birthday, '%Y%m%d'),
CURDATE()
) AS age
FROM
users;
各RDBMSの日付関連関数 まとめ
年齢計算で使う日付関数はRDBMSごとに異なります。よく使う関数の対応表をまとめます。
| 用途 |
MySQL |
PostgreSQL |
SQL Server |
Oracle |
| 現在日付 |
CURDATE() |
CURRENT_DATE |
GETDATE() |
SYSDATE |
| 年の差 |
TIMESTAMPDIFF |
AGE() + EXTRACT |
DATEDIFF |
MONTHS_BETWEEN |
| 月の取得 |
MONTH() |
EXTRACT(MONTH FROM ...) |
MONTH() |
EXTRACT(MONTH FROM ...) |
| 日の取得 |
DAY() |
EXTRACT(DAY FROM ...) |
DAY() |
EXTRACT(DAY FROM ...) |
| 日付の減算 |
DATE_SUB() |
date - INTERVAL |
DATEADD() |
ADD_MONTHS() |
まとめ
SQLで誕生日から年齢を計算する方法を、主要4つのRDBMS別に解説しました。最後にポイントを整理します。
この記事のまとめ
- MySQL:
TIMESTAMPDIFF(YEAR, birthday, CURDATE()) が最もシンプルで正確
- PostgreSQL:
EXTRACT(YEAR FROM AGE(birthday)) で専用関数を活用
- SQL Server:
DATEDIFF だけではNG。CASE式で誕生日補正が必須
- Oracle:
TRUNC(MONTHS_BETWEEN(SYSDATE, birthday) / 12) で正確に算出
- うるう年(2/29)生まれの扱いに注意。各RDBMSの挙動を検証しておくと安心
- 年代別集計は
FLOOR(年齢 / 10) * 10 で10歳刻みに変換
- パフォーマンスを考慮するなら、WHERE句では
DATE_SUB で誕生日範囲に変換
- NULL対策を忘れずに。
CASE や COALESCE で安全に処理
年齢計算は一見シンプルに見えますが、RDBMS間の違い、うるう年の境界、パフォーマンスなど、実務では多くの考慮点があります。この記事で紹介したパターンを活用して、正確で効率的な年齢計算を実装してください。