【TD迁移GaussDB(DWS)】【函数迁移之二】TD常用日期函数适配

举报
SeqList 发表于 2021/07/22 18:16:06 2021/07/22
【摘要】 日期函数是数据库非常重要的函数类型,我们从TD迁移到GaussDB(DWS)时,有很多TD特有的日期函数,GaussDB(DWS)没有的,这些日期函数需要在GaussDB(DWS)建一些自定义函数做迁移适配。本文描述了一次迁移过程中遇到的日期函数,并描述了如何通过自定义函数来适配脚本迁移。

【前言】

    日期函数是数据库非常重要的函数类型,我们从TD迁移到GaussDB(DWS)时,GaussDB(DWS)有很多日期函数和日期类型的行为与TD是一致的,但仍然有很多TD特有的日期函数,GaussDB(DWS)没有的,这些日期函数需要在GaussDB(DWS)建一些自定义函数做迁移适配。

   值得注意的是,td的日期相关的内容中,有一个日历表sys_calendar.calendar,该日历表存放1900-01-01到2100-12-31的所有日期,以及该天在一周,一月,一年的第几天等信息。经验上看该表在批量作业中使用较多,建议建同名schema同名表存同样的数据在GaussDB(DWS)中。

【返回数字的日期函数】

1.【*number_of_*】

这是一类函数获取第几天、第几月,第几年的日期函数,在实践过程中,主要有以下几个函数:

函数名称 函数作用
daynumber_of_calendar(date) 获取1900-01-01到输入日期相差的天数
daynumber_of_year(date) 获取当年1月1日到输入日期相差的天数
daynumber_of_month(date) 获取当月1日到输入日期相差的天数
daynumber_of_week(date) 获取当周周日到输入日期相差的天数,周日为0,周六为6,与GaussDB(DWS)的相差一天
weeknumber_of_calendar(date)

获取1900-01-01到输入日期相差的周数

weeknumber_of_year(date) 获取当年1月1日到输入日期相差的周数
monthnumber_of_year(date)

获取输入日期的月份数

quarternumber_of_year(date)

获取输入日期的季度数

yearnumber_of_year(date) 获取输入日期的年份数

*注意,td中这些函数最大输入是sys_calendar.calendar的最大日期,2100-12-31,超过该日期会报错,而GaussDB(DWS)使用本文方法建的函数,不会报错。

这些函数的实现方式比较简单,主要使用extract的方式获取天数,月数等信息。周数因为GaussDB(DWS)目前本身没有获取周数的函数(extract有获取iso年的周数,但没有获取自然年的周数),而且td使用一周的开始是周日,所以获取周数的时候会有点绕。

此处可以使用sys_calendar.calendar这个表的字段信息能轻松获取全部信息,比如select week_of_year from sys_calendar.calendar where calendar_date = 输入日期,能直接查询到weeknumber。但是因为与该表存在强关联,在灾难恢复的时候需要先恢复该表才能使用这类型的函数,在考虑后决定不使用该表。

以下是一些函数的例子,所有函数的sql定义会在文末附上

CREATE OR REPLACE FUNCTION public.daynumber_of_week(date)
RETURNS integer
LANGUAGE sql
IMMUTABLE NOT FENCED SHIPPABLE
AS $$
SELECT EXTRACT(dow from $1)::integer + 1; 
$$
;

CREATE OR REPLACE FUNCTION public.weeknumber_of_year(date)
RETURNS integer
LANGUAGE sql
IMMUTABLE NOT FENCED SHIPPABLE
AS $$
/*可做成依赖sys_calendar.calendar表的结果,直接select week_of_year from sys_calendar.calendar where calendar_date = $1;即可。
sys_calendar.calendar需做成复制表
但减少依赖问题可能少一些,比如备份恢复等,函数不需等待sys_calendar.calendar的恢复即可使用
*/
select ceil(
($1 - extract(dow from $1) + 1 -
/*td日历中,周日是一周的开始,部分年份的1-1是周日,weeknumber从1开始;部分年份的1-1不是周日,weeknumber从0开始
此处获取的是weeknumber为1的第一个周日*/
((extract(year from $1)||'-01-01')::date + (case when extract(dow from (extract(year from $1)||'-01-01')::date) <> 0 then 6 - extract(dow from ((extract(year from $1)||'-01-01')::date)) else 0 end))
)/7 /*日期相减后除7,使用ceil向上取整则为weeknumber*/
)::integer;
$$
;


2.【td_day_of*】

该类型函数与daynumber_of* 一样,只是名字不同,此处简单举例子:

CREATE OR REPLACE FUNCTION public.td_day_of_month(date)
RETURNS integer
LANGUAGE sql
IMMUTABLE NOT FENCED SHIPPABLE
AS $$
select extract(day from $1)::integer;
$$


【返回日期的日期函数】

返回日期的日期函数,是对日期做一定的运算后得出一个日期结果,比如trunc,oadd_months

函数名称 函数作用
trunc(date,varchar) 获取特定的日期,如上个周几等
oadd_months(date,integer) 获取多少个月后的日期,与自带的add_months区别是,oadd_months输入是月末会取到月末,比如oadd_months('2021-02-28',-12),结果是'2020-02-29',而add_months是2020-02-28
months_between(date,date) 输入的日期相差的月份数量,输出是double类型,存在小数点。该函数没办法使用sql函数处理,于是使用c函数处理了。


trunc函数在teradata的一些输入说明如下

参考:https://docs.teradata.com/r/WX0vkeB8F3JQXZ0HTR~d0Q/f96l5JKF5A34cprktUWdzA

Date Formats for TRUNC

 

Element

Description

CC
SCC

One year greater than the first two digits of a 4-digit year.

SYYY
YYYY
YEAR
SYEAR
YYY
YY
Y

Year (rounds up on July 1)

IYYY
IYY
IY
I

ISO year

MONTH
MON
MM
RM

Month (rounds up on the 16th day)

Q

Quarter (rounds up on the 16th day of the 2nd month of the quarter)

WW

Same day of the week as the 1st day of the year

IW

Same day of the week as the first day of the ISO year

W

Same day of the week as the first day of the month

DDD
DD
J

Day

DAY
DY
D

Starting day of the week

HH
HH12
HH24

Hour

MI

Minute


trunc日期函数的一些例子:

*附常用的oadd_months和trunc的sql代码

CREATE OR REPLACE FUNCTION public.oadd_months(date,integer)
RETURNS date
LANGUAGE sql
IMMUTABLE NOT FENCED SHIPPABLE
AS $$
select case when (extract(day from $1) = extract(day from (date_trunc('MONTH',$1) + interval '1 MONTH - 1 DAY'))) THEN
(DATE_TRUNC('MONTH',$1) + CAST($2 + 1 ||' MONTH - 1 DAY' AS INTERVAL))::DATE
else ($1 + CAST($2 || ' MONTH' AS INTERVAL))::DATE
end;
$$
;

CREATE OR REPLACE FUNCTION public.trunc(date,varchar default 'DD'::varchar)
RETURNS date
LANGUAGE plpgsql
IMMUTABLE STRICT NOT FENCED SHIPPABLE
AS $$
declare 
    v_timestamp date := null;
    v_format varchar := upper($2);

begin

   IF v_format in ('YYYY','YEAR','YYY','YY','Y','SYYYY','SYEAR') THEN
           v_timestamp := date_trunc('YEAR',$1);
   ELSIF v_format in ('IYYY','IYY','IY','I') THEN --ISO日历的第一天
           v_timestamp := (extract('YEAR' from $1)||'-01-01')::date - (date_part('dow',(extract('YEAR' from $1)||'-01-01')::date) - 1);
   ELSIF v_format in ('MONTH','MON','MM','RM') THEN
           v_timestamp := date_trunc('MONTH',$1);
   ELSIF v_format in ('DD','DDD','J') THEN
           v_timestamp := date_trunc('DAY',$1);
   ELSIF v_format in ('D','DY','DAY') THEN
          IF date_part('dow',$1) = 0 THEN
                  v_timestamp := $1;
          ELSE 
                  v_timestamp := date_trunc('WEEK',$1) -1;
          END IF;
   ELSIF v_format in ('WW') THEN --最近一个周几(一年的第一天的周几),假如一年第一天是周一,那返回最近一个周一
          v_timestamp := $1 - (
                         CASE WHEN date_part('isodow',$1) >= date_part('isodow',(extract('YEAR' from $1)||'-01-01')::date)
                         THEN date_part('isodow',$1) - date_part('isodow',(extract('YEAR' from $1)||'-01-01')::date)
                         ELSE 7 + date_part('isodow',$1) - date_part('isodow',(extract('YEAR' from $1)||'-01-01')::date)
                       end);
   ELSIF v_format in ('IW') THEN --最近一个周几(iso年的第一天的周几)
          v_timestamp := $1 - (date_part('isodow',$1) - 1);
   ELSIF v_format in ('W') THEN --最近一个周几(本月的第一天的周几)
          v_timestamp := $1 - (
                          CASE WHEN date_part('isodow',$1) >= date_part('isodow',(SUBSTR($1::TEXT,1,7)||'-01')::DATE)
                          THEN date_part('isodow',$1)  - date_part('isodow',(SUBSTR($1::TEXT,1,7)||'-01')::DATE)
                          ELSE 7 + date_part('isodow',$1) - date_part('isodow',(SUBSTR($1::TEXT,1,7)||'-01')::date) 
                        end
                         );
   ELSIF v_format  = 'Q' THEN
           v_timestamp := CASE WHEN EXTRACT('QUARTER' FROM $1) = 1 THEN (extract('YEAR' FROM $1)||'-01-01')::date
           WHEN EXTRACT('QUARTER' FROM $1) = 2 THEN (extract('YEAR' FROM $1)||'-04-01')::date
           WHEN EXTRACT('QUARTER' FROM $1) = 3 THEN (extract('YEAR' FROM $1)||'-07-01')::date
           WHEN EXTRACT('QUARTER' FROM $1) = 4 THEN (extract('YEAR' FROM $1)||'-10-01')::date
         END;
   ELSIF v_format in ('HH','HH24') THEN
           v_timestamp := date_trunc('HOUR',$1);
   ELSIF v_format in ('HH12') THEN
           v_timestamp := to_char($1,'YYYY-MM-DD HH12:00:00')::DATE;
   ELSIF v_format in ('MINUTE','MI') THEN
           v_timestamp := date_trunc('MINUTE',$1);
           
    ELSE
         raise exception 'U-2001 [%] is not recognize. you can try [help]',$2;
    END IF;
RETURN v_timestamp;
end;
$$
;


具体的代码可以查看附件


【总结】

在迁移TD到GaussDB(DWS)过程中,肯定有很多日期相关的函数,本文针对常见的,GaussDB(DWS)无法支持的日期函数,进行了一些简单描述和代码展示。因teradata日期函数众多,本文肯定没有覆盖全,此处可以做一个引子,若出现本文附件的代码没有覆盖的场景,读者可以自行根据本文的例子,相对应新增日期udf函数使用。


想了解GuassDB(DWS)更多信息,欢迎微信搜索“GaussDB DWS”关注微信公众号,和您分享最新最全的PB级数仓黑科技,后台还可获取众多学习资料~

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。