目 录CONTENT

文章目录

【GaussDB】从 sqlplus 到 gsql:Shell 中执行 SQL 文件方案的迁移与改造

DarkAthena
2026-01-01 / 0 评论 / 0 点赞 / 3 阅读 / 0 字

【GaussDB】从 sqlplus 到 gsql:Shell 中执行 SQL 文件方案的迁移与改造

背景

在客户应用系统中,有一套shell脚本来操作ORACLE数据库执行业务存储过程,现数据库需要迁移到GaussDB,因此这套shell脚本也要进行移植改写

原始代码demo

  1. 存储过程定义
create or replace procedure up_test_biz(i1 number,i2 varchar2,o1 out number,o2 out varchar2) is
i number;
begin
o1:=0;
i:=i1;
i:=i2;
exception when others then
o1:=sqlcode;
o2:=sqlerrm;
end;
/
  1. shell脚本
#!/bin/bash
# 参数赋值
v1=1
v2=2a
# 调用SQL文件
sqlplus -S -L system/SysPassword1@192.168.163.227:1527/pdb1 @biz.sql ${v1} ${v2}  >>biz.log
# 获取上一个命令成功失败结果
sqlresult="$?"
if [ ${sqlresult} -eq "0" ]
then 
    echo "biz 处理成功 ! " >>biz.log
else
    echo "biz 处理失败 ! " >>biz.log
fi
echo "sqlresult is:" ${sqlresult}
  1. sql文件

WHENEVER SQLERROR EXIT FAILURE
WHENEVER OSERROR EXIT FAILURE
VARIABLE O_CODE NUMBER;
VARIABLE O_MSG VARCHAR2(4000);

declare
i1 number;
i2 varchar2(20);
begin
i1:= '&1';
i2:= '&2';
up_test_biz(i1,i2,:O_CODE,:O_MSG);
exception when others then 
:O_CODE:=1;
end;
/
PRINT O_MSG
EXIT :O_CODE

QUIT;

一、原始代码关键点

存储过程定义

ORACLE的sqlcode是数字

shell文件

  1. sqlplus 执行sql文件
    使用@符号接SQL文件

  2. 传参给sql文件
    在sqlplus @SQL文件之后 ,按顺序给参数值

  3. 日志重定向
    使用>>符号重定向写入文件

  4. 命令执行结果标准值
    使用"$%"能判断sqlplus命令执行结果是否成功

SQL文件

  1. 规定命令退出时标准值
WHENEVER SQLERROR EXIT FAILURE
WHENEVER OSERROR EXIT FAILURE
  1. 声明会话级变量
VARIABLE O_CODE NUMBER;
VARIABLE O_MSG VARCHAR2(4000);
  1. sql接收命令行入参
    按顺序传递
i1:= '&1';
i2:= '&2';
  1. 存储过程出参赋给会话级变量以及会话级变量赋值
up_test_biz(i1,i2,:O_CODE,:O_MSG);
:O_CODE:=1;
  1. 打印变量
PRINT O_MSG
  1. 指定命令退出时标准值
EXIT :O_CODE

二、分析GaussDB支持情况

存储过程定义

GaussDB的sqlcode是字符类型,和ORACLE不一致,需要手动创建函数来显式转换(可参考文章https://www.darkathena.top/archives/opengauss-mogdb-sqlcode-datatype)。

shell文件

  1. gsql 执行sql文件
    使用 -f 指定sql文件

  2. 传参给sql文件
    使用 -v 指定参数

  3. 日志重定向
    可以使用>>,但是后面要接 2>&1 ,才能把标准错误也输出到日志文件中

  4. 命令执行结果标准值
    gsql默认无论执行成功还是报错,"$%"返回都是"0",需要加上参数 -v ON_ERROR_STOP=1 (或者在sql文件里执行\set ON_ERROR_STOP=1)

SQL文件

  1. 规定命令退出时标准值
    需要shell调用gsql时加上参数 -v ON_ERROR_STOP=1 (或者在sql文件里执行\set ON_ERROR_STOP=1) ,同上

  2. 声明会话级变量
    在gsql调用时指定-v 参数名="'值'" 或者sql文件里执行\set 参数名="'值'",但这种本质上只是SQL字符串替换,即不是绑定变量,也无法接收sql的执行结果,建议使用自定义变量select set_config('cust.变量名称','变量值',false)

  3. sql接收命令行入参
    使用 :参数名 接收,但plsql中不支持使用这种形式的变量,只能使用自定义变量(可参考文章https://www.darkathena.top/archives/openGauss-Unlocking-Hidden-Potential-Clever-Tricks-for-Custom-Parameters

select 
set_config('cust.v1',:v1,false) v1;

declare
i1 number;
begin
i1:=current_setting('cust.v1');

  1. 存储过程出参赋给会话级变量以及会话级变量赋值
    gaussdb由于 -v或者\set的变量无法接收sql的执行结果,因此只能使用自定义变量来处理,此时需要使用匿名块额外声明变量,先做出参接收,然后将这个变量的值赋给自定义变量
declare
i1 number;
i2 varchar2(20);
--增加出参接收的变量
o1 number; 
o2 varchar2(4000);
begin
i1:=current_setting('cust.v1');
i2:=current_setting('cust.v2');
up_test_biz(i1,i2,o1,o2);
--将出参赋值给自定义变量,以便脱离单条语句使用
set_config('cust.o_code',o1,false); 
set_config('cust.o_msg',o2,false); 

  1. 打印变量
    可以直接select 或者在匿名块内使用raise输出
select current_setting('cust.o_msg');
begin
raise notice '%',current_setting('cust.o_msg');
end;
/
  1. 当业务返回错误或者SQL执行错误时,shell侧需要能识别到命令成功或者失败
    gsql执行sql无论成功或者失败都是返回0,需要shell调用gsql时加上参数 -v ON_ERROR_STOP=1 (或者在sql文件里执行\set ON_ERROR_STOP=1),且由于匿名块最后捕获了错误,因此需要再根据匿名块记录的状态值来构造一个报错
begin
if current_setting('cust.o_code') <>'0' then
raise  '%',current_setting('cust.o_msg');
else 
raise notice '%',current_setting('cust.o_msg');
end if;
end;
/

三、整合方案

存储过程定义

--新建sqlcode转换函数
CREATE OR REPLACE FUNCTION pack_sql_state(sql_state text)
RETURNS INTEGER AS $$
DECLARE
    result INTEGER := 0;
    i INTEGER;
BEGIN
    FOR i IN 1..5 LOOP
        result := result << 6;
        result := result | (ascii(substr(sql_state, -i, 1)) - ascii('0'));
    END LOOP;
    RETURN result;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION unpack_sql_state(sql_state INTEGER)
RETURNS text AS $$
DECLARE
    result text := '';
    i INTEGER;
BEGIN
    FOR i IN 1..5 LOOP
        result := result||(chr((sql_state & 63) + ascii('0'))) ;
        sql_state := sql_state >> 6;
    END LOOP;
    RETURN result;
END;
$$ LANGUAGE plpgsql;

--存储过程改写(仅需识别sqlcode表达式,套一个转换函数即可,如果不想新建函数,也可以让o1赋值为1,把sqlcode拼接到o2里去)
create or replace procedure up_test_biz(i1 number,i2 varchar2,o1 out number,o2 out varchar2) is
i number;
begin
o1:=0;
i:=i1;
i:=i2;
o2:='biz success!';
exception when others then
o1:=pack_sql_state(sqlcode);
o2:=sqlerrm;
end;
/

shell脚本

#!/bin/bash
# 参数赋值
v1=1
v2=2a
# 调用SQL文件
gsql -h 192.168.163.131 -p 7456 -Uogadmin -WMogdb@123 -d postgres -f biz.sql -v ON_ERROR_STOP=on -v v1="'${v1}'" -v v2="'${v2}'" >>biz.log 2>&1
# 获取上一个命令成功失败结果
sqlresult="$?"
if [ ${sqlresult} -eq "0" ]
then 
    echo "biz 处理成功 ! " >>biz.log
else
    echo "biz 处理失败 ! " >>biz.log
fi
echo "sqlresult is:" ${sqlresult}

sql文件

\set ON_ERROR_STOP ON
--  -v参数只能替换sql中的参数,不能替换plsql中的参数,因此使用自定义变量中转
select 
set_config('cust.v1',:v1,false) v1,
set_config('cust.v2',:v2,false) v2;

declare
i1 number;
i2 varchar2(20);
o1 number;
o2 varchar2(4000);
begin
i1:=current_setting('cust.v1');
i2:=current_setting('cust.v2');
up_test_biz(i1,i2,o1,o2);
set_config('cust.o_code',o1,false);
set_config('cust.o_msg',o2,false);
exception when others then 
set_config('cust.o_code',sqlcode,false);
set_config('cust.o_msg',sqlerrm,false);
end;
/

--打印结果
select current_setting('cust.o_msg') as o_msg;

--处理标准命令返回值
begin
if current_setting('cust.o_code') <>'0' then
raise '%',concat('o_code:',unpack_sql_state(current_setting('cust.o_code')),', o_msg:',current_setting('cust.o_msg'));
end if;
end;
/

四、检查日志

构造输出执行报错日志

  1. oracle:

old   5: i1:= '&1';
new   5: i1:= '1';
old   6: i2:= '&2';
new   6: i2:= '2a';

PL/SQL procedure successfully completed.


O_MSG
--------------------------------------------------------------------------------
ORA-06502: PL/SQL: numeric or value error: character to number conversion error

biz 处理失败 ! 

  1. gaussdb:
 v1 | v2 
----+----
 1  | 2a
(1 row)

ANONYMOUS BLOCK EXECUTE
                    o_msg                    
---------------------------------------------
 invalid input syntax for type numeric: "2a"
(1 row)

gsql:biz.sql:73: ERROR:  o_code:22P02, o_msg:invalid input syntax for type numeric: "2a"
total time: 273  ms
biz 处理失败 ! 

构造输出执行正常日志

old   5: i1:= '&1';
new   5: i1:= '1';
old   6: i2:= '&2';
new   6: i2:= '2';

PL/SQL procedure successfully completed.


O_MSG
--------------------------------------------------------------------------------
biz success!

biz 处理成功 ! 

 v1 | v2 
----+----
 1  | 2
(1 row)

ANONYMOUS BLOCK EXECUTE
    o_msg     
--------------
 biz success!
(1 row)

ANONYMOUS BLOCK EXECUTE
total time: 276  ms
biz 处理成功 ! 

经核对,必要信息输出无遗漏,符合预期

五、总结

  1. 注意gsql执行sql的标准返回值,受参数 ON_ERROR_STOP影响,默认都是返回成功,即0 ,需设置ON_ERROR_STOP=on才会在错误时返回非0
  2. 注意gsql的错误会输出到标准错误,在标准输出中不含错误信息,如需记录错误到文件,需要 2>&1 ,将标准错误重定向到标出输出
  3. Gauss/PG系的plsql绑定变量出入参一直不太友好,好在能使用sql来进行会话级自定义变量的设置,跨越单条sql引用上下文,且不依赖客户端命令。
  4. GaussDB在Oracle兼容模式下的sqlcode是字符类型,和Oracle不兼容,且其中的确可能会包含非数字的字符,需要进行相关代码的改造。
0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin
博主关闭了所有页面的评论