🦏 第6章 内置函数
📄 一句话概括

三类:取当前时间(curdate、curtime、now)、拆日期(year、month、day)、 算派生值(monthname、dayname、dayofweek、dayofyear、quarter、datediff)。 Demo 里 now() 给 2026-10-30 07:29:34(跑的那一天); monthname 给 October、dayname 给 Friday;dayofweek 给 6,因为它的 1 是星期日。 datediff('2026-10-30', '2026-01-01') 给 302—— 参数顺序是「晚减小」,给负数就说明写反了。

📝 本章和前面几章的关系

上一类处理文本,这一类处理时间——也是「不能硬编」最集中的一类。 时间函数有两件麻烦事:「现在」是相对的(同一句 SQL 隔一天跑结果就不同), 以及「一周从哪天算」各地规定不一样。 这一篇的 Demo 输出带着跑它的日期,别当常量记。

一、取当前时间:三个函数各管一段

curdate() 给日期(2026-10-30),curtime() 给时分秒(07:29:34), now() 给完整的日期时间。 这一段的输出是「跑这一句的那一天那一秒」,你跑的时候肯定不是这个数。 凡是写进正文的当前时间,都要连「什么时候跑的」一起说,否则读者会以为那是固定值。 now() 在同一条语句里多次调用给同一个值—— 因为一条语句算「一个时间点」。 做批次记录时这一点很关键:一批数据的时间戳应该完全一致, 否则对账时排序会乱。

二、拆年、月、日:从日期里取值

year('2026-10-30') 给 2026、month 给 10、day 给 30。 它们吃字符串日期也行,只要格式是 年-月-日。 真实 MySQL 里日期是 DATE 类型,函数照样吃。 最常见的用法是按年按月统计:GROUP BY YEAR(created_at), MONTH(created_at)。 但记住上一篇那条:这样写用不上索引,大表要换成范围条件。

三、英文名与星期几:monthname、dayname、dayofweek

monthname 给 October、dayname 给 Friday—— 都是英文,因为 MySQL 返回的就是英文名。 中文名得自己映射。 dayofweek 给 6,但它代表星期五。 因为它的编号是:1 周日、2 周一、…、6 周五、7 周六。 这个编号跟「周一算第一天」的习惯不同。 做「本周一到周日」这类统计时,先算清楚编号,再写条件——差一天,整张报表的数字就全错。

四、天数、季度、相隔几天

dayofmonth 给 30(这个月的第几天)、dayofyear 给 303(这一年的第几天)、 quarter 给 4(第四季度)。 datediff(晚, 早) 给 302——两个日期之间隔了 302 天。 参数顺序容易反:它算的是 a - b,给负数就说明写反了。 datediff 只算「日期差」,不带时分秒。 要算精确到秒的间隔, MySQL 用 timestampdiff(second, a, b),本沙箱没有这个函数,所以没演示。

五、时间函数与索引:同一条常识再讲一次

WHERE YEAR(created_at) = 2026 会让 created_at 上的索引用不上, 和上一篇 YEAR(birthday) 是同一个毛病。 换成范围:created_at >= '2026-01-01' AND created_at < '2027-01-01'。 结果一样,索引能用。 为什么不用 BETWEEN: BETWEEN '2026-01-01' AND '2026-12-31' 会漏掉 12 月 31 日当天带时分秒的记录 (2026-12-31 10:00:00 比 2026-12-31 大)。 开区间最稳。

六、动手 Demo:从「今天是哪天」到「隔了多少天」

Demo 六段:当前时间、拆年月日、英文名与星期、天数与季度、日期差、索引那条常识。 第一段的输出带着跑它的时间戳,别把它当固定值抄进笔记。

import _q46 as Q
c = Q.con()

# 当前时间
c.execute("SELECT CURDATE() AS d, CURTIME() AS t, NOW() AS dt")
# 拆年月日
c.execute("SELECT YEAR('2026-10-30') AS y, MONTH('2026-10-30') AS m, "
          "DAY('2026-10-30') AS d")
c.execute("SELECT name, YEAR(birthday) AS y, MONTH(birthday) AS m "
          "FROM students LIMIT 3")
# 英文名与星期几
c.execute("SELECT MONTHNAME('2026-10-30') AS mn, "
          "DAYNAME('2026-10-30') AS dn, DAYOFWEEK('2026-10-30') AS dw")
# 天数、季度
c.execute("SELECT DAYOFMONTH('2026-10-30') AS dm, "
          "DAYOFYEAR('2026-10-30') AS dy, QUARTER('2026-10-30') AS q")
# 相隔几天
c.execute("SELECT DATEDIFF('2026-10-30', '2026-01-01') AS days")

实跑输出:

--------------------------------------------------------------------
第二十六课:时间函数,和「今天是哪天」这件事
--------------------------------------------------------------------

  取当前时间:三个函数各管一段
  SQL> SELECT CURDATE() AS d, CURTIME() AS t, NOW() AS dt
  d              t            dt
  ------------------------------------------------
  2026-10-30     07:31:08     2026-10-30 07:31:08
  (1 行)

  **`curdate()` 给日期、`curtime()` 给时分秒、`now()` 给完整的日期时间。**
  **这一段的输出就是跑这句的那一天那一秒**,你跑的时候肯定不是这个数。
  **`now()` 在同一句里多次调用给的是同一个值**,**因为一条语句算「一个时间点」**。
  这一点在做批次记录时很重要。

  拆年、月、日:从日期里取值
  SQL> SELECT YEAR('2026-10-30') AS y, MONTH('2026-10-30') AS m, DAY('2026-10-30') AS d
  y        m      d
  -------------------
  2026     10     30
  (1 行)

  SQL> SELECT name, YEAR(birthday) AS y, MONTH(birthday) AS m FROM students LIMIT 3
  name     y        m
  -----------------------
  张伟     2007     3
  王芳     2007     5
  李娜     2007     7
  (3 行)

  **这些函数吃字符串日期也行**,只要格式是 `年-月-日`。
  **真实 MySQL 里日期是 DATE 类型,函数照样吃。**

  英文名与星期几:monthname、dayname、dayofweek
  SQL> SELECT MONTHNAME('2026-10-30') AS mn, DAYNAME('2026-10-30') AS dn,
       DAYOFWEEK('2026-10-30') AS dw
  mn          dn         dw
  --------------------------
  October     Friday     6
  (1 行)

  **`monthname` 给 `October`、`dayname` 给 `Friday`**——**都是英文,因为 MySQL 返回的就是英文名。**
  **`dayofweek` 给 6,代表星期五。** **注意它从 1 开始且 1 是星期日**:1 周日、2 周一、…、6 周五、
  7 周六。 **这个编号规则跟「周一算第一天」的习惯不一样,用之前先对一遍。**

  天数、季度、相隔几天
  SQL> SELECT DAYOFMONTH('2026-10-30') AS dm, DAYOFYEAR('2026-10-30') AS dy,
       QUARTER('2026-10-30') AS q
  dm     dy      q
  ------------------
  30     303     4
  (1 行)

  SQL> SELECT DATEDIFF('2026-10-30', '2026-01-01') AS days
  days
  -----
  302
  (1 行)

  **10 月 30 日是这一年的第 303 天,第 4 季度。** **`datediff(晚, 早)` 给 302**——
  两个日期之间隔了 302 天。
  **`datediff` 的参数顺序容易反**:`datediff(a, b)` 算的是 `a - b`,**给负数就说明顺序反了。**

  时间函数与索引:同一条常识
  **`WHERE YEAR(created_at) = 2026` 会让 `created_at` 上的索引用不上**,
  **和上一篇 `YEAR(birthday)` 是同一个毛病。**
  **换成范围**:`created_at >= '2026-01-01' AND created_at < '2027-01-01'`。
  **结果一样,索引能用。**
  **另一种写法是用 `BETWEEN`**,
  但 `BETWEEN '2026-01-01' AND '2026-12-31'` **会漏掉 12 月 31 日当天带时分秒的记录**,
  所以还是开区间最稳。

第一段当前时间。 日期、时分秒、完整时间三列——这三列是同一秒的事。 第二段拆年月日。 2026/10/30;换成 birthday 列,前三个人的出生年月也出来了。 第三段英文名。 October、Friday、6——6 是周五,不是周六。 第四段天数与季度。 30、303、4——10 月 30 日是第 303 天。 第五段日期差。 302 天。 第六段没有 SQL,讲的是「怎么改写成范围条件」。

30 秒实验

  1. NOW() 与 CURDATE() 的区别是什么?哪个带时分秒?
  2. DAYOFWEEK('2026-11-01') 给几?那天是星期几?
  3. DATEDIFF('2026-01-01', '2026-10-30') 给正数还是负数?为什么?

常见误区

  1. 把 Demo 里的当前时间当成固定值。 那是跑 demo 那一刻的时间,隔一天就变了, 不能抄进配置或测试用例。
  2. 以为 dayofweek 的 1 是周一。 它是周日,按「周一算第一天」写条件会整体错一天。
  3. 用 BETWEEN '2026-01-01' AND '2026-12-31' 取整年。 12 月 31 日当天带时分秒的记录会被漏掉, 该用 < '2027-01-01'。
自测三问(点开对答案) 1. `NOW()`、`CURDATE()`、`CURTIME()` 各给什么? —— `now()` 给完整日期时间,`curdate()` 只给日期,`curtime()` 只给时分秒。 2. `DAYOFWEEK` 的编号规则是什么? —— 1 是周日、2 是周一、…、7 是周六。跟「周一算第一天」的习惯不一样。 3. 为什么取整年要用 `< '2027-01-01'` 而不是 `BETWEEN … '2026-12-31'`? —— 因为 `2026-12-31 10:00:00` 大于 `2026-12-31`,`BETWEEN` 会漏掉当天的带时间记录。

数学、字符串、时间都过完了。下一篇是 MySQL 8 才有的新东西——三个排名函数。

相关笔记