返回

MySQL查询未来时间:解决跨天数据检索问题

mysql

MySQL 时间查询:动态获取当前时间之后指定小时数的数据

遇到了一个时间查询需求,要求查询结果包含从当前时间开始,未来指定小时数范围内的所有数据,并且需要考虑跨天的情况。简单说,就是既要能查今天,也要能查明天。

问题

现有查询语句可以获取当天未来 24 小时内的数据,挺好。但问题在于,如果指定一个大于当前时间到当天 24 点之间剩余小时数的数值,比如,现在晚上 10 点,你给个hours = 17,想查未来17小时的,那按理说到第二天下午3点前的数据都要有,但这个查询不会包含第二天的数据。

现在的查询长这样:

SELECT *
FROM jobDetails jd
WHERE TIME(time) > TIME(CONVERT_TZ(NOW(), '+00:00', '+05:00'))
AND TIME(time) <= TIME(DATE_ADD(CONVERT_TZ(NOW(), '+00:00', '+05:00'), INTERVAL ? HOUR));

数据库里 time 字段是这么存的:

**time** 
19:43
02:50
16:48
18:15
16:37
17:00
16:40
16:42
19:13
21:44
03:25
03:25
03:25
03:25
03:25

问题原因分析

原查询只针对 time 字段的时间部分进行比较,忽略了日期。DATE_ADD 函数虽然增加了小时数,但当 time 字段本身只包含时间信息时,跨天的比较就失效了。原查询没有考虑到 time 字段没有日期。

解决方案

方案一: 改造表结构 (推荐)

如果可以修改表结构,把 time 列改成 DATETIMETIMESTAMP 类型,直接存储日期和时间,那所有问题就简单了! 查询时,可以直接用 >< 来比较。

  1. 原理: DATETIMETIMESTAMP 类型包含日期和时间信息,可以直接比较,不用拆分日期和时间。

  2. 代码示例:

    先要把表改一下:

    ALTER TABLE jobDetails MODIFY COLUMN time DATETIME;
    -- 如果原来 time 字段的数据只是时间,没有日期,还需要更新一下数据,给他们加上日期。比如加上当天的日期:
    -- UPDATE jobDetails SET time = CONCAT(CURDATE(), ' ', time); 
    

    然后,查询语句就简单多了:

    SELECT *
    FROM jobDetails
    WHERE time > CONVERT_TZ(NOW(), '+00:00', '+05:00')
      AND time <= DATE_ADD(CONVERT_TZ(NOW(), '+00:00', '+05:00'), INTERVAL ? HOUR);
    
  3. 安全提示: 务必备份数据库,再修改表结构!

方案二: 使用日期和时间函数组合判断

如果不能动表结构,只能在 time 字段上下功夫。基本思路是:分别比较日期和时间。

  1. 原理: 将当前时间和增加小时数后的时间,都拆分成日期和时间两部分,对jobDetails表的time,也加上日期(当天的或者第二天的),进行组合条件判断。

  2. 代码示例:

    SELECT *
    FROM jobDetails jd
    WHERE (
        (
            DATE(DATE_ADD(CONVERT_TZ(NOW(), '+00:00', '+05:00'), INTERVAL ? HOUR)) = CURDATE()  -- 如果目标时间还在今天
            AND TIME(time) > TIME(CONVERT_TZ(NOW(), '+00:00', '+05:00'))
            AND TIME(time) <= TIME(DATE_ADD(CONVERT_TZ(NOW(), '+00:00', '+05:00'), INTERVAL ? HOUR))
        )
        OR
        (
             DATE(DATE_ADD(CONVERT_TZ(NOW(), '+00:00', '+05:00'), INTERVAL ? HOUR)) > CURDATE() -- 如果目标时间超出了今天
             AND
             (
                TIME(time) > TIME(CONVERT_TZ(NOW(), '+00:00', '+05:00'))  -- 今天晚些时候的
                OR TIME(time) <= TIME(DATE_ADD(CONVERT_TZ(NOW(), '+00:00', '+05:00'), INTERVAL ? HOUR))  -- 或者是截止日期的
             )
        )
    );
    
  3. 进阶使用技巧:

    • 如果经常要执行这类查询,可以考虑创建一个函数或存储过程,把这个复杂的逻辑封装起来,简化使用。
    • 针对time列增加索引,但是效果不大。
  4. 更简洁的方案二sql

SELECT *
FROM jobDetails jd
WHERE
  (
    DATE(CONVERT_TZ(NOW(), '+00:00', '+05:00')) = CURDATE() AND
    TIME(time) > TIME(CONVERT_TZ(NOW(), '+00:00', '+05:00')) AND
    TIME(time) <= TIME(DATE_ADD(CONVERT_TZ(NOW(), '+00:00', '+05:00'), INTERVAL ? HOUR))
  )
  OR
  (
  	DATE(CONVERT_TZ(NOW(),'+00:00','+05:00')) < DATE(DATE_ADD(CONVERT_TZ(NOW(), '+00:00', '+05:00'), INTERVAL ? HOUR)) and
    (
       TIME(time) >= TIME(CONVERT_TZ(NOW(), '+00:00', '+05:00'))
         OR
       TIME(time) <= TIME(DATE_ADD(CONVERT_TZ(NOW(), '+00:00', '+05:00'), INTERVAL ? HOUR))

    )

  );

方案三: 将time转换为当天的datetime类型(DateTime Casting)

如果表结构没法改,也可以先临时把 time 拼成一个完整的 datetime, 再进行比较。

  1. 原理 : 将 time 字段值与当前日期或下一天日期拼接, 构建完整的 datetime 值,然后进行比较.

  2. 代码示例

    SELECT *
    FROM jobDetails jd
    WHERE
    CASE
       WHEN DATE(DATE_ADD(CONVERT_TZ(NOW(), '+00:00', '+05:00'), INTERVAL ? HOUR)) = CURDATE() THEN  -- 如果在同一天
         CAST(CONCAT(CURDATE(), ' ', TIME(time)) AS DATETIME) > CONVERT_TZ(NOW(), '+00:00', '+05:00')
         AND CAST(CONCAT(CURDATE(), ' ', TIME(time)) AS DATETIME) <= DATE_ADD(CONVERT_TZ(NOW(), '+00:00', '+05:00'), INTERVAL ? HOUR)
       ELSE  -- 跨天了
        (
             CAST(CONCAT(CURDATE(), ' ', TIME(time)) AS DATETIME) > CONVERT_TZ(NOW(), '+00:00', '+05:00')
             OR
             CAST(CONCAT(DATE_ADD(CURDATE(), INTERVAL 1 DAY), ' ', TIME(time)) AS DATETIME) <= DATE_ADD(CONVERT_TZ(NOW(), '+00:00', '+05:00'), INTERVAL ? HOUR)
        )
    
    END;
    
    

    这个方案在MySQL 8 表现不错, 在MySQL 5.7 , 需要稍微调整 CAST 函数的用法。

  3. 代码示例(兼容MySQL 5.7)

        SELECT *
    FROM jobDetails jd
    WHERE
    CASE
        WHEN DATE(DATE_ADD(CONVERT_TZ(NOW(), '+00:00', '+05:00'), INTERVAL ? HOUR)) = CURDATE() THEN  -- 如果在同一天
           CONCAT(CURDATE(), ' ', TIME(time)) > CONVERT_TZ(NOW(), '+00:00', '+05:00')
          AND CONCAT(CURDATE(), ' ', TIME(time)) <= DATE_ADD(CONVERT_TZ(NOW(), '+00:00', '+05:00'), INTERVAL ? HOUR)
        ELSE  -- 跨天了
         (
             CONCAT(CURDATE(), ' ', TIME(time)) > CONVERT_TZ(NOW(), '+00:00', '+05:00')
              OR
              CONCAT(DATE_ADD(CURDATE(), INTERVAL 1 DAY), ' ', TIME(time)) <= DATE_ADD(CONVERT_TZ(NOW(), '+00:00', '+05:00'), INTERVAL ? HOUR)
         )
    
    END;
    
    

注意 :在MySQL5.7版本中不支持直接CASTDATETIME的用法。

  1. 不推荐使用原因
    此方式导致索引失效,无法走索引进行查询。

总结

修改表结构最直接有效,如果改不了表,就用第二或者第三种方案,组合日期和时间判断,麻烦点,但是管用。要注意低版本MySQL数据库中CAST的用法,避免出错。