SQL 生成日历,你想为当前月份生成一个日历。日历的格式应该像我们桌面上摆放的台历那样横向有 7 列,(通常)纵向有 5 行。
SQL 生成日历 问题描述
你想为当前月份生成一个日历。日历的格式应该像我们桌面上摆放的台历那样横向有 7 列,(通常)纵向有 5 行。
SQL 生成日历 解决方案
下述每种解决方案都略有不同之处,但它们解决问题的思路是相同的。列出当前月份的每一天,然后根据每一天是星期几确定输出顺序以生成日历。
日历有许多种不同的格式。例如,Unix 下的 cal
命令输出的格式是从星期日到星期六。本实例基于 ISO 标准,按照星期一到星期五的顺序生成日历。掌握了本实例提供的解决方案之后,就能根据自己的喜好轻松地重新安排日历的格式了。
如果我们试图使用 SQL 完成各种格式化操作以便让输出结果更具可读性的话,那么查询语句也会变得更长。不要被这些长长的查询吓住;如果把本实例里的各种查询分解开,并一段一段地执行的话,我们就会发现它们实际上已经足够简洁了。
DB2
使用 WITH
递归查询列出当前月份的每一天,然后使用 CASE
表达式和 MAX
函数根据每一天是星期几编排输出顺序。
Oracle
使用 CONNECT BY
递归查询列出当前月份的每一天,然后使用 CASE
表达式和 MAX
函数根据每一天是星期几编排输出顺序。
PostgreSQL
使用 GENERATE_SERIES
函数列出当前月份的每一天。然后使用 CASE
表达式和 MAX
函数根据每一天是星期几编排输出顺序。
MySQL
使用 T500
列出当前月份的每一天,然后使用 CASE
表达式和 MAX
函数根据每一天是星期几编排输出顺序。
SQL Server
使用 WITH
递归查询列出当前月份的每一天,然后使用 CASE
表达式和 MAX
函数根据每一天是星期几编排输出顺序。
SQL 生成日历 讨论
DB2
首先要列出当前月份的每一天。这要用到 WITH
递归查询(如果你使用的 DB2 版本不支持 WITH
,则不妨借助类似 T500
这样的数据透视表,具体参考 MySQL 的解决方案)。除了当前月份的每一天(别名 DM
),我们还要提取出该日期的不同组成部分:它是星期几(别名 DW
),当前的月份(别名 MTH
),以及符合 ISO 标准的周序号(别名 WK
)。实际的递归操作发生之前(UNION ALL
之前的部分)的递归视图 X
的查询结果如下所示。
接下来就要不断地递增 DM
值(在当前月份里向前推进),直至到达月末。因为我们会遍历当前月份中的每一天,也就能逐一获得每一天是星期几,以及相应的符合 ISO 标准的周序号。部分查询结果显示如下所示。
到目前为止,得到的结果包括:当前月份的每一天,两位数字表示的日期,两位数字表示的月份,一位数字表示的星期几(1 ~ 7 分别代表从星期日到星期六的每一天),以及两位数字表示的、符合 ISO 标准的周序号。有了这些信息,我们就能使用 CASE
表达式来决定每一个 DM
值(当前月份的每一天)会落到一周的哪一天。部分查询结果如下所示。
在上面的部分输出结果里,可以看到一周里的每一天都作为单独的一行被返回。现在需要以周为单位为数据分组,并把同一周的七天合并为一行。调用聚合函数 MAX
,并按照 WK
(符合 ISO 标准的周序号)分组,这样就能把一周七天合并到一行里。为了合理地安排输出格式并保证按日期顺序输出,还要根据 WK
对结果做排序。最终结果如下所示。
Oracle
首先使用 CONNECT BY
递归查询生成当前月份的每一天。如果手边没有 Oracle 9i 或者更高版本的数据库,我们就无法按这种方式使用 CONNECT BY
。如此一来,则需要借助 MySQL 解决方案里出现过的 T500
这样的数据透视表。
除了当前月份的每一天之外,我们还需要其他信息:当前月份每一天的日期部分(别名 DM
),每一天分别是星期几(别名 DW
),当前的月份(别名 MTH
),以及符合 ISO 标准的周序号(别名 WK
)。WITH
视图 X
里与当前月份第一天相关的查询结果如下所示。
接下来就要不断地递增 DM
值(在当前月份里向前推进),直至到达月末。因为我们会遍历当前月份的每一天,也就能逐一获得每一天是星期几,以及相应的符合 ISO 标准的周序号。部分结果如下所示(为了增加可读性,额外添加了每一天的日期)。
到目前为止,当前月份的每一天都作为单独的一行被返回。每一行包括:两位数字表示的日期,两位数字表示的月份,一位数字表示的星期几(1 ~ 7 分别代表从星期日到星期六的每一天),以及两位数字表示的、符合 ISO 标准的周序号。有了这些信息,我们就能使用 CASE
表达式来决定每一个 DM
值(当前月份的每一天)会落到一周的哪一天。部分查询结果如下所示。
在上面的部分输出结果里,可以看到一周里的每一天都作为单独的一行被返回,而日期则被放置于 7 列中与 DW
值相对应的那一列。我们需要把一周七天都归并到一行里去。调用聚合函数 MAX
,并按照 WK
(符合 ISO 标准的周序号)分组,这样就能把一周七天合并到一行了。为了保证按日期顺序输出,还要根据 WK
对结果排序,最终结果如下所示。
PostgreSQL
使用 GENERATE_SERIES
函数把当前月份的每一天都当作单独的一行返回。如果你使用的 PostgreSQL 版本不支持 GENERATE_SERIES
,不妨参考 MySQL 解决方案,改为借助一个数据透视表来实现同样的功能。
对于当前月份的每一天,分别提取出下列信息:当前月份每一天的日期部分(别名 DM
),每一天分别是星期几(别名 DW
),当前的月份(别名 MTH
),以及符合 ISO 标准的周序号(别名 WK
)。尽管格式化和显式类型转换相关的代码大大降低了本解决方案的可读性,但整个查询其实并不复杂。内嵌视图 X
的部分查询结果如下所示。
注意,当遍历当前月份的每一天时,我们同时能知道每一天是星期几,以及符合 ISO 标准的周序号。为了保证遍历的范围不超出当前月份,我们按照条件 CURR_MTH = MTH
对返回的日期做了过滤(每个日期所对应的月份应该是当前月份)。到目前为止,得到的结果包括:两位数字表示的日期,两位数字表示的月份,一位数字表示的星期几(1 ~ 7 分别代表从星期日到星期六的每一天),以及两位数字表示的、符合 ISO 标准的周序号。下一步需要使用 CASE
表达式来决定每一个 DM
值(当前月份的每一天)会落到一周中的哪一天。部分查询结果如下所示。
在上面的部分输出结果里,可以看到一周里的每一天都被作为单独的一行被返回,而日期则被放置于 7 列中与 DW
值相对应的那一列。我们需要把一周七天都合并到一行中。因此接下来要调用聚合函数 MAX
,并按照 WK
(符合 ISO 标准的周序号)分组。这样一来,我们就能把一周七天合并到一行里去了,就像在真实的日历上看到的那样。为了保证按日期顺序输出,还要根据 WK
对结果做排序。最终结果如下所示。
MySQL
首先为当前月份的每一天生成单独的一行。为了实现此目的,需要使用 T500
表。在当前月份第一天的基础上依次加上 T500
表的每一个值,就能得到当前月份的每一天了。
对于每一个日期,需要提取出如下的信息:当前月份每一天的日期部分(别名 DM
),每一天分别是星期几(别名 DW
),当前的月份(别名 MTH
),以及符合 ISO 标准的周序号(别名 WK
)。内嵌视图 X
返回当前月份的第一天,以及两位数字表示的当前月份。结果如下所示。
下一步要遍历当前月份,从第一天开始,依次返回当前月份的每一天。注意,我们会遍历当前月份的每一天,并返回每个日期是星期几以及符合 ISO 标准的周序号。为了保证遍历操作不超出范围,只筛选出那些属于当前月份的日期(每一天对应的月份应该等于当前日期所属的月份)。内嵌视图 Y
的部分查询结果如下所示。
对于当前月份的每一天,现在知道了下列信息:两位数字表示的日期部分(DM
),一位数字表示的星期几(DW
),以及两位数字表示的、符合 ISO 标准的周序号(WK
)。有了这些信息之后,我们就能借助 CASE
表达式来决定每一个 DM
值(当前月份的每一天)会落到一周的哪一天。部分查询结果如下所示。
在上面的部分输出结果里,可以看到一周里的每一天都作为单独的一行被返回。每一行里,日期值都被放置于与 DW
值相对应的那一列。现在我们需要把一周七天都归并到一行里去。因此要调用聚合函数 MAX
,并按照 WK
(符合 ISO 标准的周序号)分组。为保证按日期顺序输出,还要根据 WK
对结果排序。最终结果如下所示。
SQL Server
首先把当前月份的每一天当作单独的一行返回,可以使用 WITH
递归查询来做到这一点。如果你使用的 SQL Server 版本不支持 WITH
递归查询,可以参考 MySQL 的解决方案,借助数据透视表达到同样的目的。对于每一行返回的值,需要获取如下信息:当前月份每一天的日期部分(别名 DM
),每一天分别是星期几(别名 DW
),当前的月份(别名 MTH
),以及符合 ISO 标准的周序号(别名 WK
)。实际的递归操作发生之前(UNION ALL
之前的部分)的递归视图 X
的查询结果如下所示。
接下来就要不断地递增 DM
值(在当前月份里向前推进),直至到达月末。因为我们会遍历当前月份的每一天,也就能逐一获取到每一天是星期几,以及相应的符合 ISO 标准的周序号。部分查询结果如下所示。
对于当前月份的每一天,现在得到的结果包括:两位数字表示的日期,两位数字表示的月份,一位数字表示的星期几(1 ~ 7 分别代表从星期日到星期六的每一天),以及两位数字表示的、符合 ISO 标准的周序号。
现在,我们就能使用 CASE
表达式来决定每一个 DM
值(当前月份的每一天)会落到一周的哪一天。部分查询结果如下所示。
每一天都作为单独的一行被返回。在每一行里,日期值被放置在与 DW
值相对应的那一列。因此,我们需要把一周七天都归并到一行里去。为达到此目的,针对行数据按照 WK
(符合 ISO 标准的周序号)分组,并针对不同的列执行 MAX
函数。查询结果将会以日历的形式输出,如下所示。