首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

ORACLE PL/SQL 对象、表数据对比功能存储过程简单实现

编程知识
2024年07月28日 10:55

最近帮忙跟进个oracle11g upgrade 升级到19c 的项目,由于业主方不太熟悉oracle upgrade相关升级流程,以及升级影响范围相关的事项,担心应用停机升级以后会导致数据库保存的业务数据不一致。🙂‍↔️

虽然我们一直跟业主方强调,upgrade 升级只会升级oracle 二进制安装文件和数据库系统数据字典相关的内容进行升级,不会修改业务数据,而且我们是迁移+升级不会修改源库内容,有回退方案。😎

但是业主表示需要提供数据验证的方案,这个流程不能缺,没办法只能提供数据对比方案:

  • 通过生产的备份集+归档搭建2套DG库,A库用于升级19C,B库用于保留原始数据。
  • A库升级完成以后搭建DBLINK和B库进行数据比对,查询A库升级后和B库数据是否会不一致。

以下提供两个存储过程来判断校验升级前后数据是否有差异:

  • diff_plobj_proc:该存储过程对比源端、目标端业务用户所有的对象(PL/SQL对象,表,视图,序列等等)。
  • diff_row_proc:该存储过程对比源端、目标端业务用户所有的表对象的数据是否一致,差集如果不等于0为数据不一致。

 

diff_plobj_proc 过程代码:

--------------------------------注意:--------------------------------------------
diff_plobj_proc 过程建议在目标端实例(19C)上SYS、system 或者具有DBA角色的用户执行。
因为 diff_plobj_proc 存储过程逻辑没有考虑到业务用户对于系统表访问权限这块的访问,
所以目标端(19C)创建访问源端(11G)的DBLINK使用的数据库用户最好是有SYSTEM 或者 SYS 或者 DBA的权限,
才能访问DBA_系列的系统试图。
--------------------------------注意:--------------------------------------------

drop TABLE diff_plobj_table;

CREATE TABLE diff_plobj_table(
                                 source_schema_name           VARCHAR2(200),
                                 source_obj_name              VARCHAR2(200)   ,
                                 source_obj_type              VARCHAR2(200)  ,
                                 source_status                 VARCHAR2(200)  ,
                                 target_schema_name            VARCHAR2(200)  ,
                                 target_obj_name               VARCHAR2(200)  ,
                                 target_obj_type               VARCHAR2(200) ,
                                 target_status                 VARCHAR2(200),
                                 proc_exce_time                DATE
);
COMMENT ON TABLE diff_plobj_table IS 'plsql对象比对表';
COMMENT ON COLUMN diff_plobj_table.source_schema_name       IS 'source_schema_name 源端业务模式名';
COMMENT ON COLUMN diff_plobj_table.source_obj_name          IS 'source_obj_type 源端对象名称';
COMMENT ON COLUMN diff_plobj_table.source_obj_type          IS 'source_obj_type 源端对象类型';
COMMENT ON COLUMN diff_plobj_table.source_status            IS 'source_status   源端对象状态';
COMMENT ON COLUMN diff_plobj_table.target_schema_name       IS 'target_schema_name 目标端业务模式名';
COMMENT ON COLUMN diff_plobj_table.target_obj_name          IS 'target_obj_type 目标端对象名称';
COMMENT ON COLUMN diff_plobj_table.target_obj_type          IS 'target_obj_type 目标端对象类型';
COMMENT ON COLUMN diff_plobj_table.target_status            IS 'target_status 目标端对象状态';
COMMENT ON COLUMN diff_plobj_table.proc_exce_time            IS 'diff_plobj_proc 存储过程运行的时间';




-- diff_plobj_proc 过程对比源端、目标段的业务用户的对象
CREATE OR REPLACE PROCEDURE diff_plobj_proc(
    p_source_db_link_name    IN     VARCHAR2,
    p_source_schema_name     IN     VARCHAR2,
    p_target_schema_name     IN     VARCHAR2
) IS
    /* 统一将传进来的参数转换成大写 */
    v_source_db_link_name VARCHAR2(200) := '@' || UPPER(p_source_db_link_name);
    v_source_schema_name  VARCHAR2(200) := UPPER(p_source_schema_name);
    v_target_schema_name  VARCHAR2(200) := UPPER(p_target_schema_name);

    v_sql            VARCHAR2(4000);
    v_link_name_flag VARCHAR2(10);

    -- 定义个 ref 游标变量用来接收 v_sql 返回的结果集
    v_sql_ref_cursor SYS_REFCURSOR;

    -- 定义个 record 类型用来保存 v_sql 返回的结果集
    TYPE v_sql_record_type IS RECORD (
        source_schema_name  VARCHAR2(500),
        source_obj_name     VARCHAR2(500),
        source_obj_type     VARCHAR2(500),
        source_status       VARCHAR2(500),
        target_schema_name  VARCHAR2(500),
        target_obj_name     VARCHAR2(500),
        target_obj_type     VARCHAR2(500),
        target_status       VARCHAR2(500),
        proc_exce_time      DATE
    );
    v_sql_record_row v_sql_record_type;
BEGIN
    EXECUTE IMMEDIATE 'TRUNCATE TABLE diff_plobj_table';

    /* 判断传入的 v_source_db_link_name 是否有效,如果无效报错 */
    BEGIN
        v_sql := 'SELECT 1 FROM DUAL' || v_source_db_link_name;
        EXECUTE IMMEDIATE v_sql INTO v_link_name_flag;
    EXCEPTION
        WHEN OTHERS THEN
            raise_application_error(-20001, v_source_db_link_name || ':dblink无效,请检查dblink是否可用!');
    END;

    /* 动态 SQL 拼接 */
    v_sql := 'SELECT DISTINCT ' ||
            'a.owner AS source_schema_name, ' ||
            '(CASE ' ||
            '    WHEN a.object_type = ''INDEX'' THEN ' ||
            '        (SELECT LISTAGG(''IDX_'' || COLUMN_NAME, ''_'') WITHIN GROUP (ORDER BY COLUMN_POSITION) ' ||
            '         FROM ALL_IND_COLUMNS aic ' ||
            '         WHERE INDEX_NAME = a.object_name) ' ||
            '    ELSE a.object_name ' ||
            '   END) AS source_obj_name, ' ||
            'a.object_type AS source_obj_type, ' ||
            'a.status AS source_status, ' ||
            'b.owner AS target_schema_name, ' ||
            '(CASE ' ||
            '    WHEN b.object_type = ''INDEX'' THEN ' ||
            '        (SELECT LISTAGG(''IDX_'' || COLUMN_NAME, ''_'') WITHIN GROUP (ORDER BY COLUMN_POSITION) ' ||
            '         FROM ALL_IND_COLUMNS bic ' ||
            '         WHERE INDEX_NAME = b.object_name) ' ||
            '    ELSE b.object_name ' ||
            '   END) AS target_obj_name, ' ||
            'b.object_type AS target_obj_type, ' ||
            'b.status AS target_status, ' ||
            'SYSDATE AS proc_exce_time ' ||
            'FROM (SELECT owner, object_name, object_type, status FROM DBA_OBJECTS' || v_source_db_link_name || ' WHERE owner = ''' || v_source_schema_name || ''') a ' ||
            'FULL JOIN (SELECT owner, object_name, object_type, status FROM DBA_OBJECTS WHERE OWNER = ''' || v_target_schema_name || ''') b ' ||
            'ON a.OBJECT_NAME = b.OBJECT_NAME AND a.OBJECT_TYPE = b.OBJECT_TYPE';
    

    -- 打开游标并执行动态 SQL
    OPEN v_sql_ref_cursor FOR v_sql;

    -- 使用 LOOP 循环遍历游标
    LOOP
        FETCH v_sql_ref_cursor INTO v_sql_record_row;
        EXIT WHEN v_sql_ref_cursor%NOTFOUND;

        -- 插入 diff_plobj_table 表
        INSERT INTO diff_plobj_table VALUES v_sql_record_row;
    END LOOP;
    CLOSE v_sql_ref_cursor;

    COMMIT;
END;
/



-- 执行存储过程 ,我的环境 源端和目标端的 业务SCHEMA 不一样,生产环境是一样的。
BEGIN
    diff_plobj_proc(
        p_source_db_link_name => 'SCOTT2_LINK',
        p_source_schema_name  => 'SCOTT2',
        p_target_schema_name  => 'SCOTT3');
END;
/


-- 查询表 diff_plobj_table 能获取源端和目标端的对象明细。
SELECT * FROM diff_plobj_table;




-- 查询源端、目标端对象数据比对
SELECT *
FROM (SELECT COUNT(1) source_cnt,
             source_obj_type
      FROM diff_plobj_table
      GROUP BY source_obj_type) so
         FULL JOIN (SELECT COUNT(1) target_cnt,
                           TARGET_OBJ_TYPE
                    FROM diff_plobj_table
                    GROUP BY TARGET_OBJ_TYPE) tg ON so.SOURCE_OBJ_TYPE = tg.TARGET_OBJ_TYPE
ORDER BY 1;

 

diff_row_proc 过程代码:

--------------------------------注意:--------------------------------------------
diff_row_proc 过程建议在目标端实例(19C)上SYS、system 或者具有DBA角色的用户执行。
因为 diff_row_proc 存储过程逻辑没有考虑到业务用户对于系统表访问权限这块的访问,
所以目标端(19C)创建访问源端(11G)的DBLINK使用的数据库用户最好是有SYSTEM 或者 SYS 或者 DBA的权限,
才能访问DBA_系列的系统试图。
--------------------------------注意:--------------------------------------------

drop TABLE diff_row_table;

CREATE TABLE diff_row_table(
                                 source_schema_name                  VARCHAR2(200),
                                 source_table_name                   VARCHAR2(200)   ,
                                 source_table_row_total_cnt         INT,
                                 target_schema_name                    VARCHAR2(200)  ,
                                 target_table_name                   VARCHAR2(200)  ,
                                 target_table_row_total_cnt         INT ,
                                 source_target_diff_row             INT,
                                 proc_exce_time                        DATE
);
COMMENT ON TABLE diff_row_table IS '对比源端、目标段表行数据差异表';
COMMENT ON COLUMN diff_row_table.source_schema_name            IS 'source_schema_name 源端业务模式名';      
COMMENT ON COLUMN diff_row_table.source_table_name             IS 'source_table_name 源端表对象名称';      
COMMENT ON COLUMN diff_row_table.source_table_row_total_cnt    IS 'source_table_row_total_cnt 源端表对象行数统计';  
COMMENT ON COLUMN diff_row_table.target_schema_name             IS 'target_schema_name 目标端业务模式名';      
COMMENT ON COLUMN diff_row_table.target_table_name             IS 'target_table_name 目标端表对象名称';      
COMMENT ON COLUMN diff_row_table.target_table_row_total_cnt    IS 'target_table_row_total_cnt 目标端表对象行数统计';     
COMMENT ON COLUMN diff_row_table.source_target_diff_row        IS '源端和目标端表对比行数差异,通过主键对比';     
COMMENT ON COLUMN diff_row_table.proc_exce_time                IS 'diff_row_table 存储过程运行的时间';



-- 存储过程 diff_row_proc
CREATE OR REPLACE PROCEDURE diff_row_proc (
    p_source_db_link_name IN VARCHAR2,
    p_source_schema_name  IN VARCHAR2,
    p_target_schema_name  IN VARCHAR2
) AS
    v_source_db_link_name VARCHAR2(200) := '@' || UPPER(p_source_db_link_name);
    v_source_schema_name  VARCHAR2(200) := UPPER(p_source_schema_name);
    v_target_schema_name  VARCHAR2(200) := UPPER(p_target_schema_name);
    v_link_name_flag      CHAR;
    v_sql                 VARCHAR2(4000);

    -- 保存源端表表名的数组变量
    v_source_table_name_arr DBMS_SQL.VARCHAR2_TABLE;

    -- 保存目标端表名的数组变量
    v_target_table_name_arr DBMS_SQL.VARCHAR2_TABLE;

    -- 获取源端业务用户所有表名SQL变量(动态SQL)
    v_source_table_sqlstr VARCHAR2(4000);

    -- 获取目标端业务用户所有表名SQL变量(动态SQL)
    v_target_table_sqlstr VARCHAR2(4000);

    -- 定义 ref 游标变量用来接收 v_source_table_sqlstr 返回的结果集
    v_source_sql_ref_cursor SYS_REFCURSOR;

    -- 定义 ref 游标变量用来接收 v_target_table_sqlstr 返回的结果集
    v_target_sql_ref_cursor SYS_REFCURSOR;

    -- 定义变量存储行数和 MINUS 操作的结果计数
    v_source_row_count PLS_INTEGER;
    v_target_row_count PLS_INTEGER;
    v_diff_count PLS_INTEGER;
    v_match_found BOOLEAN := FALSE;

BEGIN
    EXECUTE IMMEDIATE 'TRUNCATE TABLE DIFF_ROW_TABLE';

    /* 判断传入的 v_source_db_link_name 是否有效,如果无效报错 */
    BEGIN
        v_sql := 'SELECT 1 FROM DUAL' || v_source_db_link_name;
        EXECUTE IMMEDIATE v_sql INTO v_link_name_flag;
    EXCEPTION
        WHEN OTHERS THEN
            raise_application_error(-20001, v_source_db_link_name || ':dblink无效,请检查dblink是否可用!');
    END;

    -- 源端获取业务表名 SQL
    v_source_table_sqlstr := 'SELECT a.TABLE_NAME FROM DBA_TABLES' ||
                             v_source_db_link_name || ' a WHERE a.owner = ''' ||
                             v_source_schema_name || ''' ORDER BY NUM_ROWS';

    DBMS_OUTPUT.PUT_LINE(v_source_table_sqlstr);
    -- 目标端获取业务表名 SQL
    v_target_table_sqlstr := 'SELECT a.TABLE_NAME FROM DBA_TABLES' ||
                             ' a WHERE a.owner = ''' ||
                             v_target_schema_name || ''' ORDER BY NUM_ROWS';

    -- 打开游标并执行动态 SQL
    OPEN v_source_sql_ref_cursor FOR v_source_table_sqlstr;
    OPEN v_target_sql_ref_cursor FOR v_target_table_sqlstr;

    -- 使用 BULK COLLECT INTO 将结果集批量插入数组
    FETCH v_source_sql_ref_cursor BULK COLLECT INTO v_source_table_name_arr;
    FETCH v_target_sql_ref_cursor BULK COLLECT INTO v_target_table_name_arr;

    -- 关闭游标
    CLOSE v_source_sql_ref_cursor;
    CLOSE v_target_sql_ref_cursor;

    -- 比较表名并进行 MINUS 操作
    FOR i IN 1..v_source_table_name_arr.COUNT LOOP
            v_match_found := FALSE; -- 重置标志
            FOR j IN 1..v_target_table_name_arr.COUNT LOOP
                    IF v_source_table_name_arr(i) = v_target_table_name_arr(j) THEN
                        v_match_found := TRUE;

                        -- 获取源端表行数
                        v_sql := 'SELECT COUNT(*) FROM ' || v_source_schema_name || '.' || v_source_table_name_arr(i) || v_source_db_link_name;
                        EXECUTE IMMEDIATE v_sql INTO v_source_row_count;

                        -- 获取目标端表行数
                        v_sql := 'SELECT COUNT(*) FROM ' || v_target_schema_name || '.' || v_target_table_name_arr(j);
                        EXECUTE IMMEDIATE v_sql INTO v_target_row_count;

                        -- 进行 MINUS 操作并存储结果
                        BEGIN
                            BEGIN
                                v_sql := 'SELECT /*+ PARALLEL(8) */COUNT(*) FROM (' ||
                                         'SELECT * FROM ' || v_source_schema_name || '.' || v_source_table_name_arr(i) || v_source_db_link_name ||
                                         ' MINUS ' ||
                                         'SELECT * FROM ' || v_target_schema_name || '.' || v_target_table_name_arr(j) ||
                                         ')';
                                EXECUTE IMMEDIATE v_sql INTO v_diff_count;
                            EXCEPTION
                                WHEN OTHERS THEN
                                    v_diff_count := -999;
                            END;
                        END;

                        -- 将结果插入 diff_row_table
                        INSERT INTO diff_row_table (
                            source_schema_name,
                            source_table_name,
                            source_table_row_total_cnt,
                            target_schema_name,
                            target_table_name,
                            target_table_row_total_cnt,
                            source_target_diff_row,
                            proc_exce_time
                        ) VALUES (
                                     v_source_schema_name,
                                     v_source_table_name_arr(i),
                                     v_source_row_count,
                                     v_target_schema_name,
                                     v_target_table_name_arr(j),
                                     v_target_row_count,
                                     v_diff_count,
                                     SYSDATE
                                 );

                        COMMIT;

                        -- 跳出内层循环
                        EXIT;
                    END IF;
                END LOOP;

            -- 如果未找到匹配的表名,输出提示信息并插入记录
            IF NOT v_match_found THEN
                DBMS_OUTPUT.PUT_LINE('Source table ' || v_source_table_name_arr(i) || ' has no matching target table');

                -- 插入没有匹配表的记录
                INSERT INTO diff_row_table (
                    source_schema_name,
                    source_table_name,
                    source_table_row_total_cnt,
                    target_schema_name,
                    target_table_name,
                    target_table_row_total_cnt,
                    source_target_diff_row,
                    proc_exce_time
                ) VALUES (
                             v_source_schema_name,
                             v_source_table_name_arr(i),
                             NULL, -- 源端表行数
                             v_target_schema_name,
                             NULL, -- 目标端表名
                             NULL, -- 目标端表行数
                             NULL, -- 源端和目标端表对比行数差异
                             SYSDATE
                         );
                COMMIT;
            END IF;
        END LOOP;

END diff_row_proc;


-- 调用 diff_row_proc 比对源端和目标端的数据 ,该存储过程需要跑一段时间(可能很久)
BEGIN
    diff_row_proc( 
    p_source_db_link_name => 'SCOTT2_LINK', 
    p_source_schema_name =>  'SCOTT2', 
    p_target_schema_name => 'SCOTT3'
    );
END;
/


-- 期间可以持续观察 diff_row_table 表
-- 如果  SOURCE_TARGET_DIFF_ROW 有 -999 的值,则表示源端表和目标端表包含大字段,需要手工获取主键或者唯一列进行MINUS进行差集比对。
-- 如果  SOURCE_TARGET_DIFF_ROW 有 > 0 的值,则表示源端表和目标端表数据不一致。
SELECT * FROM  diff_row_table WHERE SOURCE_TARGET_DIFF_ROW <> 0;

最近一直在写c,pl/sql 也好久没写了,上面俩过程花了哥3个多小时才写出来😅,真的是捡一门丢一门,醉了😵‍💫。

 

From:https://www.cnblogs.com/yuzhijian/p/18328065
本文地址: http://shuzixingkong.net/article/508
0评论
提交 加载更多评论
其他文章 Scratch作品-巴黎2024奥运会
​ 《Scratch作品-巴黎2024奥运会》是一款以巴黎2024年奥运会为主题的互动作品,专为儿童和青少年设计。通过Scratch编程语言,这个作品生动地再现了奥运会的精彩瞬间,结合了动画、声音和互动元素,让用户仿佛置身于巴黎的奥运赛场。玩家可以参与各种虚拟的奥运项目,学习奥运精神,了解各国文化,
Scratch作品-巴黎2024奥运会 Scratch作品-巴黎2024奥运会
洛谷P1098 [NOIP2007 提高组] 字符串的展开
题目链接:- P1098 [NOIP2007 提高组] 字符串的展开 题目叙述: [NOIP2007 提高组] 字符串的展开 题目描述 在初赛普及组的“阅读程序写结果”的问题中,我们曾给出一个字符串展开的例子:如果在输入的字符串中,含有类似于 d-h 或者 4-8 的字串,我们就把它当作一种简写,输
Diffutoon下载介绍:真人视频转动漫工具,轻松获得上千点赞
最近在刷短视频的时候,偶尔能看到一些真人转动漫风的作品,看起来给人一种新鲜感,流量都还不错,简简单单跳个舞,就能获得上千个点赞~ 那么,这种视频是怎么制作的? 本期给大家介绍一款AI转绘工具Diffutoon,可以将逼真的视频转换成动画风格,不仅能够处理高分辨率和快速运动的视频,还能确保整个视频的风
Diffutoon下载介绍:真人视频转动漫工具,轻松获得上千点赞 Diffutoon下载介绍:真人视频转动漫工具,轻松获得上千点赞 Diffutoon下载介绍:真人视频转动漫工具,轻松获得上千点赞
c语言模拟Python的命名参数
最近在书里看到的,让c语言去模拟其他语言里有的命名函数参数。觉得比较有意思所以记录一下。 目标 众所周知c语言里是没有命名函数参数这种东西的,形式参数虽然有自己的名字,但传递的时候并不能通过这个名字来指定参数的值。 而支持命名参数的语言,比如python里,我们能让代码达到这种效果: def k_f
【WPF】Command 的一些使用方案
Command,即命令,具体而言,指的是实现了&#160;ICommand 接口的对象。此接口要求实现者包含这些成员: 1、CanExecute 方法:确定该命令是否可以执行,若可,返回 true;若不可,返回 false; 2、CanExecuteChanged 事件:发送命令(命令源)的控件可以
【WPF】Command 的一些使用方案 【WPF】Command 的一些使用方案 【WPF】Command 的一些使用方案
周期信号的傅里叶级数和频谱
傅里叶级数和信号频谱 对于一个确定的时域信号,我们只需要知道它的函数表达式就可以在任意时刻确定一个信号,但是各种场景下中我们需要的往往并不是这样的解析式,因为这些复杂的式子首先难以快速准确地获得,另外难以进行快速进行分析,其中所蕴含的信息也难以提取。因此需要一种更高效的工具来进行信号的分析。 傅里叶
周期信号的傅里叶级数和频谱 周期信号的傅里叶级数和频谱 周期信号的傅里叶级数和频谱
前端如何处理后端一次性返回10万条数据?
在前端开发中,我们经常需要处理后端返回的大量数据。假设后端一次性返回10万条数据,直接在浏览器中处理和展示这些数据会导致性能问题,比如页面卡顿、内存占用过高等。本文将结合Vue项目实战,介绍如何有效地处理和展示大数据集的方法。 1. 后端数据处理 首先,确保后端在传输数据时是经过压缩的,可以大大减少
攻坚克难岁月长,自主腾飞世界强——回顾近代中国数据库的发展与飞跃
前言 最近看了《中国数据库前世今生》纪录片,感触颇深,也是一直在思考到底该用何种方式起笔来回顾这段筚路蓝缕却又充满民族自豪感的历程。大概构思了一周左右吧,我想,或许还是应该从那个计算机技术在国内刚刚萌芽的年代开始讲起,那时的一切都显得那么原始而纯粹,一群怀揣梦想的科研人员,在资源匮乏、条件艰苦的环境
攻坚克难岁月长,自主腾飞世界强——回顾近代中国数据库的发展与飞跃 攻坚克难岁月长,自主腾飞世界强——回顾近代中国数据库的发展与飞跃 攻坚克难岁月长,自主腾飞世界强——回顾近代中国数据库的发展与飞跃