SQLで誕生日から現在の学年を取得する


前提条件として、以下の二つのテーブルを持っています。

```SQL
CREATE TABLE `mst_grade` (
  `gradeId` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'プライマリーキー',
  `gradeName` varchar(16) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '学年名',
  `gradeShortName` varchar(16) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '学年略名'
  PRIMARY KEY (`gradeId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='[Master]学年マスタ';

INSERT INTO `mst_grade` (`gradeId`, `gradeName`, `gradeShortName`)
VALUES
	(1,'小学１年','小１'),
	(2,'小学２年','小２'),
	(3,'小学３年','小３'),
	(4,'小学４年','小４'),
	(5,'小学５年','小５'),
	(6,'小学６年','小６'),
	(7,'中学１年','中１'),
	(8,'中学２年','中２'),
	(9,'中学３年','中３');
	
CREATE `tbl_student` (
	`userid``userId` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'プライマリーキー',
	`userName` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'ユーザー名',
	`birthday` date DEFAULT NULL,
  PRIMARY KEY (`userId`) COMMENT 'プライマリーキー'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='[User]ユーザーテーブル';

INSERT INTO `tbl_user` (`userId`, `userName`, `birthday`)
VALUES
	(1, '生徒1', '2000-04-02'),
	(2, '生徒2', '2001-03-31'),
	(3, '生徒3', '2001-04-01');
```
サンプルデータの例では、いずれも同じ学年であるはずです。ポイントは、早生まれの生徒の学年をうまく算出することが肝となります。
考え方の道筋としては、年度末の日には全ての生徒は同じ年齢となっていることです。つまり今年度の末で満7歳の生徒（児童）は小学校１年生であるということです。

では、誕生日から年齢を算出することができれば、応用できそうですので、ググると…

[MySQL で誕生日のカラムから年齢を計算する方法](http://d.hatena.ne.jp/r_ikeda/20111103/birthday)

```SQL
-- birthday は DATE 型のカラムとする
SELECT
	 (YEAR(CURDATE()) - YEAR(birthday))
    - (RIGHT(CURDATE(), 5) < RIGHT(birthday, 5)) AS age
FROM
	hoge
```

これは、CURDATE() を使っているので、「現在」の年齢を算出しますので、基準日を今年度末日に変更します。
通常年度末は３月３１日ですが、４月１日は早生まれの扱いなので、基準日４月１日時点での年齢を算出します。

と、記憶を頼りに判断したわけですが、都市伝説だといけないので調べてみました。

[小・中学校への就学について](http://www.mext.go.jp/a_menu/shotou/shugaku/detail/1309966.htm)

本日執筆時点（２０１５年３月８日）では、そのまま２０１５年４月１日が基準日となりますが、２０１５年４月２日になると、２０１６年度になるので、４月１日より大きい日付のときは翌年４月１日、小さいときは当年の４月１日は基準日となります。
そこで、これを表現したSQLが以下のようになります。

```SQL
SELECT 
(
	IF(
		'04-01' > RIGHT(curdate(), 5), YEAR(curdate()), YEAR(curdate()) + 1
	) - YEAR(`birthday`)) 
	- ('04-01' < RIGHT(`birthday`, 5)
) AS age 
, `birthday`
FROM `tbl_user`
```

あとは、実際の年齢から6を減算すると、学年マスタのプラーマリーキーの値に一致します。

```SQL
SELECT 
(
	IF(
		'04-01' > RIGHT(curdate(), 5), YEAR(curdate()), YEAR(curdate()) + 1
	) - YEAR(`birthday`)) 
	- ('04-01' < RIGHT(`birthday`, 5)
) - 6 AS gradeId 
, `birthday`
FROM `tbl_user`
```

サブクエリを使って表現すると、

```SQL
SELECT 
(
	SELECT `gradeName` FROM `mst_grade` WHERE `gradeId` = (
		(IF('04-01' > RIGHT(curdate(), 5), YEAR(curdate()), YEAR(curdate()) + 1) - YEAR(`birthday`)) 
		- ('04-01' < RIGHT(`birthday`, 5)) 
		- 6
	)
)
AS `gradeName`
, `birthday`
FROM `tbl_user`
```

となりました。
うーん、なんか美しくないですね。件数が多いと処理重そうだし…

と、無理やりSQLでやったらどうなるかと、試行してみましたが、実際には使えるのか？
使えないでしょうね…。なぜっていうと、実際には特殊学級や発育不全などの理由で、必ずしも年齢と学年が一致するわけではありません。実際の運用では原則どおりにはいかないものだと思って、リンクテーブルで結合する方法が良いのではないかと思います。

余談ですが、はじめはJOINを使って、表現しようと以下のようなクエリを考えたのですが…

```SQL
SELECT 
	`birthday`
	, `gradeId`
	, `gradeName`
FROM 
	`tbl_user`
LEFT JOIN 
	`mst_grade` 
ON `mst_grade`.`gradeId` = (
	IF('04-01' > RIGHT(curdate(), 5), YEAR(curdate()), YEAR(curdate()) + 1) - YEAR(`tbl_user`.`birthday`)) 
	- ('04-01' < RIGHT(`tbl_user`.`birthday`, 5) 
	- 6
)
```

MySQLだとダメみたいですね…
シンタックスエラーになるわけでもないのですが、gradeId gradeName が NULL で返ってきてしまいます。なんでだろう。
わかる人、教えて下さい！