最近在Phpfreaks.com论坛上提出了一个问题,即有人使用MySQL数据库存储用户出生日期的最佳方式。一个人建议使用varchar,因为他们最熟悉字符串函数。我的回答是使用MySQL日期类型。
我提供了一个快速的“使用日期的优点”比较列表:
存储为日期
•MySQL日期需要3个字节的存储空间
•您可以在mysql内部执行日期算术(搜索范围内的日期)并使用mysql函数直接计算值查询
•它本质上只存储有效日期
•您可以通过多种方式对其进行格式化
存储为Varchar
•字符串最少需要8个字节,或者10个字符串需要分隔符
•不能执行任何形式的高效本机范围查询
•无法在SQL中轻松地重新格式化
•将允许完全无效的日期
我认为这是一个很好的证明方法是表明你可以让MySQL使用他们的生日计算查询中人的当前年龄。当然使用字符串,例如使用PHP,您通常会查询数据库以提取字符串,并为每一行,将其转换为PHP日期并在代码中进行一些计算,然后您到达同一个地方,但是我想展示SQL的能力 - 在这种情况下,MySQL可以特别是当你使用本机数据类型和一些功能时。
在MySQL查询中计算一个人的年龄
我之前写过关于CURDATE()的文章,它可以让你访问“当前服务器日期”作为比较计算的基础。
要计算一个人当前的年龄,简单的食谱是“今年”减去“出生年份”。
使用MySQL Date类型存储用户生日的优点是,您可以使用YEAR()这样的简单函数来为您提供所需日期的任何部分。
mysql > 选择 YEAR ( CURDATE ()) ;
+ ----------------- +
| 年( CURDATE ()) |
+ ----------------- +
| 2011 |
+ ----------------- +
1行中 集合 (0.00秒)
为了测试这个概念,我们将创建一个简单的用户表,其中包含密钥,名称和生日。
CREATE TABLE用户( id int AUTO_INCREMENT PRIMARY KEY , name varchar (20 ), birthdate date ) ;
mysql > describe user;
+ ----------- ------------- + + ------ + ------ + --------- + ---------------- +
| 领域 | 输入 | 空| 钥匙| 默认| 额外的 |
+ ----------- ------------- + + ------ + ------ + --------- + ---------------- +
| id | int (11 ) | 没有 | PRI | NULL | auto_increment |
| 名字 | varchar (20 ) |是的 | | NULL | |
| 生日| 日期 | 是的 | | NULL | |
+ ----------- ------------- + + ------ + ------ + --------- + ---------------- +
3行中 集合 (0.27秒)
现在我们将添加一些用户行:
INSERT INTO用户(姓名,生日) VALUES ('Fran' , '1967-10-31' ) ;
INSERT INTO用户(姓名,生日) VALUES ('Bill' , '1964-05-07' ) ;
INSERT INTO用户(姓名,生日) VALUES ('Jimmy' , '1965-04-27' ) ;
INSERT INTO用户(名称,生日) VALUES ('Stacy' , '2002-11-30' ) ;
INSERT INTO用户(姓名,生日) VALUES ('George' , '2007-10-25' ) ;
mysql > select * from user;
+ ---- + -------- + ------------ +
| id | 名字 | 生日 |
+ ---- + -------- + ------------ +
| 1 | 弗兰 | 1967 - 10 - 31 |
| 2 | 比尔 | 1964年 - 05 - 07年|
| 3 | 吉米 | 1965 -04- 27 |
| 4 | Stacy | 2002 - 11 -30 |
| 5 | 乔治| 2007 - 10 - 25 |
+ ---- + -------- + ------------ +
5行中 集合 (0.00秒)
我们关于年龄计算的第一个传递将涉及实施当前年份 - 出生年份:
随着本教程的老化,请注意这些查询的结果可能会根据当前日期而有所不同,如果您选择自己运行它们(我强烈推荐) )。这些结果与:
mysql > select CURDATE () ;
+ ------------ +
| CURDATE () |
+ ------------ +
| 2011 -08- 11 |
+ ------------ +
1行中 集合 (0.00秒)
SELECT *, YEAR ( CURDATE ()) - YEAR ( birthdate ) AS age FROM user;
mysql > SELECT *,YEAR ( CURDATE ()) - YEAR ( birthdate ) AS age FROM user;
+ ---- + -------- + ------------ + ------ +
| id | 名字 | 生日 | 年龄 |
+ ---- + -------- + ------------ + ------ +
| 1 | 弗兰 | 1967 - 10 - 31 | 44 |
| 2 | 比尔 | 1964年 - 05 - 07年| 47 |
| 3 | 吉米 | 1965 -04- 27 | 46 |
| 4 | Stacy | 2002 - 11 - 30 | 9 |
| 5 | 乔治| 2007 - 10 - 25 | 4 |
+ ---- + -------- + ------------ + ------ +
5行中 集合 (0.00秒)
调整即将到来的生日的人的年龄
现在,如果你看一下这些结果,你可能已经注意到了一个问题。例如,2007年10月25日出生的“乔治”还不到4岁,因为它只是8月11日,而他的生日还没有发生。
因此,乔治仍然是3岁。因此,在此计算中确定“今天”是否小于您的生日纪念日是很重要的。正如我之前演示的那样,mysql提供了一个有用的三元函数IF(),可以让你解决这些“if then else”场景。但首先我们需要确定用户的出生日期是否已经过去了?
我们能做的是让MySQL根据您的出生日(月和日)和当前年份制作日期,并将其与curdate()进行比较。如果curdate()是>我们制造的“
从年龄减去一年,以反映他们的生日在当年没有发生。
逐个组装查询
我们将使用STR_TO_DATE(在MySQL 4.1.1版中引入)来创建mysql计算的“生日”日期。STR_TO_DATE采用您选择的格式的字符串,并将其转换为mysql日期。如'%Y-%c-%e'参数所示,我们将使用带有前导零的'YEAR-MO-DA'作为月份和日期。破折号不是必需的,但我使用它们可以很容易地将我们制造的日期字符串与生日专栏进行比较。内部mysql正在使用数字,最终输出的格式是您可以使用像DATE_FORMAT这样的函数在查询中控制的。
SELECT *, STR_TO_DATE ( CONCAT ( YEAR ( CURDATE ()), ' - ' ,月(生日), ' - ' , DAY (出生日期)) ,'%Y-%C-%E' ) AS生日FROM用户;
mysql > SELECT *,STR_TO_DATE ( CONCAT ( YEAR ( CURDATE ()),' - ',MONTH (生日),' - ',DAY (生日)),'%Y-%c-%e' ) 作为来自用户的生日;
+ ---- + -------- + ------------ + ------------ +
| id | 名字 | 生日 | 生日 |
+ ---- + -------- + ------------ + ------------ +
| 1 |弗兰 | 1967 - 10 - 31 | 2011 - 10 - 31 |
| 2 | 比尔 | 1964年 - 05 - 07年| 2011年 -05-07 |
| 3 | 吉米 | 1965 -04- 27 | 2011 -04- 27 |
| 4 | Stacy | 2002 - 11 - 30 | 2011 - 11 - 30 |
| 5 | 乔治| 2007 - 10 - 25 | 2011 - 10 - 25 |
+ ---- + -------- + ------------ + ------------ +
5行中 集合 (0.00秒)
现在我们将利用IF()来进行比较,并在必要时减去一年。IF()需要3个参数:
•要检查的条件
•条件为真时
返回的值•条件为假时返回的值
只是对于可视化验证,让我们首先将三元函数添加到先前查询的末尾。一个将指示用户的生日没有发生,零将指示它已经发生。当然,这些也是
我们将减去年龄计算结果来修复它的值!
SELECT *, STR_TO_DATE ( CONCAT ( YEAR ( CURDATE ()), ' - ' , MONTH (出生日期), ' - ' , DAY (出生日期)) ,'%Y-%C-%E' ) AS生日,
IF ( STR_TO_DATE ( CONCAT ( YEAR ( CURDATE ()), ' - ' ,月(生日), ' - ' ,DAY (出生日期)) ,'%Y-%C-%E' ) > CURDATE (), 1 , 0 ) AS调整
FROM用户;
+ ---- + -------- + ------------ + ------------ + --------- --- +
| id | 名字 | 生日 | 生日 | 调整|
+ ---- + -------- + ------------ + ------------ + --------- --- +
| 1 | 弗兰 | 1967 - 10 - 31 | 2011 - 10 - 31 | 1 |
| 2 | 比尔 | 1964年 - 05 - 07年| 2011年 -05-07 | 0 |
| 3 | 吉米 | 1965 -04- 27 | 2011 -04- 27 | 0 |
| 4 | Stacy | 2002 - 11 - 30 | 2011 - 11 - 30 | 1 |
| 5 | 乔治| 2007 - 10 - 25 | 2011 - 10 - 25 | 1 |
+ ---- + -------- + ------------ + ------------ + --------- --- +
5行于 集 (0.00秒)
10月和11月生日的3个人应该正确调整年龄。我们将其添加到原始年龄计算中,现在我们的年龄是准确的:
SELECT *, YEAR ( CURDATE ()) -
YEAR (出生日期) -
IF ( STR_TO_DATE ( CONCAT ( YEAR ( CURDATE ()), ' - ' , MONTH (出生日期),' - ' , DAY (出生日期)) ,“%Y - %C-%E” ) > CURDATE (), 1 , 0 )
AS年龄来自用户;
+ ---- + -------- + ------------ + ------ +
| id | 名字 | 生日 | 年龄 |
+ ---- + -------- + ------------ + ------ +
| 1 | 弗兰 | 1967 - 10 - 31 | 43 |
| 2 | 比尔 | 1964年 - 05 - 07年| 47 |
| 3 | 吉米 | 1965 -04- 27 | 46 |
| 4 | Stacy | 2002 - 11 - 30 | 8 |
| 5 | 乔治| 2007 - 10 - 25 | 3 |
+ ---- + -------- + ------------ + ------ +
5行中 集合 (0.00秒)
我们根据mysql birthdate date列计算Age!
闰年
格里高利日历中的闰年是将我们的365天日历与现实同步的调整,因为实际上需要花费365天以上的时间让地球绕太阳旋转一次。数学计算出来,你
只需要每4年增加一天,那一天是2月29日。2012年是闰年,因为它可以被4整除。闰年规则并不是那么简单:
•如果年份是百年,则不是闰年
•除非那一年可以被400整除|
否则,如果可以被4整除,那就是闰年
有一个边缘情况使这项任务复杂化。在闰年2月29日出生的人,每4年只在他们的实际“出生日”生日。通常,闰年出生的人的解决方案
是在二月的最后一天庆祝他们的生日,即28日或29日。对于我们的年龄计算,这实际上只是每年一天的问题。例如,我们可以假设
如果您是闰年宝宝,我们只会将您的出生日期视为第28天,并且您的年龄计算仅为每4年一天不正确。这是一个很容易证明合理的权衡。如图所示
令人惊讶的是,即使我们使用无效日期来将它们与curdate()进行比较,查询上方甚至会对2月29日的婴儿起作用。但是,MySQL可以做得更好。
添加计算以处理闰年婴儿
让我们在数据库中添加一个闰年宝贝,这样我们就可以测试出来:
INSERT INTO用户(姓名,生日) VALUES ('Leapie' , '2008-02-29' ) ;
mysql > insert into user ( name,birthdate ) values ('Leapie','2008-02-29' ) ;
查询OK,1行受影响(0.00秒)
mysql > select * from user;
+ ---- + -------- + ------------ +
| id | 名字 | 生日 |
+ ---- + -------- + ------------ +
| 1 | 弗兰 | 1967年 -10 - 31 |
| 2 | 比尔 | 1964年 - 05 - 07年|
| 3 | 吉米 | 1965 -04- 27 |
| 4 | Stacy | 2002 - 11 - 30 |
| 5 | 乔治| 2007 - 10 - 25 |
| 6 | Leapie | 2008 -02- 29 |
+ ---- + -------- + ------------ +
6行于 集 (0.00秒)
如果您尝试插入无效日期会怎样?MySQL将生成警告,并创建一个充满零的日期。除非您将其配置为允许无效日期,否则它不会存储无效日期。因此,如果你尝试插入2011年2月29日的生日,mysql将不会让你。
mysql > insert into user ( name,birthdate ) values ('BadLeap','2011-02-29' ) ;
查询OK,1行受影响,1警告(0.00秒)
mysql > SHOW WARNINGS;
+ --------- + ------ + -------------------------------- ---------------- +
| 等级 | 代码| 消息 |
+ --------- + ------ + -------------------------------- ---------------- +
| 警告| 1265 | 数据截断为列“生日”在行1 |
+ --------- + ------ + -------------------------------- ---------------- +
1行中 集合 (0.00秒)
的MySQL > 选择 *从用户;
+ ---- + --------- + ------------ +
| id | 名字 | 生日 |
+ ---- + --------- + ------------ +
| 1 | 弗兰 | 1967 - 10 - 31 |
| 2 |比尔 | 1964年 - 05 - 07年|
| 3 | 吉米 | 1965 -04- 27 |
| 4 | Stacy | 2002 - 11 - 30 |
| 5 | 乔治 | 2007 - 10 - 25 |
| 6 | Leapie | 2008 -02- 29 |
| 7 | BadLeap | 0000-00-00 |
+ ---- + --------- + ------------ +
7行中 集合 (0.00秒)
令人惊讶的是,MySQL将允许您构建无效日期,甚至将它们与STR_TO_DATE一起使用,即使它不允许存储无效日期:
mysql > SELECT STR_TO_DATE ('2011-02-29','%Y-%c-%e' ) 作为生日;
+ ------------ +
| 生日 |
+ ------------ +
| 2011 -02- 29 |
+ ------------ +
1行中 集合 (0.00秒)
的MySQL > SELECT STR_TO_DATE ('2011年2月31日','%Y-%C-%E' ) 为生日;
+ ------------ +
| 生日 |
+ ------------ +
| 2011-02- 31 |
+ ------------ +
1行中 集合 (0.00秒)
的MySQL > SELECT STR_TO_DATE ('2011-09-30' ,'%Y-%C-%E' ) 为生日;
+ ------------ +
| 生日 |
+ ------------ +
| 2011 -09- 30 |
+ ------------ +
1行中 集合 (0.00秒)
的MySQL > SELECT STR_TO_DATE ('2011年9月31日','%Y-%c-%e' ) 作为生日;
+ ------------ +
| 生日 |
+ ------------ +
| 2011 -09- 31 |
+ ------------ +
1行中 集合 (0.00秒)
的MySQL > SELECT STR_TO_DATE ('2011-09-32' ,'%Y-%C-%E' ) 为生日;
+ ---------- +
| 生日|
+ ---------- +
| NULL |
+ ---------- +
1行中 集合,1警告(0.00秒)
mysql > SELECT STR_TO_DATE ('2011-13-32','%Y-%c-%e' ) 作为生日;
+ ---------- +
| 生日|
+ ---------- +
| NULL |
+ ---------- +
1行中 集合,1个警告(0.00秒)
的MySQL > SELECT STR_TO_DATE ('2011-13-11' ,'%Y-%C-%E' ) 为生日;
+ ---------- +
| 生日|
+ ---------- +
| NULL |
+ ---------- +
1行中 集合,1个警告(0.00秒)
的MySQL > SELECT STR_TO_DATE ('2011-00-11' ,'%Y-%C-%E' ) 为生日;
+ ------------ +
| 生日 |
+ ------------ +
| 2011 -00- 11 |
+ ------------ +
1行中 集合 (0.00秒)
这部分是设计,部分是配置问题。当前版本的MySQL允许您调整服务器中的设置,以指示它将如何处理无效日期。关于闰年的大部分担忧都是学术上的。
我们并不需要担心闰年规则,除非年份可以被4整除,即便如此,由于我们可以安全地构建无效日期以进行比较,即使没有
闰年调整,闰年生日的年龄计算只会在一年中一天不正确,即使我们只使用上面提供的忽略2月29日问题的查询。
至少有几种不同的方法可以解决这个问题:
•确定当前年份是否为非闰年并使用第28年
•让mysql弄清楚2月的最后一天是什么,并将其作为当天使用。
在任何一种情况下,我们都需要使用三元IF()添加到现有查询中。