实验五 综合实验

一、目的与要求

本实验为综合实验,要求综合运用用户管理、逻辑备份、访问数据字典等技术解决问题。

二、操作环境

硬件:AMD Ryzen 3750H 16GB RAM

软件:搭载Oracle 12c Enterprise 的 Windows10专业版

三、实验内容

  1. 创建一个PROFILE文件pTester,设置锁定用户的登录失败次数为3次,会话的总计连接时间60分钟,口令可用天数30天。

  2. 创建一个新用户Tester,密码为Tester123,缺省表空间是tabspace_???。在tabspace_???表空间中可以使用50M空间,指定环境资源文件为pTester。

  3. 将角色RESOURCE指派给用户Tester。

  4. 用EXP和IMP工具将之前创建的major_???表导入到Tester用户下。

  5. 利用PL/SQL语言,以major_???表为例,编写一个存储过程实现针对单张表的逻辑数据导出功能,要求将给定表的数据转换成SQL语言的Insert语句,表的结构转换成SQL语言的Create Table语句,并保存在文件中。该过程以要导出的表名和保存SQL语句的文件名为参数。

四、 实验过程

创建一个PROFILE文件pTester,设置锁定用户的登录失败次数为3次,会话的总计连接时间60分钟,口令可用天数30天。

alter session set "_oracle_script"=true;

create profile pTester limit 
FAILED_LOGIN_ATTEMPTS 3 
CONNECT_TIME 60 
PASSWORD_LOCK_TIME 30;

创建一个新用户Tester,密码为Tester123,缺省表空间是tabspace_???。在tabspace_???表空间中可以使用50M空间,指定环境资源文件为pTester。

create user Tester identified by Tester123 default tablespace tabspace_J521
quota 50M on tabspace_J521
profile pTester;

将角色RESOURCE指派给用户Tester。

grant resource to Tester

用EXP和IMP工具将之前创建的major_???表导入到Tester用户下。

exp U_J521/Dashui506@orcl file=D:\Database\Exped\major.dmp tables=T_MAJOR_J521

导入给新建立的用户:

扫描二维码关注公众号,回复: 12409220 查看本文章
imp Tester/Tester123@orcl file=D:\Database\Exped\major.dmp tables=T_MAJOR_J521 fromuser=U_J521 touser=Tester

利用PL/SQL语言,以major_???表为例,编写一个存储过程实现针对单张表的逻辑数据导出功能,要求将给定表的数据转换成SQL语言的Insert语句,表的结构转换成SQL语言的Create Table语句,并保存在文件中。该过程以要导出的表名和保存SQL语句的文件名为参数。

首先设定文件输出的根目录为D:/,并将读写该目录内文件的权限给予当前用户:

create   or   replace  directory D_OUTPUT  as   'D:/' ;
grant   read ,write  on  directory D_OUTPUT  to U_J521;

创建表detailsTable,用于存储需要查询的表的列基本信息:

create table detailsTable(
    tableName varchar(256),
    columnName varchar(256),
    columnType varchar(20),
    columnLenth number
);

在以上内容的基础上编写过程实现实验指导书的要求:

create or replace PROCEDURE auto_generate (tableName in VARCHAR , filePath in VARCHAR ) AS
    cursor detailsCursor is select columnName,columnType,columnLenth from detailsTable;
    type curType is ref cursor;  
    tableCursor curType;  
    v_sql varchar(3999);  
    v_row varchar(3999);
    outFile UTL_FILE.FILE_TYPE;
    fileBuffer varchar(10000);
    columnName1 varchar(20);
    columnType1 varchar(20);
    bufferlenth number;
    columnLenth1 number;
    v_name varchar(3999);  
    v_type varchar(99);  
    v_ldata varchar(32765);  
    v_rdata varchar(32765);  
    v_cname varchar(3999);  
    v_instr number(10);  
    v_count number(6);  
    v_fields varchar(3999);  

BEGIN 
    outFile:=UTL_FILE.FOPEN('D_OUTPUT',filePath,'w',32767);
    execute immediate 'truncate table detailsTable';
    insert into detailsTable(tableName,columnName,columnType,columnLenth) select table_name, column_name,data_type,data_length from user_tab_cols where table_name=tableName;
    select distinct 'create table '||tablename||' (' into fileBuffer from detailsTable where rownum>0;
    DBMS_OUTPUT.PUT_LINE(fileBuffer);
    open detailsCursor;
    loop 
        fetch detailsCursor into columnName1,columnType1,columnLenth1;
        exit when detailsCursor%NOTFOUND;
        if instr(columnName1,'$')<=0 then
            fileBuffer:=fileBuffer || columnName1 ||' '|| columnType1 ||'('||columnLenth1||'),';
            DBMS_OUTPUT.PUT_LINE(fileBuffer);
        end if;
    end loop;
    close detailsCursor;

    select length(fileBuffer)-1 into bufferLenth from DUAL;
    select substr(fileBuffer,0,bufferLenth ) into fileBuffer from dual;
    fileBuffer:= fileBuffer ||');';
    DBMS_OUTPUT.PUT_LINE(fileBuffer);
    UTL_FILE.PUT_LINE(outFile,fileBuffer);
    

    for cur_fname in (select cname,coltype from col where tname = upper(tableName) order by colno) 
    loop  
        if v_fields is null then  
            v_fields := cur_fname.cname||'||''';  
            
        else  
            v_fields := v_fields|| ',' || '''||' || cur_fname.cname || '||''';  
        end if;  
    end loop;  
    v_sql := 'select '||''''||'insert into '||tableName||' values(' || ''''||'||'||v_fields  || ')' ||'''' ||' from '||tableName;
    dbms_output.put_line(v_sql);  

    open tableCursor for v_sql;  
        loop  
            fetch tableCursor into v_row;  
            dbms_output.put_line(v_row); 
            exit when tableCursor%notfound;  
            v_instr := instr(v_row,'(');  
            v_ldata := substr(v_row,1,v_instr);  
            v_rdata := substr(v_row,v_instr+1);  
            v_instr := instr(v_rdata,')');  
            v_rdata := substr(v_rdata,1,v_instr-1);  
  
            v_count := 0;  
            loop  
                v_instr := instr(v_rdata,',');  
                exit when v_instr = 0;  
                v_cname := substr(v_rdata,1,v_instr-1);  
                v_rdata := substr(v_rdata,v_instr+1);  
                v_count := v_count + 1;   
                v_cname := formatfield(tableName,v_cname,v_count);  
                dbms_output.put_line('get from format:'||v_cname);  
                if v_count = 1 then  
                    v_ldata := v_ldata||v_cname;  
                else  
                    v_ldata := v_ldata||','||v_cname;  
                end if;  
            end loop;  
            if v_count = 1 then  
                v_ldata := v_ldata||formatfield(tableName,v_rdata,v_count+1)||');';  
            else  
                v_ldata := v_ldata||','||formatfield(tableName,v_rdata,v_count+1)||');';  
            end if;  
    
            dbms_output.put_line(v_ldata);
            UTL_FILE.PUT_LINE(outFile,v_ldata);
        end loop;      
    close tableCursor;

    UTL_FILE.FCLOSE(outFile);
END auto_generate;

其中使用函数formatField

create or replace function formatfield(v_tname varchar,v_cname varchar,v_colno number) return varchar  
as  
v_name varchar(3999);  
v_type varchar(99);  
begin  
    select coltype into v_type from col where tname = upper(v_tname) and colno = v_colno;  
    dbms_output.put_line(v_type);
    if v_type = 'DATE' then  
    dbms_output.put_line('before format:'||v_name);
        --v_name := ''''||to_char('v_cname ', 'yy-mm-dd')||'''';
        v_name:= '''' || to_char(to_date(v_cname),'dd-Month-yy') || '''';
        --v_name:= to_char(v_cname, 'dd-Month-yy');
        --v_name := 'to_date('||''''||v_cname||''''||','||''''||'yyyy-mm-dd hh:mi:ss'||''''||')';
        dbms_output.put_line('after format:'||v_name);
    elsif v_type = 'VARCHAR' then  
        v_name := ''''||v_cname||'''';  
    elsif v_type = 'VARCHAR2' then
        v_name := ''''||v_cname||'''';  
    else  
    v_name := v_cname;  
    end if; 
    dbms_output.put_line('after format:'||v_name);
    return v_name;    
end;

接下来对于函数和过程进行解释分析

在过程中,首先通过函数参数的文件名称和已经预先定义的路径打开文件,预备输出:

outFile:=UTL_FILE.FOPEN('D_OUTPUT',filePath,'w',32767);

为了清除之前使用过程可能遗留的数据,清除detailsTable中的数据:

execute immediate 'truncate table detailsTable';

首先使用游标和数据字典生成CREATE TABLE的语句

  1. 查询系统的数据字典user_tab_cols,将过程输入的表的列信息读入到detailsTaable
insert into detailsTable(tableName,columnName,columnType,columnLenth) select table_name, column_name,data_type,data_length from user_tab_cols where table_name=tableName;
  1. 使用动态SQL生成create table [tablename]的语句头部:
select distinct 'create table '||tablename||' (' into fileBuffer from detailsTable where rownum>0;
  1. 使用游标便利detailsTable,根据其中的内容逐步拼接完成create语句:
	open detailsCursor;
    loop 
        fetch detailsCursor into columnName1,columnType1,columnLenth1;
        exit when detailsCursor%NOTFOUND;
        if instr(columnName1,'$')<=0 then
            fileBuffer:=fileBuffer || columnName1 ||' '|| columnType1 ||'('||columnLenth1||'),';
        end if;
    end loop;
    close detailsCursor;
  1. 将末尾的后半括号以及分号拼接至语句上,完成拼接后输出至文件中:
fileBuffer:= fileBuffer ||');';
UTL_FILE.PUT_LINE(outFile,fileBuffer);

完成create语句的生成之后继续完成insert语句

在变量声明部分,事先声明动态游标:

    type curType is ref cursor;  
    tableCursor curType;  
  1. 查询系统col表,获得列的信息,并进行循环拼接成动态SQL的字段模式:

        for cur_fname in (select cname,coltype from col where tname = upper(tableName) order by colno) 
        loop  
            if v_fields is null then  
                v_fields := cur_fname.cname||'||''';  
                
            else  
                v_fields := v_fields|| ',' || '''||' || cur_fname.cname || '||''';  
            end if;  
        end loop;  
    

    而后继续拼接动态SQL的头部,将select等字段拼接成完整的动态SQL语句:

        v_sql := 'select '||''''||'insert into '||tableName||' values(' || ''''||'||'||v_fields  || ')' ||'''' ||' from '||tableName;
    

例如输入的表名若为T_STUD_J521,则生成的动态SQL语句为:

select 'insert into T_STUD_J521 values('||SNO||','||SNAME||','||SEX||','||TEL||','||EMAIL||','||BIRTHDAY||','||BNO||','||MNO||','||SUM_EVALUATION||')' from T_STUD_J521
  1. 对于已经生成的完整的动态SQL语句,通过动态游标执行并产生结果:
open tableCursor for v_sql;  
        loop  
            fetch tableCursor into v_row;  
            exit when tableCursor%notfound;

产生的结果如下:

insert into T_STUD_J521 values(8202180608 ,张八, 男, 17877781808, [email protected], 01-MAY-00,8202180608          ,02,100)

此时的语句尚未根据数据类型判断是否添加引号,并且日期格式存在一定问题。

  1. 处理不同字段是否需要增加引号的问题。

    首先通过读取括号的位置,得到插入的数值的子串:

                v_instr := instr(v_row,'(');  
                v_ldata := substr(v_row,1,v_instr);  
                v_rdata := substr(v_row,v_instr+1);  
                v_instr := instr(v_rdata,')');  
                v_rdata := substr(v_rdata,1,v_instr-1); 
    

    而后通过读取系统中对于不同字段的类型的记录,判断是否需要增加引号,这一过程在函数formatfield中完成:

        select coltype into v_type from col where tname = upper(v_tname) and colno = v_colno;  
        dbms_output.put_line(v_type);
        if v_type = 'DATE' then  
            v_name:= '''' || to_char(to_date(v_cname),'dd-Month-yy') || '''';
            dbms_output.put_line('after format:'||v_name);
        elsif v_type = 'VARCHAR' then  
            v_name := ''''||v_cname||'''';  
        elsif v_type = 'VARCHAR2' then
            v_name := ''''||v_cname||'''';  
        elsif v_type:='CHAR' then
            v_name := ''''||v_cname||'''';
        else  
        v_name := v_cname;  
        end if; 
        dbms_output.put_line('after format:'||v_name);
        return v_name;    
    

    特殊处理Date日期型、VARCHAR、VARCHAR2、CHAR等字节型数据:

    • 对于日期型数据将其转化为字符形式并拼接引号
    • 对于VARCHAR2,VARCHAR,CHAR等最常见的字符形式在其两侧添加引号
    • 对于其他数据直接输出源数据形式
  2. 收到从函数formatfield中获得的数据,即可进行拼接,对最后一个数值进行他叔处理,增加括号和末尾的分号:

                    v_cname := formatfield(tableName,v_cname,v_count);  
                    dbms_output.put_line('get from format:'||v_cname);  
                    if v_count = 1 then  
                        v_ldata := v_ldata||v_cname;  
                    else  
                        v_ldata := v_ldata||','||v_cname;  
                    end if;  
                end loop;
                
                if v_count = 1 then  
                    v_ldata := v_ldata||formatfield(tableName,v_rdata,v_count+1)||');';  
                else  
                    v_ldata := v_ldata||','||formatfield(tableName,v_rdata,v_count+1)||');';  
                end if;  
    
  3. 将转换、拼接完成的记录输入到文件中,完成过程:

                UTL_FILE.PUT_LINE(outFile,v_ldata);
            end loop;      
        close tableCursor;
    
        UTL_FILE.FCLOSE(outFile);
    END auto_generate;
    

五、 实验中遇到的问题及解决办法

  1. 在本次实验中,创建用户名仍然是一个问题,由于不修改部分设定无法创建不以C##开头的用户名,仍然需要使用命令:

    alter session set "_oracle_script"=true;
    

    才能创建一个任意字段开头的用户名,但这将导致该用户无法通过Drop语句删除。这一问题截至实验报告完成时仍未解决。

  2. 在按照实验指导书编写过程的时候遇到的第一个问题是在查找系统的user_tab_cols表时,会读入一些Oracle自行生成的隐藏字段,对于生成真实的CREATE语句带来了一定的影响。

    解决办法:

    经过观察发现这些隐藏字段多包含’$'这一特殊字符,使用if条件进行过滤即可最大程度的减少Oracle隐藏字段带来的影响:

    if instr(columnName1,'$')<=0 then
                fileBuffer:=fileBuffer || columnName1 ||' '|| columnType1 ||'('||columnLenth1||'),';
    
  3. 在处理根据动态游标生成的insert语句时,在处理字段是否增加引号这一问题时出现问题,在遍历原有的detailsTable表时出现异常,无法正确地判断字段的类型。

    解决办法:

    经过查询资料得知,可以通过系统内的数据字典col对于不同列的数据类型进行读取。见过尝试后,这一系统表可以成功读取字段对应的类型,实现了根据字段类型判断是否增加引号的功能。

  4. 在处理字段引号时,遇到的另一个问题为在处理日期型字段时倘若直接将读取获得的字段转化为char类型,将会出现错误,过程无法完成。

    解决办法:

    将读取的日期字段重新进行todate处理,再在其外层进行tochar处理,即可规避日期可能不规范和无法正确输出的问题:

        if v_type = 'DATE' then  
            v_name:= '''' || to_char(to_date(v_cname),'dd-Month-yy') || '''';
    

猜你喜欢

转载自blog.csdn.net/weixin_43366276/article/details/113143431