获取高低一个工作日实际

前言

其实这个文章集体之前有进行过公布和探讨,在上一篇文章中,介绍了如何通过postgresql数据库的sql语句构建一个工作日的表,并且介绍如何应用sql语法获取某一天往前或者往后的工作日或者天然日,然而理论浏览之后发现短少了很多细节,故这里从新梳理一下整个过程,心愿能够给读者一个参考。

本次实际只是集体提供的一个工作日获取的解决方案,如果有更好的解决方案欢送探讨和分享。

上一篇文章链接:https://juejin.cn/post/7023008573827481637

留神应用的数据库为: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_idvarchar主键255YES
calendar_yearvarchar10NO
calendar_monthvarchar10NO
calendar_datevarchar10NO
day_of_weekvarchar天然周的第几天10NO
day_of_monthvarchar月的第几天10NO
week_of_yearvarchar年的第几个天然周10NO
month_of_yearvarchar年的第几月10NO
quarter_of_yearvarchar年的第几季10NO
is_end_monthvarchar是否月末10NO
is_end_quartervarchar是否季末10NO
is_end_halfayearvarchar是否半年末10NO
is_end_yearvarchar是否年末10NO
operator_idvarchar操作人ID50NO
operator_namevarchar操作人名称50NO
operate_datetimestamp操作工夫6NO
res_attr1varchar预留字段140NO
res_attr2varchar预留字段240NO
res_attr3varchar预留字段340NO
res_attr4varchar预留字段440NO
is_work_dayvarchar是否为工作日,Y是,N否(即节假日)1NO

另外这里再教大家一个技巧,如何应用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

上面是语句的调用成果,留神下面的语句倡议给所有的字段加上正文之后再执行。

填充数据

有了表构造还不够,这里咱们还须要填充数据,咱们应用如下的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;

执行实现之后,能够看到插入了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 )    ) mmORDER 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 )    ) mmORDER BY    calendar_date

看到这里,我置信大部分读者应该都晓得这是干啥用的了,这里咱们通过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变量判断是往前还是往后,然而集体并不喜爱在参数中管制办法的行为,这样很容易出问题。

写在最初

此工作日的实现办法比拟蠢笨也比较简单,如果有好的想法欢送探讨。