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)。也可以存放中間處理的數據,類似於臨時表的作用,但是是存放在內存中的。