获取上下一个工作日实践

本文涉及的产品
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
云原生数据库 PolarDB PostgreSQL 版,企业版 4核16GB
推荐场景:
HTAP混合负载
云原生数据库 PolarDB MySQL 版,通用型 2核4GB 50GB
简介: 获取上下一个工作日实践

获取上下一个工作日实践

前言

其实这个文章个人之前有进行过发布和讨论,在上一篇文章中,介绍了如何通过postgresql数据库的sql语句构建一个工作日的表,并且介绍如何使用sql语法获取某一天往前或者往后的工作日或者自然日,但是实际阅读之后发现缺少了很多细节,故这里重新梳理一下整个过程,希望可以给读者一个参考。

本次实践只是个人提供的一个工作日获取的解决方案,如果有更好的解决方案欢迎讨论和分享。

上一篇文章链接:juejin.cn/post/702300…

注意使用的数据库为:PostgreSql

前置准备

在介绍具体的编码和处理逻辑之前,我们需要准备表结构和相关的数据。

表设计

首先这里依然先回顾一下这个工作日表获取的表结构:

-- ----------------------------
-- Table structure for sa_calendar_table
-- ----------------------------
DROP TABLE IF EXISTS "public"."sa_calendar_table";
CREATE TABLE "public"."sa_calendar_table" (
  "calendar_id" varchar(255) COLLATE "pg_catalog"."default" NOT NULL,
  "calendar_year" varchar(10) COLLATE "pg_catalog"."default",
  "calendar_month" varchar(10) COLLATE "pg_catalog"."default",
  "calendar_date" varchar(10) COLLATE "pg_catalog"."default",
  "day_of_week" varchar(10) COLLATE "pg_catalog"."default",
  "day_of_month" varchar(10) COLLATE "pg_catalog"."default",
  "week_of_year" varchar(10) COLLATE "pg_catalog"."default",
  "month_of_year" varchar(10) COLLATE "pg_catalog"."default",
  "quarter_of_year" varchar(10) COLLATE "pg_catalog"."default",
  "is_end_month" varchar(10) COLLATE "pg_catalog"."default",
  "is_end_quarter" varchar(10) COLLATE "pg_catalog"."default",
  "is_end_halfayear" varchar(10) COLLATE "pg_catalog"."default",
  "is_end_year" varchar(10) COLLATE "pg_catalog"."default",
  "operator_id" varchar(50) COLLATE "pg_catalog"."default",
  "operator_name" varchar(50) COLLATE "pg_catalog"."default",
  "operate_date" timestamp(6),
  "res_attr1" varchar(40) COLLATE "pg_catalog"."default",
  "res_attr2" varchar(40) COLLATE "pg_catalog"."default",
  "res_attr3" varchar(40) COLLATE "pg_catalog"."default",
  "res_attr4" varchar(40) COLLATE "pg_catalog"."default",
  "is_work_day" varchar(1) COLLATE "pg_catalog"."default"
)
WITH (fillfactor=100)
;
ALTER TABLE "public"."sa_calendar_table" OWNER TO "postgres";
COMMENT ON COLUMN "public"."sa_calendar_table"."calendar_id" IS '主键';
COMMENT ON COLUMN "public"."sa_calendar_table"."calendar_year" IS '年';
COMMENT ON COLUMN "public"."sa_calendar_table"."calendar_month" IS '月';
COMMENT ON COLUMN "public"."sa_calendar_table"."calendar_date" IS '日';
COMMENT ON COLUMN "public"."sa_calendar_table"."day_of_week" IS '自然周的第几天';
COMMENT ON COLUMN "public"."sa_calendar_table"."day_of_month" IS '月的第几天';
COMMENT ON COLUMN "public"."sa_calendar_table"."week_of_year" IS '年的第几个自然周';
COMMENT ON COLUMN "public"."sa_calendar_table"."month_of_year" IS '年的第几月';
COMMENT ON COLUMN "public"."sa_calendar_table"."quarter_of_year" IS '年的第几季';
COMMENT ON COLUMN "public"."sa_calendar_table"."is_end_month" IS '是否月末';
COMMENT ON COLUMN "public"."sa_calendar_table"."is_end_quarter" IS '是否季末';
COMMENT ON COLUMN "public"."sa_calendar_table"."is_end_halfayear" IS '是否半年末';
COMMENT ON COLUMN "public"."sa_calendar_table"."is_end_year" IS '是否年末';
COMMENT ON COLUMN "public"."sa_calendar_table"."operator_id" IS '操作人ID';
COMMENT ON COLUMN "public"."sa_calendar_table"."operator_name" IS '操作人名称';
COMMENT ON COLUMN "public"."sa_calendar_table"."operate_date" IS '操作时间';
COMMENT ON COLUMN "public"."sa_calendar_table"."res_attr1" IS '预留字段1';
COMMENT ON COLUMN "public"."sa_calendar_table"."res_attr2" IS '预留字段2';
COMMENT ON COLUMN "public"."sa_calendar_table"."res_attr3" IS '预留字段3';
COMMENT ON COLUMN "public"."sa_calendar_table"."res_attr4" IS '预留字段4';
COMMENT ON COLUMN "public"."sa_calendar_table"."is_work_day" IS '是否为工作日,Y是,N否(即节假日)';
列名称 数据类型 描述 数据长度 不能为空
calendar_id varchar 主键 255 YES
calendar_year varchar 10 NO
calendar_month varchar 10 NO
calendar_date varchar 10 NO
day_of_week varchar 自然周的第几天 10 NO
day_of_month varchar 月的第几天 10 NO
week_of_year varchar 年的第几个自然周 10 NO
month_of_year varchar 年的第几月 10 NO
quarter_of_year varchar 年的第几季 10 NO
is_end_month varchar 是否月末 10 NO
is_end_quarter varchar 是否季末 10 NO
is_end_halfayear varchar 是否半年末 10 NO
is_end_year varchar 是否年末 10 NO
operator_id varchar 操作人ID 50 NO
operator_name varchar 操作人名称 50 NO
operate_date timestamp 操作时间 6 NO
res_attr1 varchar 预留字段1 40 NO
res_attr2 varchar 预留字段2 40 NO
res_attr3 varchar 预留字段3 40 NO
res_attr4 varchar 预留字段4 40 NO
is_work_day varchar 是否为工作日,Y是,N否(即节假日) 1 NO

另外这里再教大家一个技巧,如何使用postgresql获取某一个表的表结构:

Postgresql 获取某一个表的表结构:

SELECT A
  .attname AS COLUMN_NAME,
  T.typname AS data_type,
  d.description AS column_comment,
  btrim( SUBSTRING ( format_type ( A.atttypid, A.atttypmod ) FROM '\(.*\)' ), '()' ) AS character_maximum_length,
CASE
    WHEN A.attnotnull = 'f' THEN
    'NO' 
    WHEN A.attnotnull = 't' THEN
    'YES' ELSE'NO' 
END AS NULLABLE 
FROM
  pg_class C,
  pg_attribute A,
  pg_type T,
  pg_description d 
WHERE
  C.relname = '这里填表名' 
  AND A.attnum > 0 
  AND A.attrelid = C.oid 
  AND A.atttypid = T.oid 
  AND d.objoid = A.attrelid 
  AND d.objsubid = A.attnum

下面是语句的调用效果,注意上面的语句建议给所有的字段加上注释之后再执行。

image.png

填充数据

有了表结构还不够,这里我们还需要填充数据,我们使用如下的sql填充数据内容,sql语句可能略微复杂了些,另外执行过程中可能会出现缺失函数的情况,由于个人使用过程中没有碰到此问题,所以就跳过了:

INSERT INTO sa_calendar_table (
  calendar_id,
  calendar_year,
  calendar_month,
  calendar_date,
  day_of_week,
  day_of_month,
  week_of_year,
  month_of_year,
  quarter_of_year,
  is_end_month,
  is_end_quarter,
  is_end_halfayear,
  is_end_year,
  operator_id,
  operator_name,
  operate_date,
  res_attr1,
  res_attr2,
  res_attr3,
  res_attr4,
  is_work_day 
) SELECT A
.calendar_id,
A.calender_year,
A.calender_month,
A.calendar_date,
A.day_of_week,
A.day_of_month,
A.week_of_year,
A.month_of_year,
A.quarter_of_year,
A.is_end_month,
A.is_end_quarter,
A.is_end_halfayear,
A.is_end_year,
A.operator_id,
A.operator_name,
A.operator_date,
A.res_attr1,
A.res_attr2,
A.res_attr3,
A.res_attr4,
A.is_work_day 
FROM
  (
  SELECT
    gen_random_uuid ( ) AS calendar_id,
    to_char( tt.DAY, 'yyyy' ) AS calender_year,
    to_char( tt.DAY, 'yyyy-mm' ) AS calender_month,
    to_char( tt.DAY, 'yyyy-mm-dd' ) AS calendar_date,
    EXTRACT ( DOW FROM tt.DAY ) AS day_of_week,
    to_char( tt.DAY, 'dd' ) AS day_of_month,
    EXTRACT ( MONTH FROM tt.DAY ) AS month_of_year,
    EXTRACT ( WEEK FROM tt.DAY ) AS week_of_year,
    EXTRACT ( QUARTER FROM tt.DAY ) AS quarter_of_year,
  CASE
      WHEN tt.DAY = date_trunc( 'month', tt.DAY + INTERVAL '1 month' ) - INTERVAL '1 day' THEN
      'Y' ELSE'N' 
    END AS is_end_month,
  CASE
      WHEN tt.DAY = date_trunc( 'quarter', tt.DAY + INTERVAL '3 month' ) - INTERVAL '1 day' THEN
      'Y' ELSE'N' 
    END AS is_end_quarter,
  CASE
      WHEN tt.DAY = date_trunc( 'year', tt.DAY ) + INTERVAL '6 month' - INTERVAL '1 day' THEN
      'Y' ELSE'N' 
    END AS is_end_halfayear,
  CASE
      WHEN tt.DAY = date_trunc( 'year', tt.DAY ) + INTERVAL '12 month' - INTERVAL '1 day' THEN
      'Y' ELSE'N' 
    END AS is_end_year,
    'b8617d3d-d2c9-4a2a-93ba-5b2d8b700cb0' AS operator_id,
    'admin' AS operator_name,
    CAST ( CURRENT_DATE AS TIMESTAMP ) AS operator_date,
    NULL AS res_attr1,
    NULL AS res_attr2,
    NULL AS res_attr3,
    NULL AS res_attr4,
  CASE
      WHEN EXTRACT ( DOW FROM tt.DAY ) = 6 THEN
      'N' 
      WHEN EXTRACT ( DOW FROM tt.DAY ) = 0 THEN
      'N' ELSE'Y' 
    END AS is_work_day 
  FROM
    (
    SELECT
      generate_series (
        ( SELECT ( date_trunc( 'year', now( ) ) + INTERVAL '1 year' ) :: DATE AS next_year_first_date ),
        ( SELECT ( SELECT ( date_trunc( 'year', now( ) ) + INTERVAL '2 year' ) :: DATE - 1 AS last_year_last_date ) ),
        '1 d' 
      ) AS DAY 
    ) AS tt 
  ) AS A;

image.png

执行完成之后,可以看到插入了365天的数据,这里唯一需要改动的地方是:'1 year'2 year

实战部分

在上一篇文章中,只是简单介绍了一个应用场景,这里继续完善此案例的内容,下面来说一下应用的场景,其实需求也比较简单,但是也比较常见:

  • 获取某一天的上一个工作日或者下一个工作日,或者获取自然日

获取工作日sql

首先我们需要根据当前的天数获取某一天的工作日列表:

SELECT
    *
FROM
    (
        SELECT
            -ROW_NUMBER ( ) OVER ( ORDER BY T.calendar_date DESC ) AS addDay,
                T.calendar_date,
            T.is_work_day
        FROM
            sa_calendar_table T
        WHERE
            T.calendar_year in (#{nowYear}, #{prevYear})
          and T.calendar_date < CAST ( #{targetYyyyMMdd} AS VARCHAR )
        UNION
        SELECT ROW_NUMBER
                   ( ) OVER ( ORDER BY T.calendar_date )-1 AS addDay,
                T.calendar_date,
               T.is_work_day
        FROM
            sa_calendar_table T
        WHERE
            T.calendar_year in (#{nowYear}, #{prevYear})
          ANd T.calendar_date >= CAST ( #{targetYyyyMMdd} AS VARCHAR )
    ) mm
ORDER BY
    calendar_date

这里我们使用一个实际的案例看一下数据的形式:

SELECT
    *
FROM
    (
        SELECT
            -ROW_NUMBER ( ) OVER ( ORDER BY T.calendar_date DESC ) AS addDay,
                T.calendar_date,
            T.is_work_day
        FROM
            sa_calendar_table T
        WHERE
            T.calendar_year in  ('2020', '2021')
          and T.calendar_date < CAST ('2021-12-12' AS VARCHAR )
        UNION
        SELECT ROW_NUMBER
                   ( ) OVER ( ORDER BY T.calendar_date )-1 AS addDay,
                T.calendar_date,
               T.is_work_day
        FROM
            sa_calendar_table T
        WHERE
            T.calendar_year in ('2020', '2021')
          ANd T.calendar_date >= CAST ( '2021-12-12' AS VARCHAR )
    ) mm
ORDER BY
    calendar_date

image.png

看到这里,我相信大部分读者应该都知道这是干啥用的了,这里我们通过0获取到当天,如果是+1则是下一天,而如果是-1则是上一天,如果是工作日,则对于数据进行判断,,根据这样的规则,下面我们便可以使用代码来实现:

下面是获取下一天工作日的处理,获取下一天的代码如下:

private static final Pattern TD_DAY = Pattern.compile("^(T|D)\\+\\d$");
    private static final String WORK_DAY_CONFIG_T = "T";
    private static final String IS_WORK_DAY = "Y";
    private static final String IS_NOT_WORK_DAY = "N";
    private static final String WORK_DAY_CONFIG_D = "D";
public String findNextDayByCalendarList(CalendarDataProcessBo calendarDataProcessBo) {
        Objects.requireNonNull(calendarDataProcessBo, "当前业务传输对象不能为空");
        if (StrUtil.isAllNotBlank(new CharSequence[]{calendarDataProcessBo.getBankSettleCycle()}) && !CollectionUtil.isEmpty(calendarDataProcessBo.getCalendarDayDtos())) {
            // 额外需要往前推的天数
            int extDayOfWorkDayCount = calendarDataProcessBo.getExtDayOfWorkDayCount();
            // T+N 或者 D+N
            String bankSettleCycle = calendarDataProcessBo.getBankSettleCycle();
            // 上方截图对应的数据列表
            List<SaCalendarDayDto> calendarDayDtos = calendarDataProcessBo.getCalendarDayDtos();
            boolean matches = TD_DAY.matcher(bankSettleCycle).matches();
            // 校验正则的格式
            if (!matches) {
                logger.error("由于正则表达式{}不符合校验规则{}所以对账定时任务无法处理时间,定时任务运行失败", bankSettleCycle, TD_DAY);
                throw new UnsupportedOperationException(String.format("由于正则表达式%s不符合校验规则%s所以对账定时任务无法处理时间,定时任务运行失败", bankSettleCycle, TD_DAY));
            } else {
                String[] cycDay = bankSettleCycle.split("\\+");
                String tOrDday = cycDay[0];
                String addDay = cycDay[1];
                boolean matchWorkDayEnable;
                if (Objects.equals(tOrDday, "T")) {
                    matchWorkDayEnable = true;
                } else {
                    if (!Objects.equals(tOrDday, "D")) {
                        throw new UnsupportedOperationException("无法处理t+N或者d+N以外的数据");
                    }
                    matchWorkDayEnable = false;
                }
        // 如果需要获取工作日但是下一天不是工作日,则不断的+1往下获取
                for(int finDay = Integer.parseInt(addDay) + extDayOfWorkDayCount; finDay < CollectionUtil.size(calendarDayDtos); ++finDay) {
                    Optional<SaCalendarDayDto> first = calendarDayDtos.stream().filter((item) -> {
                        return Objects.equals(item.getAddDay(), String.valueOf(finDay));
                    }).findFirst();
                    if (!first.isPresent()) {
                        throw new UnsupportedOperationException("未发现任何工作日或者自然日数据");
                    }
                    SaCalendarDayDto saCalendarDayDto = (SaCalendarDayDto)first.get();
                    if (!matchWorkDayEnable || !Objects.equals(saCalendarDayDto.getIsWorkDay(), "N")) {
                        return saCalendarDayDto.getCalendarDate();
                    }
                }
                throw new UnsupportedOperationException("未发现任何工作日或者自然日数据");
            }
        } else {
            throw new IllegalArgumentException("传递参数有误,请确保所有参数均已传递");
        }
    }

这里其实还有别的写法,比如增加一个BOOLEAN变量判断是往前还是往后,但是个人并不喜欢在参数中控制方法的行为,这样很容易出问题。

写在最后

此工作日的实现方法比较笨拙也比较简单,如果有好的想法欢迎讨论。

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
SQL 关系型数据库 数据库
获取上下一个工作日实践
​ 其实这个文章个人之前有进行过发布和讨论,在上一篇文章中,介绍了如何通过postgresql数据库的sql语句构建一个工作日的表,并且介绍如何使用sql语法获取某一天往前或者往后的工作日或者自然日,但是实际阅读之后发现缺少了很多细节,故这里重新梳理一下整个过程,希望可以给读者一个参考。
62 0
|
程序员
758.每周复盘-第十周
758.每周复盘-第十周
|
程序员
766.每周复盘-第十一周
766.每周复盘-第十一周
|
程序员
773.每周复盘-第十二周
773.每周复盘-第十二周
|
缓存 程序员
780.【复盘】每周复盘-第十三周
780.【复盘】每周复盘-第十三周
阿里云备案初审时间工作日24小时内注意接听如果没接到怎么办?
阿里云备案初审时间要多久?24小时内注意接听电话
1916 0
|
测试技术 开发者
活动一等奖及8月份“性能测试 PTS”活动获奖名单
活动一等奖及8月份“性能测试 PTS”活动获奖名单
活动一等奖及8月份“性能测试 PTS”活动获奖名单
|
数据可视化 测试技术 开发工具
吭哧吭哧开发2周,老板突然说这个功能不要了。。。
吭哧吭哧开发2周,老板突然说这个功能不要了。。。
吭哧吭哧开发2周,老板突然说这个功能不要了。。。
|
机器学习/深度学习 人工智能 自然语言处理
从大年初一开始,他们在支付宝 7*24 小时工作
这个春节,在全民抗击新型肺炎期间,AI赋能的智能语音机器人走上前台,快速补充人力不足的影响,成为支付宝与客户沟通的主力,7*24小时不间断服务百姓,每日呼出电话服务时长覆盖了10%至60%原有人工外呼,并根据当前情况迅速扩展新的覆盖场景。
914 0
从大年初一开始,他们在支付宝 7*24 小时工作
|
前端开发
支付宝工程师如何在13个小时内上线“医护人员保障金”?
感谢医护人员的付出,我们要为他们保驾护航。
360 0
支付宝工程师如何在13个小时内上线“医护人员保障金”?

热门文章

最新文章

  • 1
    流量控制系统,用正则表达式提取汉字
    25
  • 2
    Redis09-----List类型,有序,元素可以重复,插入和删除快,查询速度一般,一般保存一些有顺序的数据,如朋友圈点赞列表,评论列表等,LPUSH user 1 2 3可以一个一个推
    26
  • 3
    Redis08命令-Hash类型,也叫散列,其中value是一个无序字典,类似于java的HashMap结构,Hash结构可以将对象中的每个字段独立存储,可以针对每字段做CRUD
    27
  • 4
    Redis07命令-String类型字符串,不管是哪种格式,底层都是字节数组形式存储的,最大空间不超过512m,SET添加,MSET批量添加,INCRBY age 2可以,MSET,INCRSETEX
    28
  • 5
    S外部函数可以访问函数内部的变量的闭包-闭包最简单的用不了,闭包是内层函数+外层函数的变量,简称为函数套函数,外部函数可以访问函数内部的变量,存在函数套函数
    24
  • 6
    Redis06-Redis常用的命令,模糊的搜索查询往往会对服务器产生很大的压力,MSET k1 v1 k2 v2 k3 v3 添加,DEL是删除的意思,EXISTS age 可以用来查询是否有存在1
    31
  • 7
    Redis05数据结构介绍,数据结构介绍,官方网站中看到
    22
  • 8
    JS字符串数据类型转换,字符串如何转成变量,+号只要有一个是字符串,就会把另外一个转成字符串,- * / 都会把数据转成数字类型,数字型控制台是蓝色,字符型控制台是黑色,
    20
  • 9
    JS数组操作---删除,arr.pop()方法从数组中删除最后一个元素,并返回该元素的值,arr.shift() 删除第一个值,arr.splice()方法,删除指定元素,arr.splice,从第一
    20
  • 10
    定义好变量,${age}模版字符串,对象可以放null,检验数据类型console.log(typeof str)
    19