SQL 时间范围和时间粒度
前言
使用 SQL 进行业务数据计算时,经常会遇到两个概念:时间范围 和 时间粒度 。以 最近一天的每小时的用户访问人数 为例:
- 最近一天 是时间范围
- 每小时 是时间粒度
常见的时间范围:最近五分钟、最近一小时、最近一天、最近一周、最近一月、最近一年、截止到今天、截止到本周、截止到本月、截止到今年。
常见的时间粒度:五分钟、小时、天、周、月、年。
大多数情况下,我们需要根据计算时间和时间范围,计算出业务数据的开始时间和结束时间,用于过滤业务数据;然后再根据业务数据的业务时间和时间粒度,计算出业务时间点,用于分组统计业务数据。
假设用户访问表(user_visit)记录如下:
| id | uid | timestamp |
|---|---|---|
| 1 | u1 | 2022-09-19 15:10:58 |
| 2 | u2 | 2022-09-19 16:24:19 |
| 3 | u1 | 2022-09-20 01:04:03 |
| 4 | u2 | 2022-09-20 02:12:36 |
| 5 | u1 | 2022-09-20 02:35:03 |
| 6 | u1 | 2022-09-20 03:10:27 |
使用 最近一天 过滤数据,开始时间:2022-09-20 00:00:00,结束时间:2022-09-21 00:00:00,SQL 伪代码:
SELECT
*
FROM
user_visit
WHERE
timestamp >= "2022-09-20 00:00:00"
AND timestamp < "2022-09-21 00:00:00"
过滤结果:
| id | uid | timestamp |
|---|---|---|
| 3 | u1 | 2022-09-20 01:04:03 |
| 4 | u2 | 2022-09-20 02:12:36 |
| 5 | u1 | 2022-09-20 02:35:03 |
| 6 | u1 | 2022-09-20 03:10:27 |
过滤后的业务数据,使用 小时 将业务时间转换成业务时间点,转换结果:
| id | uid | timestamp |
|---|---|---|
| 3 | u1 | 2022-09-20 01:00:00 |
| 4 | u2 | 2022-09-20 02:00:00 |
| 5 | u1 | 2022-09-20 02:00:00 |
| 6 | u1 | 2022-09-20 03:00:00 |
按小时分组统计用户访问人数,SQL 伪代码:
SELECT
timestamp, COUNT(DISTINCT(uid)) AS uids
FROM
user_visit
GROUP BY
timestamp
统计结果:
| timestamp | uids |
|---|---|
| 2022-09-20 01:00:00 | 1 |
| 2022-09-20 02:00:00 | 2 |
| 2022-09-20 03:00:00 | 1 |
整个过程涉及两个关键的时间计算:
- 根据计算时间和时间范围,计算业务数据开始时间和结束时间
- 根据业务时间和时间粒度,计算业务时间点
这两个时间的计算均需要通过 SQL 的 日期时间函数 实现。然而不同的数据库对于日期时间函数的支持程度差异很大,实际的计算过程可能比较繁琐。
本文以阿里云 ODPS 和 RDS 为例,详细说明日期时间函数关于时间范围和时间粒度的计算方法。
时间范围的开始时间是闭区间,结束时间是开区间。
时间类型
阿里云的 ODPS 和 RDS 都是支持日期时间(DATETIME)类型的,业务数据可以直接使用 DATETIME 存储业务时间;也可以使用其它数据类型存储业务时间,常见的有日期时间字符串(STRING)和 Unix 时间戳(INT)。
我们建议将业务时间统一转换成 DATETIME 类型之后再进行时间计算。
日期时间字符串
以字符串 2022-09-20 15:10:58 例,将其转换成 DATETIME。
ODPS
TO_DATE('2022-09-20 15:10:58', 'yyyy-mm-dd hh:mi:ss')
RDS
STR_TO_DATE('2022-09-20 15:10:58', '%Y-%m-%d %H:%i:%s')
Unix 时间戳
以时间戳 1663657859 为例,将其转换成 DATETIME。
ODPS
FROM_UNIXTIME(1663657859)
RDS
FROM_UNIXTIME(1663657859)
时间范围
我们使用 当前时间 指代 计算时间,获取当前时间(DATETIME):
ODPS
GETDATE()
RDS
NOW()
最近五分钟
以计算时间:2022-09-20 17:07:33 为例,最近五分钟的业务开始时间应为:2022-09-20 17:00:00,业务结束时间应为:2022-09-20 17:05:00。
ODPS
// 开始时间
FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(GETDATE()) / 300 - 1) * 300)
// 结束时间
FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(GETDATE()) / 300) * 300)
RDS
// 开始时间
FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(NOW()) / 300 - 1) * 300)
// 结束时间
FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(NOW()) / 300) * 300)
300 表示 5 分钟,即:300 秒。
最近一小时
以计算时间 2022-09-20 17:19:57 为例,最近一小时的业务开始时间应为 2022-09-20 16:00:00,业务结束时间应为 2022-09-20 17:00:00。
ODPS
// 开始时间
DATETRUNC(DATEADD(GETDATE(), -1, 'hh'), 'hh')
// 结束时间
DATETRUNC(GETDATE(), 'hh')
RDS
// 开始时间
DATE_FORMAT(DATE_ADD(NOW(), INTERVAL - 1 HOUR), '%Y-%m-%d %H:00:00')
// 结束时间
DATE_FORMAT(NOW(), '%Y-%m-%d %H:00:00')
最近一天
以计算时间 2022-09-20 17:31:06 为例,最近一天的业务开始时间应为 2022-09-19 00:00:00,业务结束时间应为 2022-09-20 00:00:00。
ODPS
// 开始时间
DATETRUNC(DATEADD(GETDATE(), -1, 'dd'), 'dd')
// 结束时间
DATETRUNC(GETDATE(), 'dd')
RDS
// 开始时间
DATE_FORMAT(DATE_ADD(NOW(), INTERVAL - 1 DAY), '%Y-%m-%d 00:00:00')
// 结束时间
DATE_FORMAT(NOW(), '%Y-%m-%d 00:00:00')
最近一周
以计算时间 2022-09-20 17:48:10 为例,最近一周的业务开始时间应为 2022-09-12 00:00:00,业务结束时间应为 2022-09-19 00:00:00。
ODPS
// 开始时间
DATETRUNC(DATEADD(GETDATE(), - WEEKDAY(GETDATE()) - 7 , 'dd'), 'dd')
// 结束时间
DATETRUNC(DATEADD(GETDATE(), - WEEKDAY(GETDATE()), 'dd'), 'dd')
RDS
// 开始时间
DATE_FORMAT(ADDDATE(NOW(), - 7 - WEEKDAY(NOW())), '%Y-%m-%d 00:00:00')
// 结束时间
DATE_FORMAT(ADDDATE(NOW(), - WEEKDAY(NOW())), '%Y-%m-%d 00:00:00')
最近一月
以计算时间 2022-09-20 17:57:05 为例,最近一月的业务开始时间应为 2022-08-01 00:00:00,业务结束时间应为 2022-09-01 00:00:00。
ODPS
// 开始时间
DATETRUNC(DATEADD(GETDATE(), -1, 'mm'), 'mm')
// 结束时间
DATETRUNC(GETDATE(), 'mm')
RDS
// 开始时间
DATE_FORMAT(DATE_ADD(NOW(), INTERVAL - 1 MONTH), '%Y-%m-01 00:00:00')
// 结束时间
DATE_FORMAT(NOW(), '%Y-%m-01 00:00:00')
最近一年
以计算时间 2022-09-20 18:03:00 为例,最近一年的业务开始时间应为 2021-01-01 00:00:00,业务结束时间应为 2022-01-01 00:00:00。
ODPS
// 开始时间
DATETRUNC(DATEADD(GETDATE(), -1, 'yyyy'), 'yyyy')
// 结束时间
DATETRUNC(GETDATE(), 'yyyy')
RDS
// 开始时间
DATE_FORMAT(DATE_ADD(NOW(), INTERVAL - 1 YEAR), '%Y-01-01 00:00:00')
// 结束时间
DATE_FORMAT(NOW(), '%Y-01-01 00:00:00')
截止到今天
以计算时间 2022-09-20 18:12:31 为例,截止到今天的业务开始时间应为 2022-09-20 00:00:00,业务结束时间应为 2022-09-21 00:00:00。
ODPS
// 开始时间
DATETRUNC(GETDATE(), 'dd')
// 结束时间
DATETRUNC(DATEADD(GETDATE(), 1, 'dd'), 'dd')
RDS
// 开始时间
DATE_FORMAT(NOW(), '%Y-%m-%d 00:00:00')
// 结束时间
DATE_FORMAT(ADDDATE(NOW(), 1), '%Y-%m-%d 00:00:00')
截止到本周
以计算时间 2022-09-20 18:16:20 为例,截止到本周的业务开始时间应为 2022-09-19 00:00:00,业务结束时间应为 2022-09-26 00:00:00。
ODPS
// 开始时间
DATETRUNC(DATEADD(GETDATE(), - WEEKDAY(GETDATE()), 'dd'), 'dd')
// 结束时间
DATETRUNC(DATEADD(GETDATE(), 7 - WEEKDAY(GETDATE()), 'dd'), 'dd')
RDS
// 开始时间
DATE_FORMAT(ADDDATE(NOW(), - WEEKDAY(NOW())), '%Y-%m-%d 00:00:00')
// 结束时间
DATE_FORMAT(ADDDATE(NOW(), 7 - WEEKDAY(NOW())), '%Y-%m-%d 00:00:00')
截止到本月
以计算时间 2022-09-20 18:19:15 为例,截止到本月的业务开始时间为 2022-09-01 00:00:00,业务结束时间应为 2022-10-01 00:00:00。
ODPS
// 开始时间
DATETRUNC(GETDATE(), 'mm')
// 结束时间
DATETRUNC(DATEADD(GETDATE(), 1, 'mm'), 'mm')
RDS
// 开始时间
DATE_FORMAT(NOW(), '%Y-%m-01 00:00:00')
// 结束时间
DATE_FORMAT(ADDDATE(NOW(), INTERVAL 1 MONTH), '%Y-%m-01 00:00:00')
截止到今年
以计算时间 2022-09-20 18:21:09 为例,截止到今年的业务开始时间为 2022-01-01 00:00:00,业务结束时间应为 2023-01-01 00:00:00。
ODPS
// 开始时间
DATETRUNC(GETDATE(), 'yyyy')
// 结束时间
DATETRUNC(DATEADD(GETDATE(), 1, 'yyyy'), 'yyyy')
RDS
// 开始时间
DATE_FORMAT(NOW(), '%Y-01-01 00:00:00')
// 结束时间
DATE_FORMAT(ADDDATE(NOW(), INTERVAL 1 YEAR), '%Y-01-01 00:00:00')
时间粒度
五分钟
参考时间范围为最近五分钟的结束时间的计算方法。
小时
参考时间范围为最近一小时的结束时间的计算方法。
天
参考时间范围为最近一天的结束时间的计算方法。
周
参考时间范围为最近一周的结束时间的计算方法。
月
参考时间范围为最近一月的结束时间的计算方法。
年
参考时间范围为最近一年的结束时间的计算方法。
结语
时间范围和时间粒度的计算虽然不是什么技术难点,却是数据分析 SQL 语句中极其重要的组成部分。不同数据库之间的日期时间函数的支持程度差异较大,具体使用时很容易混淆,如果平时可以多记录多总结,则可以幅度提升开发效率。
SQL 时间范围和时间粒度的更多相关文章
- SQL Server时间粒度系列----第3节旬、月时间粒度详解
本文目录列表: 1.SQL Server旬时间粒度2.SQL Server月有关时间粒度 3.SQL Server函数重构 4.总结语 5.参考清单列表 SQL Server旬时间粒度 ...
- SQL Server时间粒度系列----第4节季、年时间粒度详解
本文目录列表: 1.SQL Server季时间粒度2.SQL Server年时间粒度 3.总结语 4.参考清单列表 SQL Serve季时间粒度 季时间粒度也即是季度时间粒度.一年每3 ...
- SQL Server时间粒度系列----第2节日期、周时间粒度详解
本文目录列表: 1.从MySQL提供的TO_DAYS和FROM_DAYS这对函数说起2.SQL Server日期时间粒度3.SQL Server周有关时间粒度 4.总结语 5.参考清单列表 从My ...
- SQL Server时间粒度系列----第5节小时、分钟时间粒度详解
本文目录列表: 1.SQL Server小时时间粒度2.SQL Server分钟时间粒度 3.总结语 4.参考清单列表 SQL Server小时时间粒度 这里说的时间粒度是指带有 ...
- SQL Server时间粒度系列
工作中经常遇到针对业务部门提出不同时间粒度(年.季度.月.周.日等等日期时间粒度,以下简称时间粒度)的数据统计汇总任务,也看到不少博友针对这方便的博文,结合SQL Server的日期时间函数和 ...
- SQL Server时间粒度系列----第9节时间粒度示例演示
本文目录列表: 1.准备测试数据 2.向测试数据表添加相关时间粒度字段列 3.基于日月季年统计汇总的演示 4.总结语 5.参考清单列表 准备测试数据 为了提供不同时间粒度示例的演示,就需要测试 ...
- SQL Server时间粒度系列----第7节日历数据表详解
本文目录列表: 1.时间粒度有关描述 2.时间维度有关功能函数3.日历数据表 4.日历数据表数据填充 5.总结语 6.参考清单列表 时间粒度有关描述 将该系列涉及到的时间粒度以及分钟以下的粒度 ...
- SQL Server时间粒度系列----第1节时间粒度概述
本文目录列表: 1.什么是时间粒度?2.SQL Server提供的时间粒度3.SQL Server时间粒度代码演示 4.SQL Server基准日期 5.总结语6.参考清单列表 什么是时间粒度 ...
- sql server日期时间转字符串
一.sql server日期时间函数Sql Server中的日期与时间函数 1. 当前系统日期.时间 select getdate() 2. dateadd 在向指定日期加上一段时间的基 ...
随机推荐
- Linux 安装Apche服务
用yum 进行在线安装apche服务 yum install -y httpd 我这边是centos7 需要开启一下端口: 1 firewall-cmd --zone=public --add-por ...
- NC16430 [NOIP2016]蚯蚓
NC16430 [NOIP2016]蚯蚓 题目 题目描述 本题中,我们将用符号 \(\lfloor c \rfloor\) 表示对 c 向下取整,例如:\(\lfloor 3.0 \rfloor = ...
- Day04 HTML标记
路径 ./ 同级目录 ./ 进入该目录名下 ../ 上一级目录 HTML标记 图片 <!-- 图片标记 src 图片的路径 width 设置图片宽度 height 设置图片高度 title 鼠标 ...
- SuperSocket 1.6 创建一个简易的报文长度在头部的Socket服务器
我们来做一个头为6位报文总长度,并且长度不包含长度域自身的例子.比如这样的Socket报文000006123456. 添加SuperSocket.Engine,直接使用Nuget搜索SuperSock ...
- ArrayList源码深度剖析,从最基本的扩容原理,到魔幻的迭代器和fast-fail机制,你想要的这都有!!!
ArrayList源码深度剖析 本篇文章主要跟大家分析一下ArrayList的源代码.阅读本文你首先得对ArrayList有一些基本的了解,至少使用过它.如果你对ArrayList的一些基本使用还不太 ...
- 流程控制语句break
break语句 用于结束循环结构,通常与分支结构if一起使用 即非正常循环,在中间循环的时候直接退出 注意break打断的是循环语句,不是if语句 注意while循环中一般需要有改变变量这个操作,否则 ...
- NFS配置-实现多服务器共享目录
NFS网络文件系统 为什么要用NFS? 前端所有的应用服务器接收到用户上传的图片.文件.视频,都会统一放到后端的存储上.共享存储的好处:方便数据的查找与取出,缺点:存储服务器压力大,坏了丢失全部数据. ...
- MySQL基本操作笔记
一.数值类型 1.常量(1)字符串常量 ASCII字符串常量占一个字节 例如:'Hello Word' Unicode字符串常量占两个字节 例如:N'Hello Word' mysql> sel ...
- Go语言基础三:基本数据类型和运算符
Go语言数据类型 与其他编程语言一样,Go语言提供了各种数据类型,可分为基本的数据类型和复杂的数据类型.基本的数据类型就是基本的构造块,例如字符串.数字和布尔值.复杂的数据类型是用户自己定义的结构,由 ...
- python 异常捕捉与异常处理
简介 在实际开发中,为了防止异常界面直接被用户看到,往往我们会采用捕捉异常的方式来进一步处理异常. 异常捕捉 如下代码由于下标越界会导致异常 data = range(10) print(data[1 ...