ORACLE PL/SQL 对象、表数据对比功能存储过程简单实现
最近帮忙跟进个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个多小时才写出来,真的是捡一门丢一门,醉了。
ORACLE PL/SQL 对象、表数据对比功能存储过程简单实现的更多相关文章
- oracle pl sql 解锁表
select p.spid,a.serial#, c.object_name,b.session_id,b.oracle_username,b.os_user_name from v$pr ...
- ORACLE PL/SQL编程详解
ORACLE PL/SQL编程详解 编程详解 SQL语言只是访问.操作数据库的语言,并不是一种具有流程控制的程序设计语言,而只有程序设计语言才能用于应用软件的开发.PL /SQL是一种高级数据库程序设 ...
- Oracle——PL/SQL 语句
目录: 1.什么是PL/SQL 2.PL/SQL 语法基础 3.PL/SQL 实例 一.过程 实例 二.函数 实例 三.游标的使用 实例 四.动态sql 实例 五.触发器 实例 1.什么是 ...
- [强烈推荐]ORACLE PL/SQL编程详解之七:程序包的创建与应用(聪明在于学习,天才在于积累!)
原文:[强烈推荐]ORACLE PL/SQL编程详解之七:程序包的创建与应用(聪明在于学习,天才在于积累!) [强烈推荐]ORACLE PL/SQL编程详解之七: 程序包的创建与应用(聪明在于学习,天 ...
- ORACLE PL/SQL编程之八:把触发器说透
原文:ORACLE PL/SQL编程之八:把触发器说透 ORACLE PL/SQL编程之八: 把触发器说透 大家一定要评论呀,感谢!光发表就花了我将近一个下午. 本篇主要内容如下: 8.1 触发器类型 ...
- ORACLE PL/SQL编程之六:把过程与函数说透(穷追猛打,把根儿都拔起!)
原文:ORACLE PL/SQL编程之六:把过程与函数说透(穷追猛打,把根儿都拔起!) ORACLE PL/SQL编程之六: 把过程与函数说透(穷追猛打,把根儿都拔起!) 继上篇:ORACLE P ...
- ORACLE PL/SQL编程详解(转)
原帖地址:http://blog.csdn.net/chenjinping123/article/details/8737604 ORACLE PL/SQL编程详解 SQL语言只是访问.操作数据库的语 ...
- ORACLE PL/SQL:触发器
ORACLE PL/SQL 触发器 本篇主要内容如下: 8.1 触发器类型 8.1.1 DML触发器 8.1.2 替代触发器 8.1.3 系统触发器 8.2 创建触发器 8.2.1 触发器触发次序 8 ...
- oracle pl/sql 程序设计 历史笔记整理
20131016 周三 oracle pl/sql 程序设计 第2章 创建并运行pl/sql代码 sqlplus yjkhecc/yjkhecc@10.85.23.92:1521/orcl 在java ...
- [推荐]ORACLE PL/SQL编程之五:异常错误处理(知已知彼、百战不殆)
原文:[推荐]ORACLE PL/SQL编程之五:异常错误处理(知已知彼.百战不殆) [推荐]ORACLE PL/SQL编程之五: 异常错误处理(知已知彼.百战不殆) 继上三篇:ORACLE PL/S ...
随机推荐
- C#使用WebView2替代Electron
C#想要实现Electron那样混合桌面程序可以用以下几个库.本文使用EdgeSharp NanUIgithub.com/NetDimension/NanUI Photinogithub.com/ ...
- Deepin15.11+WIN10 双系统安装过程与遇到的问题(一)
一.deepin安装流程 1.下载 下载深度系统最新版本官网https://www.deepin.org/zh/download/下载深度系统专用U盘启动盘制作工具https://www.deepin ...
- MySQL学习笔记-多表查询(下)
多表查询(下) 一. 联合查询 联合查询:将多次查询结果合并,形成新的查询结果集 select {字段列表} from {表A} ... union [all] select {字段列表} from ...
- ABC353
不知道为啥有断更了一周... E woc,怎么跟我出的题目这么像 先把字符串扔到一个 Trie 里面,然后对于每一个点我们考虑这一个点到根节点组成的字符串能是多少对字符串的最长公共前缀. 我们定义 \ ...
- 夜莺监控发布 v6.7 版本,推送部分商业版功能
熟悉夜莺的小伙伴都知道夜莺分为开源版.专业版.企业版,三个版本良性发展.近期夜莺团队发布了 v6.7 版本,把机器Metadata管理功能推送到了开源版,下面是该功能的简单介绍. 如上图,机器列表页面 ...
- C++面向对象语言自制多级菜单
因为要做一个小应用,需要一个菜单类,在网上找了许久,也没有找到一款心仪的菜单类,索性用C++语言,自制一个命令行级别的菜单类,并制作成库,现记录下来,供以后借鉴. 一.特性 无限制条目 无限制层级 用 ...
- R语言求取大量遥感影像的平均值、标准差:raster库
本文介绍基于R语言中的raster包,批量读取多张栅格图像,对多个栅格图像计算平均值.标准差,并将所得新的栅格结果图像保存的方法. 在文章基于R语言的raster包读取遥感影像中,我们介绍了基 ...
- 阿里云 腾讯云上搭建Samba服务
对于这个主题,鄙人走了很久的坑,最后很抱歉的告诉你. 运营商把Samba服务的端口全部封掉了,所以你根本就没办法访问! 那怎么办,我Windows空间不够,又不想浪费云上的资源. 那就用FTP代替它吧 ...
- Java面试知识点(三)Java中的单继承和多继承
多继承的优缺点 优点:对象可以调用多个父类中的方法 缺点:如果派生类所继承的多个父类有相同的父类(也就是一个菱形继承结构),而派生类对象需要调用这个祖先类的方法,就会容易出现二义性. 1.java 与 ...
- vba--数组
Sub shishi() Range("e2") = Split(Range("e1"), "-")(0) '用短横线分隔后取第1个值 En ...