MySQL必知必会09:时间函数-时间类数据处理

阅读整理自《MySQL 必知必会》- 朱晓峰,详细内容请登录 极客时间 官网购买专栏。


时间函数就是用来处理时间的函数。时间,几乎可以说是各类项目中都会存在的数据,项目需求不同,需要的时间函数也不一样。

  • 如果要统计一天之中不同时间段的销售情况,就要获取时间值中的小时值,这就会用到函数 HOUR();
  • 要计算与去年同期相比的增长率,这就要计算去年同期的日期时间,会用到函数 DATE_ADD();
  • 要计算今天是周几、有没有优惠活动,这就要用到函数 DAYOFWEEK() 了;
  • ……

获取日期时间数据中部分信息的函数

超市的经营者提出,他们希望通过实际的销售数据,了解到一天当中什么时间段卖得好,什么时间段卖得不好,这样他们就可以根据不同时间的销售情况,合理安排商品陈列和人员促销,以实现收益最大化。要达到这个目标,我们就需要统计一天中每小时的销售数量和销售金额。

这里涉及 3 组数据,分别是销售单头表(demo.transactionhead)、销售单明细表 (demo.transactiondetails) 和商品信息表(demo.goodsmaster)(为了便于你理解,表的结构和表里的记录都是经过简化的)。

销售单头表(demo.transactionhead)

transactionidtransactionnocashieridmemberidoperatoridtransdate
101201912010000011112019-12-01 09:25:56
201201912020000011NULL22019-12-02 09:50:50
301201912020000021NULL12019-12-02 10:10:05
401202011020000011212020-11-02 11:15:06
501202011020000021122020-11-02 12:20:03
601202011020000031NULL12020-11-02 13:05:01
701202012020000011212020-12-02 15:25:22
801202012020000021NULL12020-12-02 17:10:25
901202012020000031NULL12020-12-02 18:35:33
1001202012020000041NULL12020-12-02 19:33:55

销售明细表(demo.transactiondetails)

transactioniditemnumberquantitypricesalesvalue
11289178
125525
21389267
226530
3118989
3210550
4310330
52405200
61589445
726530
83133
91289178
102236

商品信息表(demo.goodsmaster

itemnumberbarcodegoodsnamespecificationunitsalesprice
1116开89
2210支装5
33橡皮NULL3

销售单明细表通过流水编号销售单头表关联,其中流水编号是外键。通过流水编号销售单明细表引用销售单头表里的交易时间会员编号等信息,同时,通过商品编号商品信息表关联,引用商品信息表里的商品名称等信息。如图所示:

请添加图片描述

需求:统计一天中每小时的销售数量和销售金额

要统计一天中每小时的销售情况,实际上就是要把销售数据按照小时进行分组统计。那么,解决问题的关键,就是把交易时间的小时部分提取出来。这就要用到 MySQL 的日期时间处理函数 extract()hour() 了。

extract(type from date)表示从日期时间数据“date”中抽取“type”指定的部分。

mysql> select extract(hour from b.transdate) as time_part,
    -> sum(a.quantity) as quantity,
    -> sum(a.salesvalue) as salesvalue
    -> from demo.transactiondetails as a
    -> join demo.transactionhead as b on (b.transactionid = a.transactionid)
    -> group by extract(hour from b.transdate)
    -> order by extract(hour from b.transdate);
+-----------+----------+------------+
| time_part | quantity | salesvalue |
+-----------+----------+------------+
|         9 |   16.000 |     500.00 |
|        10 |   11.000 |     139.00 |
|        11 |   10.000 |      30.00 |
|        12 |   40.000 |     200.00 |
|        13 |    5.000 |     445.00 |
|        15 |    6.000 |      30.00 |
|        17 |    1.000 |       3.00 |
|        18 |    2.000 |     178.00 |
|        19 |    2.000 |       6.00 |
+-----------+----------+------------+
9 rows in set (0.00 sec)

查询的过程是这样的:

  1. 从交易时间中抽取小时信息:extract(hour from b.transdate)
  2. 按交易的小时信息分组;
  3. 按分组统计销售数量和销售金额的和;
  4. 按交易的小时信息排序。

需要获取其他时间部分的信息,可以参考下时间单位

extract() 函数中的 hour 表示要获取时间的类型,而 hour() 是一个函数,hour(time) 可以单独使用,表示返回 time 的小时部分信息。

select hour(b.transdate) as time_part,
sum(a.quantity) as quantity,
sum(a.salesvalue) as salesvalue 
from demo.transactiondetails as a
join demo.transactionhead as b on (b.transactionid = a.transactionid)
group by hour(b.transdate)
order by hour(b.transdate);

其他时间函数:

  • YEAR(date):获取 date 中的年
  • MONTH(date):获取 date 中的月
  • DAY(date):获取 date 中的日
  • HOUR(date):获取 date 中的小时
  • MINUTE(date):获取 date 中的分
  • SECOND(date):获取 date 中的秒

计算日期时间的函数

  • date_add(date, interval 表达式 type):表示计算从时间点“date”开始,向前或者向后一段时间间隔的时间。“表达式”的值为时间间隔数,正数表示向后,负数表示向前,“type”表示时间间隔的单位(比如年、月、日等)
  • last_day(date):表示获取日期时间“date”所在月份的最后一天的日期。

需求:计算这个月单品销售金额的统计,以及与去年同期相比的增长率。

假设今天是 2020 年 12 月 10 日,那么也就是要需要获取 2019 年 12 月的销售数据。

步骤:

  1. 用 date_add 函数,获取到 2020 年 12 月 10 日上一年的日期:2019 年 12 月 10 日

    mysql> select date_add('2020-12-10', interval - 1 year);
    +-------------------------------------------+
    | date_add('2020-12-10', interval - 1 year) |
    +-------------------------------------------+
    | 2019-12-10                                |
    +-------------------------------------------+
    
  2. 获取 2019 年 12 月 10 日这个时间节点开始上个月的日期,这样做的目的是方便获取月份的起始时间

    mysql> select date_add(date_add('2020-12-10', interval - 1 year), interval - 1 month);
    +-------------------------------------------------------------------------+
    | date_add(date_add('2020-12-10', interval - 1 year), interval - 1 month) |
    +-------------------------------------------------------------------------+
    | 2019-11-10                                                              |
    +-------------------------------------------------------------------------+
    
  3. 获取 2019 年 11 月 10 日这个时间点月份的最后一天,继续接近的目标:2019 年 12 月 01 日

    mysql> select last_day(date_add(date_add('2020-12-10', interval - 1 year), interval - 1 month));
    +-----------------------------------------------------------------------------------+
    | last_day(date_add(date_add('2020-12-10', interval - 1 year), interval - 1 month)) |
    +-----------------------------------------------------------------------------------+
    | 2019-11-30                                                                        |
    +-----------------------------------------------------------------------------------+
    

    如果用 11-30 作为统计销售额的起始日期,可能就多算了这一天的销售,所以应该找这个时间的下一天。

  4. 计算 2019 年 11 月 30 日后一天的日期

    mysql> select date_add(last_day(date_add(date_add('2020-12-10', interval - 1 year), interval - 1 month)), interval +1 day);
    +--------------------------------------------------------------------------------------------------------------+
    | date_add(last_day(date_add(date_add('2020-12-10', interval - 1 year), interval - 1 month)), interval +1 day) |
    +--------------------------------------------------------------------------------------------------------------+
    | 2019-12-01                                                                                                   |
    +--------------------------------------------------------------------------------------------------------------+
    

    此时,终于获得了正确的起始日期:2019 年 12 月 01 日。

同样类似方法,获得截止日期:

mysql> select date_add(last_day(date_add('2020-12-10', interval - 1 year)), interval +1 day);
+--------------------------------------------------------------------------------+
| date_add(last_day(date_add('2020-12-10', interval - 1 year)), interval +1 day) |
+--------------------------------------------------------------------------------+
| 2020-01-01                                                                     |
+--------------------------------------------------------------------------------+

可以用 date_add() 来计算从某个时间点开始,过去或者未来一个时间间隔的时间;通过 last_day() 函数,获得某个时间节点当月的最后一天的日期。借助它们,就可以获取从某个时间节点出发的指定月份的起始日期截止日期

除了 date_add(),adddate()、date_sub() 和 subdate() 也能达到同样的效果:

  • adddate():跟 date_add() 用法一致

    mysql> select adddate(last_day(adddate('2020-12-10', interval - 1 year)), interval +1 day);
    +------------------------------------------------------------------------------+
    | adddate(last_day(adddate('2020-12-10', interval - 1 year)), interval +1 day) |
    +------------------------------------------------------------------------------+
    | 2020-01-01                                                                   |
    +------------------------------------------------------------------------------+
    
  • date_sub(),subdate():与 date_add() 用法类似,方向相反,执行日期的减操作

    mysql> select date_sub('2020-12-10', interval 1 year);
    +-----------------------------------------+
    | date_sub('2020-12-10', interval 1 year) |
    +-----------------------------------------+
    | 2019-12-10                              |
    +-----------------------------------------+
    
    mysql> select subdate('2020-12-10', interval -1 year);
    +-----------------------------------------+
    | subdate('2020-12-10', interval -1 year) |
    +-----------------------------------------+
    | 2021-12-10                              |
    +-----------------------------------------+
    

其他日期时间函数

curdate()、dayofweek()、date_format 和 datediff()。

  • curdate():获取当前的日期。日期格式为“YYYY-MM-DD”,也就是年月日的格式。
  • dayofweek(week):获取日期“date”是周几。1 表示周日,2 表示周一,以此类推,直到 7 表示周六。

举例:按照周中不同的日期进行促销,单品促销信息(demo.discountrule)。

这个表中的信息表示单品打折的时间和折扣率:编号是 1 的商品,周一、周三和周五打折,折扣率分别是 9 折、75 折和 88 折;编号是 2 的商品,周二、周四和周六打折,折扣率分别是 5 折、65 折和 8 折。周日,所有商品打 5 折。

branchiditemnumberweekdaydiscountrate
1110.9
1130.75
1150.88
1220.5
1240.65
1260.8
1170.5
1270.5
1370.5
create table demo.discountrule 
(
branchid int,
itemnumber text,
weekday text,
discountrate text
);
select * from demo.discountrule;

查询今天商品的全部折后价格:

mysql> select curdate() as 日期,
    -> case dayofweek(curdate()) - 1 when 0 then 7 else dayofweek(curdate()) - 1 end as 周几,
    -> a.goodsname as 商品名称,
    -> a.salesprice as 价格,
    -> ifnull(b.discountrate, 1) as 折扣率,
    -> a.salesprice * ifnull(b.discountrate, 1) as 折后价格
    -> from demo.goodsmaster a
    -> left join demo.discountrule b on (a.itemnumber = b.itemnumber and case dayofweek(curdate()) - 1 when 0 then 7 else dayofweek(curdate()) -1 end = b.weekday);
+------------+------+----------+--------+--------+----------+
| 日期       | 周几 | 商品名称 | 价格   | 折扣率 | 折后价格 |
+------------+------+----------+--------+--------+----------+
| 2022-03-09 |    3 | 书       | 89.000 | 0.75   |    66.75 |
| 2022-03-09 |    3 | 笔       |  5.000 | 1      |        5 |
| 2022-03-09 |    3 | 橡皮     |  3.000 | 1      |        3 |
+------------+------+----------+--------+--------+----------+

MySQL 中 CASE 函数的语法如下:

CASE 表达式 WHEN 值1 THEN 表达式1 [ WHEN 值2 THEN 表达式2] ELSE 表达式m END

在当前业务查询中,“表达式”有 7 种可能的值。通过 case 函数,可以根据 dayofweek() 函数返回的值对每个返回值进行处理,从而跟促销信息表中的字段 weekday 对应。

关系图:

请添加图片描述

除了获取特定的日期,咱们还经常需要把日期按照一定的格式显示出来,这就要用到日期时间格式化的函数 date_format(),它表示将日期时间“date”按照指定格式显示。

mysql> select date_format("2020-12-01 13:25:50", "%T");  -- 24小时制
+------------------------------------------+
| date_format("2020-12-01 13:25:50", "%T") |
+------------------------------------------+
| 13:25:50                                 |
+------------------------------------------+

mysql> select date_format("2020-12-01 13:25:50", "%r");  -- 上下午时间
+------------------------------------------+
| date_format("2020-12-01 13:25:50", "%r") |
+------------------------------------------+
| 01:25:50 PM                              |
+------------------------------------------+

时间格式:https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_date-format

datediff(date1,date2),表示日期“date1”与日期“date2”之间差几天

mysql> select datediff("2021-02-01","2020-12-01");
+-------------------------------------+
| datediff("2021-02-01","2020-12-01") |
+-------------------------------------+
|                                  62 |
+-------------------------------------+

小结

分类函数
获取日期时间
数据中部分信息函数
extract() 获取日期时间中指定的值
hour() 小时; year() 年份; month() 月份
day() 日; minute() 分; second() 秒
日期时间计算函数date_add() 计算从某个时间点出发过去或未来一段时间间隔的时间。adddate()也是
date_sub() 与 date_add() 相似,方向相反。subdate()也是
datediff() 计算2个时间点之间相隔的天数
last_day() 计算给定时间点当月的最后一天的日期
其他时间函数curdate() 当前时间
weekofday() 今天是星期几,周日为1,周一为2,递推
date_format() 格式化日期时间

其他

构建外键的时候,出现一个报错,1170 - BLOB/TEXT column ‘xxx’ used in key specification without a key lenght。

请添加图片描述

排查发现是 demo.discountrule 表的字段类型搞错了:

mysql> describe demo.discountrule;
+--------------+------+------+-----+---------+-------+
| Field        | Type | Null | Key | Default | Extra |
+--------------+------+------+-----+---------+-------+
| branchid     | int  | YES  |     | NULL    |       |
| itemnumber   | text | YES  |     | NULL    |       |
| weekday      | text | YES  |     | NULL    |       |
| discountrate | text | YES  |     | NULL    |       |
+--------------+------+------+-----+---------+-------+

mysql> alter table demo.discountrule change itemnumber itemnumber int;  -- 修改表字段类型

mysql> describe demo.discountrule;
+--------------+------+------+-----+---------+-------+
| Field        | Type | Null | Key | Default | Extra |
+--------------+------+------+-----+---------+-------+
| branchid     | int  | YES  |     | NULL    |       |
| itemnumber   | int  | YES  | MUL | NULL    |       |
| weekday      | text | YES  |     | NULL    |       |
| discountrate | text | YES  |     | NULL    |       |
+--------------+------+------+-----+---------+-------+
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值