SQL 计算两个日期之间的工作日天数

SQL 计算两个日期之间的工作日天数,给定两个日期,你想知道两者之间有多少个工作日,并且两个日期自身也要计算进去。例如,如果 1 月 10 日是星期一,1 月 11 日是星期二,则两者之间的工作日个数是 2,这是因为两个日期都是工作日。对于本实例而言,“工作日”定义为除了星期六和星期日以外的日子。

SQL 计算两个日期之间的工作日天数 问题描述

给定两个日期,你想知道两者之间有多少个工作日,并且两个日期自身也要计算进去。例如,如果 1 月 10 日是星期一,1 月 11 日是星期二,则两者之间的工作日个数是 2,这是因为两个日期都是工作日。对于本实例而言,“工作日”定义为除了星期六和星期日以外的日子。

SQL 计算两个日期之间的工作日天数 解决方案

下面的解决方案以找出 BLAKE 和 JONES 的 HIREDATE 之间有多少个工作日为例。为了计算两个日期之间的工作日天数,我们可以使用数据透视表,把两个日期之间的每一天(包括开始和结束日期)都作为单独的一行返回。这样的话,统计工作日天数就变成了计算有多少个既不是星期六又不是星期日的日期。

如果想要把假日也排除在外,就需要创建一个 `HOLIDAYS` 表。然后在本解决方案基础上,使用 `NOT IN` 排除掉 `HOLIDAYS` 表里列出的日期。

DB2
使用数据透视表 T500 生成所需要的行数(两个日期之间的天数)的结果集。然后统计非周末日期的个数。使用 DAYNAME 函数能够知道一个日期是星期几。例如:

 1  select sum(case when dayname(jones_hd+t500.id day -1 day)
 2                    in ( 'Saturday','Sunday' )
 3                  then 0 else 1
 4             end) as days
 5    from (
 6  select max(case when ename = 'BLAKE'
 7                  then hiredate
 8             end) as blake_hd,
 9         max(case when ename = 'JONES'
10                  then hiredate
11             end) as jones_hd
12    from emp
13   where ename in ( 'BLAKE','JONES' )
14          ) x,
15          t500
16   where t500.id <= blake_hd-jones_hd+1

MySQL
使用数据透视表 T500 生成所需要的行数(两个日期之间的天数)的结果集,然后统计非周末日期的个数。使用 DATE_ADD 函数为每一个日期加上若干天。使用 DATE_FORMAT 函数能够知道一个日期是星期几。

 1  select sum(case when date_format(
 2                          date_add(jones_hd,
 3                                   interval t500.id-1 DAY),'%a')
 4                    in ( 'Sat','Sun' )
 5                  then 0 else 1
 6             end) as days
 7    from (
 8  select max(case when ename = 'BLAKE'
 9                  then hiredate
10             end) as blake_hd,
11          max(case when ename = 'JONES'
12                   then hiredate
13              end) as jones_hd
14    from emp
15   where ename in ( 'BLAKE','JONES' )
16         ) x,
17         t500
18   where t500.id <= datediff(blake_hd,jones_hd)+1

Oracle
使用数据透视表 T500 生成所需要的行数(两个日期之间的天数)的结果集,然后统计非周末日期的个数。使用 TO_CHAR 函数能够知道一个日期是星期几。

 1  select sum(case when to_char(jones_hd+t500.id-1,'DY')
 2                    in ( 'SAT','SUN' )
 3                  then 0 else 1
 4             end) as days
 5    from (
 6  select max(case when ename = 'BLAKE'
 7                  then hiredate
 8             end) as blake_hd,
 9         max(case when ename = 'JONES'
10                  then hiredate
11             end) as jones_hd
12    from emp
13   where ename in ( 'BLAKE','JONES' )
14         ) x,
15         t500
16   where t500.id <= blake_hd-jones_hd+1

PostgreSQL
使用数据透视表 T500 生成所需要的行数(两个日期之间的天数)的结果集,然后统计非周末日期的个数。使用 TO_CHAR 函数能够知道一个日期是星期几。

 1  select sum(case when trim(to_char(jones_hd+t500.id-1,'DAY'))
 2                    in ( 'SATURDAY','SUNDAY' )
 3                  then 0 else 1
 4             end) as days
 5    from (
 6  select max(case when ename = 'BLAKE'
 7                  then hiredate
 8             end) as blake_hd,
 9         max(case when ename = 'JONES'
10                  then hiredate
11             end) as jones_hd
12    from emp
13   where ename in ( 'BLAKE','JONES' )
14         ) x,
15         t500
16   where t500.id <= blake_hd-jones_hd+1

SQL Server
使用数据透视表 T500 生成所需要的行数(两个日期之间的天数)的结果集,然后统计非周末日期的个数。使用 DATENAME 函数能够知道一个日期是星期几。

 1  select sum(case when datename(dw,jones_hd+t500.id-1)
 2                    in ( 'SATURDAY','SUNDAY' )
 3                   then 0 else 1
 4             end) as days
 5    from (
 6  select max(case when ename = 'BLAKE'
 7                  then hiredate
 8             end) as blake_hd,
 9         max(case when ename = 'JONES'
10                  then hiredate
11             end) as jones_hd
12    from emp
13   where ename in ( 'BLAKE','JONES' )
14         ) x,
15         t500
16   where t500.id <= datediff(day,jones_hd-blake_hd)+1

SQL 计算两个日期之间的工作日天数 扩展知识

尽管不同数据库要调用不同的内置函数来确认一个日期是星期几,但上述解决方案的思路都是相同的。具体做法分为两步:
(1) 计算出开始日期和结束日期之间相隔多少天(包含开始日期和结束日期);
(2) 排除掉周末,统计有多少个工作日(实际是在计算有多少条记录)。
内嵌视图 X 负责完成第一步工作。仔细查看内嵌视图 X 的话,我们会注意到它使用了聚合函数 MAX,其目的在于排除掉 Null。如果不了解 MAX 的用处,下面的查询结果输出能帮我们加深理解。下面的输出内容展示了没有 MAX 函数的内嵌视图 X 的查询结果。

select case when ename = 'BLAKE'
            then hiredate
       end as blake_hd,
       case when ename = 'JONES'
            then hiredate
       end as jones_hd
  from emp
 where ename in ( 'BLAKE','JONES' )
 
BLAKE_HD    JONES_HD
----------- -----------
            02-APR-1981
01-MAY-1981

如果不调用 MAX 函数,会有两行查询结果。有了 MAX 函数,查询结果就是一行,Null 会被过滤掉。

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' )
 
BLAKE_HD    JONES_HD
----------- -----------
01-MAY-1981 02-APR-1981

两个日期之间相差 30 天(包含开始日期和结束日期)。现在,我们在同一行中得到了两个日期,下一步就是为这 30 天里的每一天单独生成一行记录。为了得到 30 天(行)的记录,这里用到了 T500 表。T500 表的 ID 列每一个值都等于前面一行的值加上 1,在两个日期中较早的那个(JONES_HD)的基础上依次加上 T500 表对应的值,这样就生成了 JONES_HDBLAKE_HD 之间的连续日期序列。结果如下所示(使用 Oracle 语法)。

select x.*, t500.*, jones_hd+t500.id-1
  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
 
BLAKE_HD    JONES_HD            ID JONES_HD+T5
----------- ----------- ---------- -----------
01-MAY-1981 02-APR-1981          1 02-APR-1981
01-MAY-1981 02-APR-1981          2 03-APR-1981
01-MAY-1981 02-APR-1981          3 04-APR-1981
01-MAY-1981 02-APR-1981          4 05-APR-1981
01-MAY-1981 02-APR-1981          5 06-APR-1981
01-MAY-1981 02-APR-1981          6 07-APR-1981
01-MAY-1981 02-APR-1981          7 08-APR-1981
01-MAY-1981 02-APR-1981          8 09-APR-1981
01-MAY-1981 02-APR-1981          9 10-APR-1981
01-MAY-1981 02-APR-1981         10 11-APR-1981
01-MAY-1981 02-APR-1981         11 12-APR-1981
01-MAY-1981 02-APR-1981         12 13-APR-1981
01-MAY-1981 02-APR-1981         13 14-APR-1981
01-MAY-1981 02-APR-1981         14 15-APR-1981
01-MAY-1981 02-APR-1981         15 16-APR-1981
01-MAY-1981 02-APR-1981         16 17-APR-1981
01-MAY-1981 02-APR-1981         17 18-APR-1981
01-MAY-1981 02-APR-1981         18 19-APR-1981
01-MAY-1981 02-APR-1981         19 20-APR-1981
01-MAY-1981 02-APR-1981         20 21-APR-1981
01-MAY-1981 02-APR-1981         21 22-APR-1981
01-MAY-1981 02-APR-1981         22 23-APR-1981
01-MAY-1981 02-APR-1981         23 24-APR-1981
01-MAY-1981 02-APR-1981         24 25-APR-1981
01-MAY-1981 02-APR-1981         25 26-APR-1981
01-MAY-1981 02-APR-1981         26 27-APR-1981
01-MAY-1981 02-APR-1981         27 28-APR-1981
01-MAY-1981 02-APR-1981         28 29-APR-1981
01-MAY-1981 02-APR-1981         29 30-APR-1981
01-MAY-1981 02-APR-1981         30 01-MAY-1981

如果仔细查看 WHERE 子句的话,我们会注意到 BLAKE_HDJONES_HD 相减后又加上了 1,这是为了生成所需的 30 行记录(否则就是 29 行)。我们也要注意到外层查询的 SELECT 列表里 T500.ID 减去了 1,这是因为 ID 列的起始值是 1,如果在 JONES_HD 基础上加上 1 就等同于从最终结果里排除掉了 JONES_HD
一旦生成了所需数目的行记录,接着使用 CASE 表达式来标记每一个日期是工作日或者周末(若是工作日返回 1,周末则返回 0)。最后使用聚合函数 SUM 来合计 1 的个数,并得到最终答案。

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程

SQL 实例