Oracle版本9提供了一種有趣的新的數據類型,開發人員借助此類型可以聲明包括任何類型數據的變量。對于單個數據來說,此數據類型即ANYDATA。對于TABLE或者VARRAY數據來說,則為ANYDATASET。ANYTYPE用于描述存儲在ANYDATA或者ANYDATASET變量以及欄中的數據類型。 這些數據類型對于處理存儲在數據庫中的xml數據或高級序列(Advanced Queues)具有非常重要的意義。說明文檔中提到了ANYDATA數據類型可以用于對對象進行串行化(serialize),但與之相關的示例較少。
串行化首先將數據值和其他結構(strUCture)組成為另外一些結構,然后將生成的結構的所有構成成分輸出為流。流可以被結構返回讀取,并且將覆蓋前一個會話的信息。通常而言,在應用程序中進行的保存和打開文件的操作即不過是串行化的一種形式。
一個Oracle數據庫或許需要使用串行化功能來存儲一些表格數據的某個版本備份,這樣可以在不使用數據庫提交(commits)、回滾(rollbacks)、回閃(Flashback)查詢的情況下對數據進行查看和其他操作。許多應用程序都會用到類似的對數據源的控制功能,諸如可以在應用級對當前和以前的數據版本進行比較,或對合并操作(merge)和撤銷操作(undo)所產生數據改變進行比較。很多此類應用程序都被設計為對每個表格創建一個備份表格。而對于數據庫性能和開發進度來說,要維護這些眾多的備份表格以及之間的各種關系,成為了生產數據(PRoduction data)以外的沉重負擔。
而通過ANYDATA數據類型以及動態SQL功能,使得通過單一的串行化存儲進程來把許多需要備份的表格串行輸入到一個單獨的備份表格成為可能。ANYDATA的一個優勢在于,不同于類似VARCHAR2的簡單的轉換數據類型,使用ANYDATA方法原始的數據類型并不會丟失。數據可以被存儲在ANYDATA欄或者變量中而不會丟失任何細節(或根據在DATA和VARCHAR2之間進行轉換的當前NLS語義而定)。這些存儲的數據在轉化過程中不會有任何損失。
一個ANYDATA對象可以通過使用任何Convert*方法構造簡單值的方法來實現,或者通過“piecewise”構造方法創建諸如對象和數據庫一類的更為復雜的變量。對于本例而言,我將集中解釋如何使用Convert*方法。
為了創建一個串行化進程,我使用了動態SQL來產生一個對表格中所有數據的查詢命令,其中包括ROWID。然后我將查詢命令進行分解并描述,從而得到一個關于欄和數據類型的列表。再定義提取(fetch)出欄,將每一欄從各行中提取出來,然后將其插入到串行化表格中。在本例中我使用了DBMS_SQL,因為“自身動態SQL(native dynamic SQL)”現在還不能支持描述動態查詢。絕大多數的工作都是對從DBMS_SQL數據類型代碼到合適的數據類型方法以及函數的轉換過程進行處理。要得到這些代碼的列表,可以查看OCI包含文件ocidfn.h,或者是諸如USER_TAB_COLUMNS這樣的對查看(view)的定義。在本例中,我使用了簡單的數據類型(可以在EMP和DEPT表格中找到),這樣可以直接對其進行轉換。
drop table serialized_data;
create table serialized_data
(
tablename varchar2(30) not null,
row_id rowid not null,
colseq integer not null,
item anydata
);
create or replace procedure serialize(p_tablename varchar2)
is
l_tablename varchar2(30) := upper(p_tablename);
c pls_integer; -- cursor
x pls_integer; -- dummy
col_cnt pls_integer;
dtab dbms_sql.desc_tab;
l_rowid char(18);
l_anydata anydata;
l_vc2 varchar2(32767);
l_number number;
l_vc varchar(32767);
l_date date;
l_raw raw(32767);
l_ch char;
l_clob clob;
l_blob blob;
l_bfile bfile;
begin
c := dbms_sql.open_cursor;
dbms_sql.parse(c,'select rowid,'p_tablename'.* from 'p_tablename,
dbms_sql.native);
dbms_sql.describe_columns(c,col_cnt,dtab);
dbms_sql.define_column(c,1,l_rowid,18);
for i in 2 .. col_cnt loop
case dtab(i).col_type
when 1 then
dbms_sql.define_column(c,i,l_vc2,dtab(i).col_max_len);
when 2 then
dbms_sql.define_column(c,i,l_number);
when 9 then
dbms_sql.define_column(c,i,l_vc,dtab(i).col_max_len);
when 12 then
dbms_sql.define_column(c,i,l_date);
when 23 then
dbms_sql.define_column_raw(c,i,l_raw,dtab(i).col_max_len);
when 96 then
dbms_sql.define_column_char(c,i,l_ch,dtab(i).col_max_len);
when 112 then
dbms_sql.define_column(c,i,l_clob);
when 113 then
dbms_sql.define_column(c,i,l_blob);
when 114 then
dbms_sql.define_column(c,i,l_bfile);
end case;
end loop;
x := dbms_sql.execute(c);
while dbms_sql.fetch_rows(c) != 0 loop
dbms_sql.column_value(c,1,l_rowid);
for i in 2 .. col_cnt loop
case dtab(i).col_type
when 1 then
dbms_sql.column_value(c,i,l_vc2);
l_anydata := ANYDATA.ConvertVarchar2(l_vc2);
when 2 then
dbms_sql.column_value(c,i,l_number);
l_anydata := ANYDATA.ConvertNumber(l_number);
when 9 then
dbms_sql.column_value(c,i,l_vc);
l_anydata := ANYDATA.ConvertVarchar(l_vc);
when 12 then
dbms_sql.column_value(c,i,l_date);
l_anydata := ANYDATA.ConvertDate(l_date);
when 23 then
dbms_sql.column_value(c,i,l_raw);
l_anydata := ANYDATA.ConvertRaw(l_raw);
when 96 then
dbms_sql.column_value(c,i,l_ch);
l_anydata := ANYDATA.ConvertChar(l_ch);
when 112 then
dbms_sql.column_value(c,i,l_clob);
l_anydata := ANYDATA.ConvertClob(l_clob);
when 113 then
dbms_sql.column_value(c,i,l_blob);
l_anydata := ANYDATA.ConvertBlob(l_blob);
when 114 then
dbms_sql.column_value(c,i,l_bfile);
l_anydata := ANYDATA.ConvertBFile(l_bfile);
end case;
insert into serialized_data (tablename,row_id,colseq,item)
values (l_tablename,l_rowid,i,l_anydata);
end loop;
end loop;
dbms_sql.close_cursor(c);
end;
/
show errors;
假如我希望對“EMP”和“DEPT”表格串行化,我可以按照以下代碼通過SQL*Plus來完成:
exec serialize('emp');
exec serialize('dept');
select t.item.gettypename() from serialized_data t;
使用ANYDATA中的一個問題是,假如是對象,則只有很少的信息可以通過直接SQL恢復過來。
表格數據必須使用PL/SQL過程進行訪問。為了表明這點,我將回過頭來對原始表格中的一欄和串行化表格中的一欄進行比較。我可以使此匿名PS/SQL代碼段如下表示:
Declare
l_anydata anydata;
l_vc2 varchar2(32767);
x pls_integer;
begin
for row in
(
select emp.ename,sd.item