日期算术运算
1、加上或减去若干天、若干月或若干年
问题:你需要给某个日期加上或减去若干天、若干月或若干年。例如,你想根据员工 CLARK 的 HIREDATE,返回 6 个日期:CLARK 获聘前 5 天和后 5 天,CLARK 获聘前 5个月和后 5 个月,CLARK 获聘前 5 年和后 5 年。CLARK 获聘于 2006 年 6 月 9 日,因此你要返回如下结果集。
sql
HD_MINUS_5D HD_PLUS_5D HD_MINUS_5M HD_PLUS_5M HD_MINUS_5Y HD_PLUS_5Y
----------- ----------- ----------- ----------- ----------- -----------
04-JUN-2006 14-JUN-2006 09-JAN-2006 09-NOV-2006 09-JUN-2001 09-JUN-2001
12-NOV-2006 22-NOV-2006 17-JUN-2006 17-APR-2007 17-NOV-2001 17-NOV-2001
18-JAN-2007 28-JAN-2007 23-AUG-2006 23-JUN-2007 23-JAN-2002 23-JAN-2002
解决方案:
DB2:标准加法运算和减法运算可用于日期值,但在给某个日期加上或减去的值后面,必须指定其单位。
sql
select hiredate -5 day as hd_minus_5D,
hiredate +5 day as hd_plus_5D,
hiredate -5 month as hd_minus_5M,
hiredate +5 month as hd_plus_5M,
hiredate -5 year as hd_minus_5Y,
hiredate +5 year as hd_plus_5Y
from emp
where deptno = 10
Oracle:要加上或减去若干天,可以使用标准加法运算和减法运算;要加上或减去若干月或若干年,可以使用函数ADD_MONTHS。
sql
select hiredate-5 as hd_minus_5D,
hiredate+5 as hd_plus_5D,
add_months(hiredate,-5) as hd_minus_5M,
add_months(hiredate,5) as hd_plus_5M,
add_months(hiredate,-5*12) as hd_minus_5Y,
add_months(hiredate,5*12) as hd_plus_5Y
from emp
where deptno = 10
PostgreSQL:使用标准加法运算和减法运算以及关键字 INTERVAL 指定时间单位和数量。单位和数量必须放在单引号内。
sql
select hiredate - interval '5 day' as hd_minus_5D,
hiredate + interval '5 day' as hd_plus_5D,
hiredate - interval '5 month' as hd_minus_5M,
hiredate + interval '5 month' as hd_plus_5M,
hiredate - interval '5 year' as hd_minus_5Y,
hiredate + interval '5 year' as hd_plus_5Y
from emp
where deptno=10;
hd_minus_5d | hd_plus_5d | hd_minus_5m | hd_plus_5m | hd_minus_5y | hd_plus_5y
---------------------+---------------------+---------------------+---------------------+---------------------+---------------------
2007-01-18 00:00:00 | 2007-01-28 00:00:00 | 2006-08-23 00:00:00 | 2007-06-23 00:00:00 | 2002-01-23 00:00:00 | 2012-01-23 00:00:00
2006-11-12 00:00:00 | 2006-11-22 00:00:00 | 2006-06-17 00:00:00 | 2007-04-17 00:00:00 | 2001-11-17 00:00:00 | 2011-11-17 00:00:00
2006-06-04 00:00:00 | 2006-06-14 00:00:00 | 2006-01-09 00:00:00 | 2006-11-09 00:00:00 | 2001-06-09 00:00:00 | 2011-06-09 00:00:00
(3 rows)
MySQL:使用标准加法运算和减法运算以及关键字 INTERVAL 指定时间单位和数量。与 PostgreSQL 解决方案不同,单位和数量无须放在单引号内。
sql
select hiredate - interval 5 day as hd_minus_5D,
hiredate + interval 5 day as hd_plus_5D,
hiredate - interval 5 month as hd_minus_5M,
hiredate + interval 5 month as hd_plus_5M,
hiredate - interval 5 year as hd_minus_5Y,
hiredate + interval 5 year as hd_plus_5Y
from emp
where deptno=10;
也可以使用函数 DATE_ADD。
sql
select date_add(hiredate,interval -5 day) as hd_minus_5D,
date_add(hiredate,interval 5 day) as hd_plus_5D,
date_add(hiredate,interval -5 month) as hd_minus_5M,
date_add(hiredate,interval 5 month) as hd_plus_5M,
date_add(hiredate,interval -5 year) as hd_minus_5Y,
date_add(hiredate,interval 5 year) as hd_plus_5DY
from emp;
SQL Server:使用函数 DATEADD 给指定日期加上或减去指定单位和数量的时间。
sql
select dateadd(day,-5,hiredate) as hd_minus_5d,
dateadd(day,5,hiredate) as hd_plus_5d,
dateadd(month,-5,hiredate) as hd_minus_5m,
dateadd(month,5,hiredate) as hd_plus_5m,
dateadd(year,-5,hiredate) as hd_minus_5y,
dateadd(year,5,hiredate) as hd_plus_5y
from emp
where deptno = 10;
2、确定两个日期相差多少天
问题:你想找出两个日期相隔多长时间,并在表示结果时以天为单位。例如,你想找出员工 ALLEN 和 WARD 的HIREDATE 之间有多少天。
解决方案:
DB2:使用两个内嵌视图找出 ALLEN 和 WARD 的HIREDATE,再使用函数 DAYS 将这两个日期相减。
sql
select days(ward_hd) - days(allen_hd)
from (
select hiredate as ward_hd
from emp
where ename = 'WARD'
) x,
(
select hiredate as allen_hd
from emp
where ename = 'ALLEN'
) y;
Oracle 和 PostgreSQL:使用两个内嵌视图找出 ALLEN 和 WARD 的HIREDATE,再将这两个日期相减。
sql
select ward_hd - allen_hd
from (
select hiredate as ward_hd
from emp
where ename = 'WARD'
) x, (
select hiredate as allen_hd
from emp
where ename = 'ALLEN'
) y;
MySQL 和 SQL Server:使用函数 DATEDIFF 找出两个日期相差多少天。在MySQL 中,函数 DATEDIFF 接受两个参数(两个日期,你要找出它们相差多少天),并要求将较早的那个日期作为第一个参数(SQL Server 中与此相反)。在 SQLServer 中,函数 DATEDIFF 允许你指定要返回的值的单位(在本实例中,你希望返回相差的天数)。下面是SQL Server 的解决方案。
sql
select datediff(day,allen_hd,ward_hd)
from (
select hiredate as ward_hd
from emp
where ename = 'WARD'
) x, (
select hiredate as allen_hd
from emp
where ename = 'ALLEN'
) y;
3、确定两个日期之间有多少个工作日
问题:给定两个日期,你想找出它们之间有多少个工作日(包括这两个日期本身)。如果 1 月 10 日是星期一,1 月 11 日是星期二,那么这两个日期之间有两个工作日,因为这两个日期都是工作日。在本实例中,只要不是星期六或星期日,就认为是工作日。
解决方案:本解决方案旨在找出 BLAKE 和 JONES 的 HIREDATE之间有多少个工作日。为了确定两个日期之间的工作日数量,可以使用一张透视表。在这张表中,两个日期之间的每一天(包含这两个日期本身)都有对应的一行。这样,要计算工作日数,只需计算在返回的日期中,有多少个日期不是星期六或星期日。
如果要剔除节假日,那么可以创建一个HOLIDAYS 表,并在解决方案中使用谓词 NOT IN 将 HOLIDAYS 表中的日期排除在外。
DB2:使用透视表 T500 生成足够的行数,用于表示两个日期之间的每一天。再计算不是周末的天数。使用函数DAYNAME 来确定各个日期是星期几。
sql
select sum(case when dayname(jones_hd+t500.id day -1 day)
in ( 'Saturday','Sunday' )
then 0 else 1
end) as days
from (
select max(case when ename = 'BLAKE'
then hiredate
end) as blake_hd,
max(case when ename = 'JONES'
then hiredate
end) as jones_hd
from emp
where ename in ( 'BLAKE','JONES' )
) x,
t500
where t500.id <= blake_hd-jones_hd+1;
MySQL:使用透视表 T500 生成足够的行数,用于表示两个日期之间的每一天。再计算不是周末的天数。使用函数DATE_ADD 给起始日期加上若干天,以获得各个中间日期。使用函数 DATE_FORMAT 确定各个日期是星期几。
sql
select sum(case when date_format(
date_add(jones_hd,
interval t500.id-1 DAY),'%a')
in ( 'Sat','Sun' )
then 0 else 1
end) as days
from (
select max(case when ename = 'BLAKE'
then hiredate
end) as blake_hd,
max(case when ename = 'JONES'
then hiredate
end) as jones_hd
from emp
where ename in ( 'BLAKE','JONES' )
) x,
t500
where t500.id <= datediff(blake_hd,jones_hd)+1;
Oracle:使用透视表 T500 生成足够的行数,用于表示两个日期之间的每一天。再计算不是周末的天数。使用函数TO_CHAR 确定各个日期是星期几。
sql
select sum(case when to_char(jones_hd+t500.id-1,'DY')
in ( 'SAT','SUN' )
then 0 else 1
end) as days
from (
select max(case when ename = 'BLAKE'
then hiredate
end) as blake_hd,
max(case when ename = 'JONES'
then hiredate
end) as jones_hd
from emp
where ename in ( 'BLAKE','JONES' )
) x,
t500
where t500.id <= blake_hd-jones_hd+1;
PostgreSQL:使用透视表 T500 生成足够的行数,用于表示两个日期之间的每一天。再计算不是周末的天数。使用函数TO_CHAR 确定各个日期是星期几。
sql
select sum(case when trim(to_char(jones_hd+t500.id-1,'DAY'))
in ( 'SATURDAY','SUNDAY' )
then 0 else 1
end) as days
from (
select max(case when ename = 'BLAKE'
then hiredate
end) as blake_hd,
max(case when ename = 'JONES'
then hiredate
end) as jones_hd
from emp
where ename in ( 'BLAKE','JONES' )
) x,
t500
where t500.id <= blake_hd-jones_hd+1;
SQL Server:使用透视表 T500 生成足够的行数,用于表示两个日期之间的每一天。再计算不是周末的天数。使用函数DATENAME 确定各个日期是星期几。
sql
select sum(case when datename(dw,jones_hd+t500.id-1)
in ( 'SATURDAY','SUNDAY' )
then 0 else 1
end) as days
from (
selectmax(case when ename = 'BLAKE'
then hiredate
end) as blake_hd,
max(case when ename = 'JONES'
then hiredate
end) as jones_hd
from emp
where ename in ( 'BLAKE','JONES' )
) x,
t500
where t500.id <= datediff(day,jones_hd-blake_hd)+1;
4、确定两个日期相隔多少个月或多少年
问题:你想确定两个日期相隔多少个月或多少年。例如,你想找出第一位员工和最后一位员工获聘的日期相隔多少个月,还想确定这段时间相当于多少年。
解决方案:由于一年是 12 个月,因此找出两个日期相隔多少个月后,只要再除以 12 就可以得到相隔多少年。熟悉本解决方案后,你可能想根据需要对相差的年数进行舍入处理。
例如,在 EMP 表中,第一个 HIREDATE 为 1980 年 12月 17 日,最后一个 HIREDATE 为 1983 年 1 月 12 日。如果只考虑年份,那么结果将为 3(1983 -- 1980)年,但相差的月数大约为 25(两年多一点儿)。应该根据需要调整解决方案。下面的解决方案都返回 25 个月和大约两年。
DB2 和 MySQL:使用函数 YEAR 和 MONTH 返回指定日期的 4 位年份和 2位月份。
sql
select mnth, mnth/12
from (
select (year(max_hd) - year(min_hd))*12 + (month(max_hd) - month(min_hd)) as mnth
from (
select min(hiredate) as min_hd, max(hiredate) as max_hd
from emp
) x
) y;
Oracle:使用函数 MONTHS_BETWEEN 确定两个日期相隔多少个月(要确定相隔多少年,只需再除以 12)。
sql
select months_between(max_hd,min_hd),
months_between(max_hd,min_hd)/12
from (
select min(hiredate) min_hd, max(hiredate) max_hd
from emp
) x;
PostgreSQL:使用函数 EXTRACT 返回指定日期的 4 位年份和 2 位月份。
sql
SELECT mnth,
mnth / 12 AS years
FROM (
SELECT (EXTRACT(YEAR FROM max_hd) - EXTRACT(YEAR FROM min_hd)) * 12
+ (EXTRACT(MONTH FROM max_hd) - EXTRACT(MONTH FROM min_hd)) AS mnth
FROM (
SELECT MIN(hiredate) AS min_hd,
MAX(hiredate) AS max_hd
FROM emp
) x
) y;
SQL Server:使用函数 DATEDIFF 确定两个日期相隔多长时间,并使用参数 datepart 将时间单位指定为月或年。
sql
select datediff(month,min_hd,max_hd),
datediff(year,min_hd,max_hd)
from (
select min(hiredate) min_hd, max(hiredate) max_hd
from emp
) x;
5、确定两个日期相隔多少秒、多少分钟或多少小时
问题:你想返回两个日期相隔的秒数。例如,你想返回 ALLEN和 WARD 的 HIREDATE 相隔的秒数、分钟数和小时数。
解决方案:只要能确定两个日期相隔多少天,就能确定它们相隔多少秒、多少分钟和多少小时,因为它们只是不同的时间单位而异。
DB2:使用函数 DAYS 确定 ALLEN_HD 和 WARD_HD 相隔多少天,再使用乘法运算计算小时数、分钟数和秒数。
sql
select dy*24 hr, dy*24*60 min, dy*24*60*60 sec
from (
select ( days(max(case when ename = 'WARD'
then hiredate
end)) -
days(max(case when ename = 'ALLEN'
then hiredate
end))
) as dy
from emp
) x;
MySQL:使用函数 DATEDIFF 返回 ALLEN_HD 和 WARD_HD 相隔多少天,再使用乘法运算计算小时数、分钟数和秒数。
sql
select datediff(day,allen_hd,ward_hd)*24 hr,
datediff(day,allen_hd,ward_hd)*24*60 min,
datediff(day,allen_hd,ward_hd)*24*60*60 sec
from (
select max(case when ename = 'WARD'
then hiredate
end) as ward_hd,
max(case when ename = 'ALLEN'
then hiredate
end) as allen_hd
from emp
) x;
SQL Server:使用函数 DATEDIFF 返回 ALLEN_HD 和 WARD_HD 相隔多少天,再使用参数 DATEPART 指定时间单位。
sql
select datediff(day,allen_hd,ward_hd,hour) as hr,
datediff(day,allen_hd,ward_hd,minute) as min,
datediff(day,allen_hd,ward_hd,second) as sec
from (
select max(case when ename = 'WARD'
then hiredate
end) as ward_hd,
max(case when ename = 'ALLEN'
then hiredate
end) as allen_hd
from emp
) x;
Oracle 和 PostgreSQL:使用减法运算返回 ALLEN_HD 和 WARD_HD 相隔多少天,再使用乘法运算计算小时数、分钟数和秒数。
sql
select dy*24 as hr, dy*24*60 as min, dy*24*60*60 as sec
from (
select (max(case when ename = 'WARD'
then hiredate
end) -
max(case when ename = 'ALLEN'
then hiredate
end)) as dy
from emp
) x;
hr | min | sec
----+------+--------
48 | 2880 | 172800
(1 row)
6、计算一年中有多少个工作日
问题:你想计算一年中有多少个工作日(同 8.3 节,在本实例中,只要不是星期六或星期日,就认为是工作日)。
解决方案:为了确定一年中有多少个工作日,必须做到以下几点。
- 生成这一年内所有的日期。
- 设置这些日期的格式,确定它们是星期几。
- 计算有多少个工作日。
DB2:使用递归式 WITH,以免使用 SELECT 来查询一张至少包含 366 行的表。使用函数 DAYNAME 确定各个日期是星期几,然后计算每个工作日出现的次数。
sql
with x (start_date,end_date)
as (
select start_date,
start_date + 1 year end_date
from (
select (current_date
dayofyear(current_date) day)
+1 day as start_date
from t1
) tmp
union all
select start_date + 1 day, end_date
from x
where start_date + 1 day < end_date
)
select dayname(start_date),count(*)
from x
group by dayname(start_date);
MySQL:查询 T500 表以生成足够的行,从而返回一年中的每一天。使用函数 DATE_FORMAT 确定各个日期都是星期几,然后计算工作日出现的次数。
sql
select date_format(
date_add(
cast(concat(year(current_date),'-01-01') as date),
interval t500.id-1 day),'%W') day,
count(*)
from t500
where t500.id <= datediff(
cast(concat(year(current_date)+1,'-01-01') as date),
cast(concat(year(current_date),'-01-01') as date))
group by date_format(
date_add(
cast(concat(year(current_date),'-01-01') as date),
interval t500.id-1 day),'%W');

Oracle:可以使用递归式 CONNECT BY 返回一年中的每一天。
sql
with x as (
select level lvl
from dual
connect by level <= (
add_months(trunc(sysdate,'y'),12)-trunc(sysdate,'y')
)
)
select to_char(trunc(sysdate,'y')+lvl-1,'DAY'), count(*)
from x
group by to_char(trunc(sysdate,'y')+lvl-1,'DAY');
PostgreSQL:首先使用内置函数 GENERATE_SERIES 为一年中的每一天都生成一行数据,然后使用函数 TO_CHAR 确定各个日期都是星期几,最后计算工作日出现的次数。
sql
select to_char(
cast( date_trunc('year',current_date) as date) + gs.id-1,'DAY'),
count(*)
from generate_series(1,366) gs(id)
where gs.id <= (
cast(date_trunc('year',current_date) +interval '12 month' as date) -
cast(date_trunc('year',current_date) as date))
group by to_char(
cast(date_trunc('year',current_date)as date) + gs.id-1,'DAY');
to_char | count
-----------+-------
MONDAY | 52
TUESDAY | 52
SUNDAY | 52
WEDNESDAY | 52
THURSDAY | 53
FRIDAY | 52
SATURDAY | 52
(7 rows)
SQL Server:使用递归式 WITH,以免使用 SELECT 来查询一个至少包含 366 行的表。使用函数 DATENAME 确定各个日期都是星期几,然后计算工作日出现的次数。
sql
with x (start_date,end_date)
as (
select start_date,
dateadd(year,1,start_date) end_date
from (
select cast(
cast(year(getdate()) as varchar) + '-01-01'
as datetime) start_date
from t1
) tmp
union all
select dateadd(day,1,start_date), end_date
from x
where dateadd(day,1,start_date) < end_date
)
select datename(dw,start_date),count(*)
from x
group by datename(dw,start_date)
OPTION (MAXRECURSION 366);
7、确定当前记录和下一条记录存储的日期相隔多少天
问题:你想确定两个日期(准确地说是存储在两行中的两个日期)相差多少天。例如,对于 10 号部门的每一位员工,你想确定从其获聘起到下一位员工(可能是另一个部门的)获聘过了多少天。
解决方案:要解决这个问题,诀窍是找出当前员工的 HIREDATE 后最早的 HIREDATE,然后,使用 2 节介绍的技巧确定相隔的天数即可。
DB2:使用标量子查询找出当前 HIREDATE 后的第一个HIREDATE,然后使用函数 DAYS 计算这两个日期相差的天数。
sql
select x.*,
days(x.next_hd) - days(x.hiredate) diff
from (
select e.deptno, e.ename, e.hiredate,
lead(hiredate)over(order by hiredate) next_hd
from emp e
where e.deptno = 10
) x;
MySQL 和 SQL Server:使用函数 LEAD 访问下一条记录。下面演示了在 SQLServer 解决方案中如何使用函数 DATEDIFF。
sql
select x.ename, x.hiredate, x.next_hd,
datediff(x.hiredate,x.next_hd,day) as diff
from (
select deptno, ename, hiredate,
lead(hiredate)over(order by hiredate) as next_hd
from emp e
) x
where e.deptno=10;
Oracle: 使用窗口函数 LEAD OVER 访问按照 HIREDATE 排序时位于当前行后面一行的 HIREDATE,然后执行减法运算。
sql
select ename, hiredate, next_hd,
next_hd - hiredate diff
from (
select deptno, ename, hiredate,
lead(hiredate)over(order by hiredate) next_hd
from emp
)
where deptno=10;
PostgreSQL:使用标量子查询找出当前 HIREDATE 后的第一个HIREDATE,然后使用减法运算找出相隔的天数。
sql
select x.*,
x.next_hd - x.hiredate as diff
from (
select e.deptno, e.ename, e.hiredate,
lead(hiredate)over(order by hiredate) as next_hd
from emp e
where e.deptno = 10
) x;