美文网首页
[SQL] 生日问题的一种解法

[SQL] 生日问题的一种解法

作者: zqq90 | 来源:发表于2018-11-01 15:49 被阅读0次

这是同事做分享的时候提及的一个问题, 觉得比较有意思就尝试了一下

生日问题

用一条 SQL 得出雇员们的全名,以及他的最近一次生日(如果他今年没有过生日,显示今年的生日,如果今年过生日了,显示明年的生日), 环境为 MySQL 5.7+,

表结构如下:

CREATE TABLE employees (
    emp_no      INT             NOT NULL,
    birth_date  DATE            NOT NULL,
    first_name  VARCHAR(14)     NOT NULL,
    last_name   VARCHAR(16)     NOT NULL,
    gender      ENUM ('M','F')  NOT NULL,    
    hire_date   DATE            NOT NULL,
    PRIMARY KEY (emp_no)
);

记录详见附录

废话少说, 上代码

虽然看着 实际很简单, 都是注释害的

-- 为了思路清晰, 分了很多子查询, 对单测也友好
-- 思路真的很简单, 别掉队
-- PS: 不考虑时区问题
-- PS: 不考虑跨天执行的临界情况
-- PS: 部分字段使用驼峰命名, 莫怪, 真的只是为了更清晰一些

SELECT fullname, birthday, 
    -- 这里就算正式开始了
    CASE 
      -- 如果生日是 02-29 直接取下一个 02-29 就可以了
      WHEN birthMonthDay = 229 THEN str_to_date(next_229, "%Y%m%d")
      -- 否则, 只需要看是不是需要 +1 年: 今年的生日已经过了 +1, 否则不加
      ELSE str_to_date((thisYear + if(birthMonthDay < thisMonthDay, 1, 0)) * 10000 + birthMonthDay, "%Y%m%d")
    END as next_birthday
    -- 到这里整体思路就已经结束了, 意外不? 简单吧? 还没完, 咱们还有几个问题没解决呢
FROM 
-- 员工的基础信息拆解: 为了简化上面的操作, 非常简单, 不多解释
  (
    SELECT 
      CONCAT(first_name, ' ', last_name) as fullname, birth_date as birthday, 
      0 + date_format(birth_date, '%m%d') as birthMonthDay
    FROM employees
  ) t_employees_meta
-- 接下来, 最关键的就是解决 next_229, 即: 下一个 02-29 在哪一年?
JOIN (
  -- 如果可以的话, 拆成视图: CREATE VIEW t_today_meta AS
  SELECT *, 
    -- 解决 next_229 问题:
    CASE 
      -- 如果今天是 02-29 肯定就是今天了 (可以合并到下一条, 为了简单先剔出来)
      WHEN thisMonthDay = 229 THEN thisYear * 10000 + 229
      -- 如果今天小于 02-29, 且是闰年: 直接取今年的 02-29
      WHEN thisMonthDay < 229 and (thisYear % 4 = 0 AND thisYear % 100 <> 0 OR thisYear % 400 = 0)
        THEN thisYear * 10000 + 229
      -- 否则为下一个闰年的 02-29
      -- 如果下一个疑似闰年不是闰年, +4 后必然是闰年
      --   PS: 不需要判断 %4, 因为候选已经满足了
      ELSE if(nextLeapYearCandidate % 100 <> 0 OR nextLeapYearCandidate % 400 = 0,
        nextLeapYearCandidate, nextLeapYearCandidate +4) * 10000 + 229
    END as next_229
  FROM (
    SELECT
      today,
      year(today) as thisYear,
      0 + date_format(today, '%m%d') as thisMonthDay,
      -- 下一个疑似闰年, 只满足 4 的倍数, 先不管其他 (为了上层更简单, 这里先计算好这个候选)
      year(today) + 4 - year(today) % 4 as nextLeapYearCandidate
    FROM (
      -- 为了方便测试, 写成子查询, 可以指定某天
      SELECT curdate()
      -- SELECT str_to_date(20181101, "%Y%m%d")
      -- SELECT str_to_date(20200228, "%Y%m%d")
      -- SELECT str_to_date(20200229, "%Y%m%d")
      -- SELECT str_to_date(20980220, "%Y%m%d")
      -- SELECT str_to_date(20980301, "%Y%m%d")
      -- SELECT str_to_date(19970301, "%Y%m%d")
      -- SELECT str_to_date(20000229, "%Y%m%d")
        as today
    ) t_today_source
  ) t_today
) t_today_meta

执行结果详见附录

其他思考

  • 优化点: 将解决 next_229 问题 的部分拆成视图 (VIEW), 可以实现共用, 题目要求 "用一条 SQL" 所以就没这么做

附录:

员工数据:

INSERT INTO `employees` VALUES (10001,'1953-02-28','Georgi','Facello','M','1986-06-26'),
(10002,'1964-02-29','Bezalel','Simmel','F','1985-11-21'),
(10003,'1959-12-03','Parto','Bamford','M','1986-08-28'),
(10004,'1954-05-01','Chirstian','Koblick','M','1986-12-01'),
(10005,'1955-01-21','Kyoichi','Maliniak','M','1989-09-12'),
(10006,'1953-11-20','Anneke','Preusig','F','1989-06-02'),
(10007,'1957-05-23','Tzvetan','Zielinski','F','1989-02-10');

数据来源: https://github.com/datacharmer/test_db, 有稍许修改

查询结果

  • 日期: 20181101
+-------------------+------------+---------------+
| fullname          | birthday   | next_birthday |
+-------------------+------------+---------------+
| Georgi Facello    | 1953-02-28 | 2019-02-28    |
| Bezalel Simmel    | 1964-02-29 | 2020-02-29    |
| Parto Bamford     | 1959-12-03 | 2018-12-03    |
| Chirstian Koblick | 1954-05-01 | 2019-05-01    |
| Kyoichi Maliniak  | 1955-01-21 | 2019-01-21    |
| Anneke Preusig    | 1953-11-20 | 2018-11-20    |
| Tzvetan Zielinski | 1957-05-23 | 2019-05-23    |
+-------------------+------------+---------------+
  • 日期: 20200228
+-------------------+------------+---------------+
| fullname          | birthday   | next_birthday |
+-------------------+------------+---------------+
| Georgi Facello    | 1953-02-28 | 2020-02-28    |
| Bezalel Simmel    | 1964-02-29 | 2020-02-29    |
| Parto Bamford     | 1959-12-03 | 2020-12-03    |
| Chirstian Koblick | 1954-05-01 | 2020-05-01    |
| Kyoichi Maliniak  | 1955-01-21 | 2021-01-21    |
| Anneke Preusig    | 1953-11-20 | 2020-11-20    |
| Tzvetan Zielinski | 1957-05-23 | 2020-05-23    |
+-------------------+------------+---------------+
  • 日期: 20200229
+-------------------+------------+---------------+
| fullname          | birthday   | next_birthday |
+-------------------+------------+---------------+
| Georgi Facello    | 1953-02-28 | 2021-02-28    |
| Bezalel Simmel    | 1964-02-29 | 2020-02-29    |
| Parto Bamford     | 1959-12-03 | 2020-12-03    |
| Chirstian Koblick | 1954-05-01 | 2020-05-01    |
| Kyoichi Maliniak  | 1955-01-21 | 2021-01-21    |
| Anneke Preusig    | 1953-11-20 | 2020-11-20    |
| Tzvetan Zielinski | 1957-05-23 | 2020-05-23    |
+-------------------+------------+---------------+
  • 日期: 20980220

取这个 test case 是因为 2100 年不是闰年, 看是否真的跳过了

+-------------------+------------+---------------+
| fullname          | birthday   | next_birthday |
+-------------------+------------+---------------+
| Georgi Facello    | 1953-02-28 | 2098-02-28    |
| Bezalel Simmel    | 1964-02-29 | 2104-02-29    |
| Parto Bamford     | 1959-12-03 | 2098-12-03    |
| Chirstian Koblick | 1954-05-01 | 2098-05-01    |
| Kyoichi Maliniak  | 1955-01-21 | 2099-01-21    |
| Anneke Preusig    | 1953-11-20 | 2098-11-20    |
| Tzvetan Zielinski | 1957-05-23 | 2098-05-23    |
+-------------------+------------+---------------+
  • 日期: 20980301
+-------------------+------------+---------------+
| fullname          | birthday   | next_birthday |
+-------------------+------------+---------------+
| Georgi Facello    | 1953-02-28 | 2099-02-28    |
| Bezalel Simmel    | 1964-02-29 | 2104-02-29    |
| Parto Bamford     | 1959-12-03 | 2098-12-03    |
| Chirstian Koblick | 1954-05-01 | 2098-05-01    |
| Kyoichi Maliniak  | 1955-01-21 | 2099-01-21    |
| Anneke Preusig    | 1953-11-20 | 2098-11-20    |
| Tzvetan Zielinski | 1957-05-23 | 2098-05-23    |
+-------------------+------------+---------------+
  • 日期: 19970301
+-------------------+------------+---------------+
| fullname          | birthday   | next_birthday |
+-------------------+------------+---------------+
| Georgi Facello    | 1953-02-28 | 1998-02-28    |
| Bezalel Simmel    | 1964-02-29 | 2000-02-29    |
| Parto Bamford     | 1959-12-03 | 1997-12-03    |
| Chirstian Koblick | 1954-05-01 | 1997-05-01    |
| Kyoichi Maliniak  | 1955-01-21 | 1998-01-21    |
| Anneke Preusig    | 1953-11-20 | 1997-11-20    |
| Tzvetan Zielinski | 1957-05-23 | 1997-05-23    |
+-------------------+------------+---------------+
  • 日期: 20000229
+-------------------+------------+---------------+
| fullname          | birthday   | next_birthday |
+-------------------+------------+---------------+
| Georgi Facello    | 1953-02-28 | 2001-02-28    |
| Bezalel Simmel    | 1964-02-29 | 2000-02-29    |
| Parto Bamford     | 1959-12-03 | 2000-12-03    |
| Chirstian Koblick | 1954-05-01 | 2000-05-01    |
| Kyoichi Maliniak  | 1955-01-21 | 2001-01-21    |
| Anneke Preusig    | 1953-11-20 | 2000-11-20    |
| Tzvetan Zielinski | 1957-05-23 | 2000-05-23    |
+-------------------+------------+---------------+

相关文章

  • [SQL] 生日问题的一种解法

    这是同事做分享的时候提及的一个问题, 觉得比较有意思就尝试了一下 生日问题 用一条 SQL 得出雇员们的全名,以及...

  • 第一章 引论

    1.本书讨论的内容 设有一组N个数而要确定其中第K个最大者,称之为选择问题 一种解法 该问题的一种解法是将这N个数...

  • LeetCode之Queries on a Permutatio

    问题: 方法:最简单解法,模拟整个操作,代码如下所示;网上还有一种FenwickTree的解法,有时间可以学习一下...

  • 用栈代替递归之汉诺塔问题

    问题描述 递归解法 运行结果 手工解法 非递归解法 运行结果 递归中的栈

  • 八皇后问题一种解法

    首先定义一个二维数组表示棋盘: 检查方式:每次落点检查 纵向/ 左斜方/ 右斜方是否满足条件 落点的方式:从上往下...

  • leetcode2-Add Two Numbers

    问题: 解法:

  • 经典SQL50题

    二刷经典SQL面试50题,当当,决定将解法重新梳理一遍。从一刷的磕磕绊绊,自己就是常见错误解法,到二刷思维...

  • 约瑟夫问题

    约瑟夫问题 一、数组解法 二、循环队列 三、数学解法

  • 回文数

    第一种解法 第二种解法

  • 【圆锥曲线】圆

    一、切线方程 问题 解法一 解法二 y_0)=0\&下面化简同解法一\end{align}$$

网友评论

      本文标题:[SQL] 生日问题的一种解法

      本文链接:https://www.haomeiwen.com/subject/ogsxxqtx.html