Oracle 自定義TYPE 的幾種用法

Oracle 自定義TYPE 的幾種用法

Oracle中的類型有很多種,主要可以分爲以下幾類:
1、字符串類型。如:char、nchar、varchar2、nvarchar2。
2、數值類型。如:int、number(p,s)、integer、smallint。
3、日期類型。如:date、interval、timestamp。
4、PL/SQL類型。如:pls_integer、binary_integer、binary_double(10g)、binary_float(10g)、boolean。plsql類型是不能在sql環境中使用的,比如建表時。
5、自定義類型。


下面簡單的枚舉下常用的幾種自定義類型。
1、子類型。
這種類型最簡單,類似類型的一個別名,主要是爲了對常用的一些類型簡單化,它基於原始的某個類型。如:
有些應用會經常用到一些貨幣類型:number(16,2)。如果在全局範圍各自定義這種類型,一旦需要修改該類型的精度,則需要一個個地修改。
那如何實現定義的全局化呢?於是就引出了子類型:
subtype cc_num is number(16,2);
這樣就很方便地解決了上述的麻煩。

2、普通類型
如:
create or replace type typ_calendar as object(
    年 varchar2(8),
    月 varchar2(8),
    星期日 varchar2(8),
    星期一 varchar2(8),
    星期二 varchar2(8),
    星期三 varchar2(8),
    星期四 varchar2(8),
    星期五 varchar2(8),
    星期六 varchar2(8),
    本月最後一日 varchar2(2)
);
這種類型可以在表結構定義的時候使用:
create table tcalendar of typ_calendar;

插入數據測試:
SQL> insert into tcalendar
  2  select typ_calendar('2010','05','1','2','3','4','5','6','7','31') from dual
  3  /
 
注意:插入的數據需要用typ_calendar進行轉換。

1 row inserted

--查看結果
SQL> select * from tcalendar;
 
年       月       星期日   星期一   星期二   星期三   星期四   星期五   星期六   本月最後一日
-------- -------- -------- -------- -------- -------- -------- -------- -------- ------------
2010     05       1        2        3        4        5        6        7        31

3、帶成員函數的類型體(type body)
這種類型包含了對類型中數據的內部處理,調用該類型時,可將處理後的數據返回給調用方。
對上面的例子進行擴展。要求給當天加上特殊標識(【】)來突出顯示。
首先,在typ_calendar中增加一個成員函數聲明:
create or replace type typ_calendar as object(
    年 varchar2(8),
    月 varchar2(8),
    星期日 varchar2(8),
    星期一 varchar2(8),
    星期二 varchar2(8),
    星期三 varchar2(8),
    星期四 varchar2(8),
    星期五 varchar2(8),
    星期六 varchar2(8),
    本月最後一日 varchar2(2),
   
    member function format(
        curday date        := sysdate,
        fmtlen pls_integer := 8
    )return typ_calendar
)
然後,創建一個type body,在type body中實現該成員函數:
create or replace type body typ_calendar as
    member function format(
        curday date        := sysdate,
        fmtlen pls_integer := 8
    ) return typ_calendar as
        v_return   typ_calendar := typ_calendar('','','','','','','','','','');
        v_dd       varchar2(2)  := to_char(curday, 'dd');
       
        function fmt(
            fmtstr varchar2
        )return varchar2 as
        begin
            return lpad(fmtstr, fmtlen, ' ');
        end fmt;
    begin
        v_return.年 := 年;
        v_return.月 := 月;
        v_return.星期日 := fmt(星期日);
        v_return.星期一 := fmt(星期一);
        v_return.星期二 := fmt(星期二);
        v_return.星期三 := fmt(星期三);
        v_return.星期四 := fmt(星期四);
        v_return.星期五 := fmt(星期五);
        v_return.星期六 := fmt(星期六);
        v_return.本月最後一日 := 本月最後一日;

        if (年 || lpad(月, 2, '0') = to_char(curday, 'yyyymm')) then
            case v_dd
            when 星期日 then
                v_return.星期日 := fmt('【' || 星期日 || '】');
            when 星期一 then
                v_return.星期一 := fmt('【' || 星期一 || '】');
            when 星期二 then
                v_return.星期二 := fmt('【' || 星期二 || '】');
            when 星期三 then
                v_return.星期三 := fmt('【' || 星期三 || '】');
            when 星期四 then
                v_return.星期四 := fmt('【' || 星期四 || '】');
            when 星期五 then
                v_return.星期五 := fmt('【' || 星期五 || '】');
            when 星期六 then
                v_return.星期六 := fmt('【' || 星期六 || '】');
            else null;
            end case;
        end if;
       
        return v_return;
    end format;
end;


插入測試數據:
SQL> insert into tcalendar
  2  select typ_calendar('2010','05','1','2','3','4','5','6','7','31') from dual
  3  /
 
1 row inserted
 
SQL> insert into tcalendar
  2  select typ_calendar('2010','05','1','2','3','4','5','6','7','31').format() from dual
  3  /
 
1 row inserted

 SQL> insert into tcalendar
  2  select typ_calendar('2010','05','11','12','13','14','15','16','17','31').format() from dual
  3  /
 
1 row inserted
 
SQL> select * from tcalendar;
 
年       月       星期日   星期一   星期二   星期三   星期四   星期五   星期六   本月最後一日
-------- -------- -------- -------- -------- -------- -------- -------- -------- ------------
2010     05       1        2        3        4        5        6        7        31
2010     05              1        2        3        4        5        6        7 31
2010     05             11   【12】       13       14       15       16       17 31

可以看到數據已經居中處理了,並且到了第三條已經可以突出顯示當前日期了。
在這裏type 中的成員函數(member function)和靜態函數(static function)的區別有必要說明一下:
成員函數有隱含參數self,即自身類型,可以在執行的時候引用當前的數據並對數據進行操作。它的調用可以如下:object_expression.method()
靜態函數沒有該隱含參數。它的調用如下:type_name.method();

舉個例子:
首先,創建一個帶靜態函數聲明的類型頭:
SQL> create or replace type typ_col as object(
  2      col_name varchar2(30),
  3      tab_name varchar2(30),
  4      static function to_upper return typ_col
  5  )
  6  /
 
Type created

然後創建類型體:
SQL>
SQL> create or replace type body typ_col as
  2      static function to_upper
  3      return typ_col as
  4      begin
  5          return typ_col(upper(col_name), upper(tab_name));
  6      end to_upper;
  7  end;
  8  /
 
Warning: Type body created with compilation errors
 
SQL> show errors
Errors for TYPE BODY LYON.TYP_COL:
 
LINE/COL ERROR
-------- ---------------------------------------------------
5/30     PLS-00588: 非限定實例屬性引用只允許在成員方法中使用
5/9      PL/SQL: Statement ignored

錯誤信息表明,實例屬性只能在成員方法中使用。這裏隱去了self的調用,其實:
typ_col(upper(col_name), upper(tab_name));
等價於:
typ_col(upper(self.col_name), upper(self.tab_name));
而這種方式的使用根據前面的定義,只能在成員函數中實現:
SQL> create or replace type typ_col as object(
  2      col_name varchar2(30),
  3      tab_name varchar2(30),
  4      member function to_upper return typ_col
  5  )
  6  /
 
Type created
 
SQL>
SQL> create or replace type body typ_col as
  2      member function to_upper
  3      return typ_col as
  4      begin
  5          return typ_col(upper(self.col_name), upper(self.tab_name));
  6      end to_upper;
  7  end;
  8  /
 
Type body created
 
那兩者調用上又有什麼差別呢?
按照前面的定義,靜態函數的用法是type_name.method(),所以有:
SQL> select typ_col.to_lower(x).tab_name, typ_col.to_lower(x).col_name from tcol
  2  /
 
TYP_COL.TO_LOWER(X).TAB_NAME   TYP_COL.TO_LOWER(X).COL_NAME
------------------------------ ------------------------------
ipseg_int_db_tmp               start_ip
ipseg_int_db_tmp               end_ip
ipseg_int_db_tmp               area_code
px_city                        city_id
px_city                        city_name
px_city                        province_id
px_citygdp                     gdp_cycle_id
px_citygdp                     city_id
px_citygdp                     gdp
px_citygdp                     province_id
 
10 rows selected

SQL> select typ_col(column_name, table_name).to_upper().tab_name,
  2  typ_col(column_name, table_name).to_upper().col_name
  3  from user_tab_columns t
  4  where rownum <= 10;
 
TYP_COL(COLUMN_NAME,TABLE_NAME TYP_COL(COLUMN_NAME,TABLE_NAME
------------------------------ ------------------------------
DEMO                           X
IPSEG_INT_DB_TMP               START_IP
IPSEG_INT_DB_TMP               END_IP
IPSEG_INT_DB_TMP               AREA_CODE
PX_CITY                        CITY_ID
PX_CITY                        CITY_NAME
PX_CITY                        PROVINCE_ID
PX_CITYGDP                     GDP_CYCLE_ID
PX_CITYGDP                     CITY_ID
PX_CITYGDP                     GDP
 
10 rows selected
也就是說,靜態函數主要是用於處理並返回外部數據的,而成員函數是用於處理並返回內部數據的。

然後可以在函數中使用該類型,下面是一個顯示日曆的函數,並調用類型的成員函數對結果做了格式化:
create or replace function show_calendar(
    v_yermonth varchar2  := to_char(sysdate, 'yyyymm'))
return tbl_calendar as
    v_cal tbl_calendar;
    v_seg pls_integer := 6;
    v_len pls_integer := 8;
    v_yer varchar2(4) := substr(v_yermonth, 1, 4);
    v_mon varchar2(2) := lpad(substr(v_yermonth, 5, 2), 2, '0');
    v_ini date := to_date(v_yermonth || '01', 'yyyymmdd');
begin
    select typ_calendar(v_yer, v_mon,
           case when rn >= wkn - 1 and rn - wkn + 2 <= mxdays
           then  rn - wkn + 2 end,
           case when rn >= wkn - 2 and rn - wkn + 3 <= mxdays
           then  rn - wkn + 3 end,
           case when rn >= wkn - 3 and rn - wkn + 4 <= mxdays
           then  rn - wkn + 4 end,
           case when rn >= wkn - 4 and rn - wkn + 5 <= mxdays
           then  rn - wkn + 5 end,
           case when rn >= wkn - 5 and rn - wkn + 6 <= mxdays
           then  rn - wkn + 6 end,
           case when rn >= wkn - 6 and rn - wkn + 7 <= mxdays
           then  rn - wkn + 7 end,
           case when rn >= wkn - 7 and rn - wkn + 8 <= mxdays
           then  rn - wkn + v_len end,
           mxdays).format()
      bulk collect into v_cal
      from (select (rownum - 1)*7 rn,
                   to_number(to_char(trunc(v_ini, 'mm'), 'd')) wkn,
                   to_number(to_char(last_day(v_ini), 'dd')) mxdays
              from dual
            connect by rownum <= v_seg) b
     where rn - wkn + 2 <= mxdays; --過濾空行
    return v_cal;
end show_calendar;

獲得當前月的日曆:
SQL> select * from table(show_calendar);
/
 
年       月       星期日   星期一   星期二   星期三   星期四   星期五   星期六   本月最後一日
-------- -------- -------- -------- -------- -------- -------- -------- -------- ------------
2010     05                                                                    1 31
2010     05              2        3        4        5        6        7        8 31
2010     05              9       10       11   【12】       13       14       15 31
2010     05             16       17       18       19       20       21       22 31
2010     05             23       24       25       26       27       28       29 31
2010     05             30       31                                              31
 
6 rows selected

獲取指定月份的日曆:
SQL> select * from table(show_calendar('201001'));
 
年       月       星期日   星期一   星期二   星期三   星期四   星期五   星期六   本月最後一日
-------- -------- -------- -------- -------- -------- -------- -------- -------- ------------
2010     01                                                           1        2 31
2010     01              3        4        5        6        7        8        9 31
2010     01             10       11       12       13       14       15       16 31
2010     01             17       18       19       20       21       22       23 31
2010     01             24       25       26       27       28       29       30 31
2010     01             31                                                       31
 
6 rows selected
 
顯示多個月的日曆:
SQL> select b.*
  2    from (select to_char(add_months(date'1998-01-01', rownum-1), 'yyyymm') c from dual connect by rownum <= 10) a,
  3         table(show_calendar(to_char(a.c))) b
  4  /
 
年       月       星期日   星期一   星期二   星期三   星期四   星期五   星期六   本月最後一日
-------- -------- -------- -------- -------- -------- -------- -------- -------- ------------
1998     01                                                  1        2        3 31
1998     01              4        5        6        7        8        9       10 31
1998     01             11       12       13       14       15       16       17 31
1998     01             18       19       20       21       22       23       24 31
1998     01             25       26       27       28       29       30       31 31
1998     02              1        2        3        4        5        6        7 28
1998     02              8        9       10       11       12       13       14 28
1998     02             15       16       17       18       19       20       21 28
...............
1998     09                                1        2        3        4        5 30
1998     09              6        7        8        9       10       11       12 30
1998     09             13       14       15       16       17       18       19 30
1998     09             20       21       22       23       24       25       26 30
1998     09             27       28       29       30                            30
1998     10                                                  1        2        3 31
1998     10              4        5        6        7        8        9       10 31
1998     10             11       12       13       14       15       16       17 31
1998     10             18       19       20       21       22       23       24 31
1998     10             25       26       27       28       29       30       31 31
 
51 rows selected
上面是一個特殊的table函數使用方法。
即將a表中構造的月份,作爲參數傳入到table函數中的show_calendar函數中,然後show_calendar函數根據指定的月份返回
該月的日曆。實現了獲取多個月日曆的要求。

自定義type的一個限制是不能使用rowid類型:
 
SQL> create or replace type typ_rowid as object(rid urowid);
  2  /
 
Warning: Type created with compilation errors
 
SQL> show errors;
Errors for TYPE CUSTOMER21.TYP_ROWID:
 
LINE/COL ERROR
-------- ---------------------------------------------------
1/30     PLS-00530: 爲此對象類型屬性使用了非法類型: UROWID。


其他的特殊使用還有自定義聚集函數,典型的例子就是字符串相加的問題。
我們知道,對數字列的相加很簡單,直接求sum即可。但是如何對字符列進行相加呢?
如:
SQL> with tmp as (
  2  select '1' c from dual union all
  3  select '2' c from dual union all
  4  select '3' c from dual union all
  5  select '4' c from dual)
  6  select * from tmp
  7  /
 
C
-
1
2
3
4
1,2,3,4要合併爲1->2->3->4,該如何實現?
一個辦法是用層級查詢來實現(用sys_connect_by_path即可)。
另外,10g下,還可以用wm_sys.wm_concat函數來實現。
還有就是自定義聚集函數了。自定義聚集函數首先要定義一個類型,在類型中調用了Oracle內部實現的幾個接口函數:
CREATE OR REPLACE TYPE "TYP_STRCAT" as object
(
    strsum     varchar2(4000),
    strcnt     number,
    strdelimit varchar2(10),

    static function ODCIAggregateInitialize(
        actx in out typ_strcat)
    return number,

    member function ODCIAggregateIterate(
        self in out typ_strcat,
        val  in varchar2)
    return number,

    member function ODCIAggregateTerminate(
        self        in typ_strcat,
        returnvalue out varchar2,
        flags       in number)
    return number,

    member function ODCIAggregateMerge(
        self in out typ_strcat,
        ctx2 typ_strcat)
    return number
)
CREATE OR REPLACE TYPE BODY "TYP_STRCAT" as
    static function ODCIAggregateInitialize(actx in out typ_strcat)
        return number as
    begin
        actx := typ_strcat(null, 1, ',');
        return ODCICONST.Success;
    end;
    member function ODCIAggregateIterate(self in out typ_strcat,
                                         val  in varchar2) return number as
    begin
        self.strsum := self.strsum || strdelimit || val;
        self.strcnt := self.strcnt + 1;
        return ODCICONST.Success;
    end;
    member function ODCIAggregateTerminate(self        in typ_strcat,
                                           returnvalue out varchar2,
                                           flags       in number) return number as
    begin
        returnvalue := ltrim(self.strsum, strdelimit);
        return Odciconst.Success;
    end;
    member function ODCIAggregateMerge(self in out typ_strcat,
                                       ctx2 in typ_strcat) return number as
    begin
        self.strsum := ctx2.strsum || self.strsum;
        return Odciconst.Success;
    end;
end;

然後創建函數:
CREATE OR REPLACE FUNCTION "SSUM" (p_str varchar2)
return varchar2
/*parallel_enable*/ aggregate using typ_strcat;

然後,就可以使用字符串相加的功能了:

SQL> with tmp as (
  2  select '1' c from dual union all
  3  select '2' c from dual union all
  4  select '3' c from dual union all
  5  select '4' c from dual)
  6  select replace(ssum(c), ',', '->') from tmp
  7  /
 
REPLACE(SSUM(C),',','->')
--------------------------------------------------------------------------------
1->2->3->4

 

4.表類型
這種類型類似於一個數組類型,可以申明一維或多維。
比如說,創建一個元素長度爲4000的字符串數組,則有:
create or replace type tbl_varchar2 as table of varchar2(4000)

然後可以如下使用該類型:
SQL> select * from table(tbl_varchar2('1','1','3','4','5','6'));
 
COLUMN_VALUE
--------------------------------------------------------------------------------
1
1
3
4
5
6
 
6 rows selected
 
如果要獲取多字段的,則可以取上面例子:
SQL> select *
  2    from table(tbl_calendar(
  3               typ_calendar('2008','2','3','4','5','6','7','8','9','28'),
  4               typ_calendar('2009','12','13','4','5','6','7','8','9','31'),
  5               typ_calendar('2010','12','13','4','5','6','7','8','9','31')));
 
年       月       星期日   星期一   星期二   星期三   星期四   星期五   星期六   本月最後一日
-------- -------- -------- -------- -------- -------- -------- -------- -------- ------------
2008     2        3        4        5        6        7        8        9        28
2009     12       13       4        5        6        7        8        9        31
2010     12       13       4        5        6        7        8        9        31

以上使用的類型都基於schema級別,如果是定義在包、函數、過程等這些結構裏是不能給table函數使用的。
這種類型可以使用在管道函數中(pipelined function)。也可以存放中間處理的數據,類似於臨時表的作用,但是是存放在內存中的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章