我们的数据迁移脚本利用匿名PL / SQL块来帮助整理代码,主要是因为我们可以将创建和更新的用户ID列设置为“系统”用户。
我们的迁移脚本类似于:
DECLARE
v_user_id users.id%TYPE;
BEGIN
SELECT id INTO v_user_id FROM users WHERE username = 'system';
UPDATE table1
SET col1 = value,
updated_at = SYSDATE,
updated_by = v_user_id
WHERE some condition;
INSERT INTO table2 (val1, SYSDATE, v_user_id);
END;
/
更新记录的用户是来自users表的数字ID,而不是字符串username。这是我们的数据建模团队的要求,否则我只需硬编码“系统”帐户的用户名。
作为附加说明,我们的DBA运行脚本,并且他不应该是显示为更新或插入记录的人/用户的脚本。来自更大的企业环境的另一个要求。
我希望从sqlplus命令行看到的输出类似于:
Updated X rows
Inserted Y rows
就像你在PL / SQL块之外运行INSERT和UPDATE语句一样。
我真的希望找到一个不需要显式调用DBMS_OUTPUT.PUT_LINE
的解决方案。
如何在没有为每个语句显式调用DBMS_OUTPUT.PUT_LINE
的情况下自动在匿名PL / SQL块中显示每个DML语句的输出?
答案 0 :(得分:4)
复合触发器可以计算并显示修改的行数,而无需更改原始代码。
这里仍然存在一些问题和挑战。这个解决方案可能不会使用并行DML - 它要么不能正常计数,要么触发器会阻止直接路径写入。它可能适用于多用户环境,但需要进行测试。您还需要为DELETE创建代码,也可能是MERGE。这可能会减慢DML。
create table users(id number, username varchar2(100));
insert into users values(1, 'system');
create table table1(col1 number, updated_at date, updated_by number);
insert into table1 values(1, null, null);
insert into table1 values(2, null, null);
create table table2(col1 number, updated_at date, updated_by number);
不断打印输出可能会导致问题。所以我们想默认禁用输出。你可能不想简单地使用DBMS_OUTPUT.DISABLE
,这可能会关闭其他东西,而且很难永远记住这样做。
使用全局变量创建一个简单的包。
create or replace package print_feedback is
--Outputing large amounts of data can sometimes break things.
--Only enable DBMS_OUTPUT when explicitly requested.
g_print_output boolean := false;
end;
/
在运行导入之前将其设置为TRUE
。
--Run this block first to enable printing.
begin
print_feedback.g_print_output := true;
end;
/
此代码动态生成触发器以捕获INSERT和UPDATE。
动态PL / SQL有点棘手。请注意我使用模板和替代引用机制来避免连接地狱。一旦理解了这些技巧,代码就会变得相对可读。 (希望您的IDE了解q'[
如何比StackOverflow语法高亮显示更好。)
--Create automatic UPDATE and INSERT feedback triggers.
declare
c_sql_template constant varchar2(32767) :=
q'[
create or replace trigger #TABLE_NAME#_#UPD_or_INS#_trg for #UPDATE_OR_INSERT# on #TABLE_NAME# compound trigger
--Purpose: Print a feedback message after updates and inserts.
g_count number := 0;
after each row is
begin
g_count := g_count + 1;
end after each row;
after statement is
begin
if print_feedback.g_print_output then
if g_count = 1 then
dbms_output.put_line('#Inserted_or_Updated# '||g_count||' row in #TABLE_NAME#');
else
dbms_output.put_line('#Inserted_or_Updated# '||g_count||' rows in #TABLE_NAME#');
end if;
end if;
end after statement;
end;
]';
v_sql varchar2(32767);
begin
--Loop through the relevant tables
for tables in
(
select table_name
from user_tables
where table_name in ('TABLE1', 'TABLE2')
order by table_name
) loop
--Create and execute update trigger.
v_sql := replace(replace(replace(replace(c_sql_template
, '#TABLE_NAME#', tables.table_name)
, '#UPD_or_INS#', 'upd')
, '#UPDATE_OR_INSERT#', 'update')
, '#Inserted_or_Updated#', 'Updated');
execute immediate v_sql;
--Create and execute insert trigger.
v_sql := replace(replace(replace(replace(c_sql_template
, '#TABLE_NAME#', tables.table_name)
, '#UPD_or_INS#', 'ins')
, '#UPDATE_OR_INSERT#', 'insert')
, '#Inserted_or_Updated#', 'Inserted');
execute immediate v_sql;
end loop;
end;
/
现在,您未更改的脚本将显示一些输出。 (我确实对脚本进行了一些微不足道的更改,但只是让它可以运行。)
SQL> --Run this block first to enable printing.
SQL> set serveroutput on;
SQL> begin
2 print_feedback.g_print_output := true;
3 end;
4 /
PL/SQL procedure successfully completed.
SQL> DECLARE
2 v_user_id users.id%TYPE;
3 BEGIN
4 SELECT id INTO v_user_id FROM users WHERE username = 'system';
5
6 UPDATE table1
7 SET col1 = 1,--value,
8 updated_at = SYSDATE,
9 updated_by = v_user_id
10 WHERE 1=1;--some condition;
11
12 INSERT INTO table2 values(2/*val1*/, SYSDATE, v_user_id);
13 END;
14 /
Updated 2 rows in TABLE1
Inserted 1 row in TABLE2
PL/SQL procedure successfully completed.
SQL>
答案 1 :(得分:2)
我不确定是否有一些oracle参数或配置要更改,以便您的PL / SQL可以按照您想要的方式工作,但您可以创建一个接受DML语句并运行该DML语句的过程。见下面的示例,
DECLARE
v_var VARCHAR2(10);
PROCEDURE run_dml (p_dml VARCHAR2)
IS
BEGIN
EXECUTE IMMEDIATE p_dml;
DBMS_OUTPUT.PUT_LINE(p_dml);
DBMS_OUTPUT.PUT_LINE(sql%rowcount||' rows '||REPLACE(LOWER(TRIM(SUBSTR(p_dml, 1, 6)))||'ed.', 'eed', 'ed'));
END;
BEGIN
v_var := 'hello too';
run_dml(q'[INSERT INTO test1_log VALUES ('hello')]');
run_dml(q'[DELETE FROM test1_log WHERE log1 = 'hello']');
run_dml(q'[UPDATE test1_log SET log1 = 'hello1']');
run_dml('INSERT INTO test1_log VALUES('''||v_var||''')');
END;
/
INSERT INTO test1_log VALUES ('hello')
1 rows inserted.
DELETE FROM test1_log WHERE log1 = 'hello'
1 rows deleted.
UPDATE test1_log SET log1 = 'hello1'
1 rows updated.
INSERT INTO test1_log VALUES('hello too')
1 rows inserted.
答案 2 :(得分:0)
SQL * Plus通过检查OCI返回状态获取有关受影响的行数等的状态信息。 OCI不是我的区域,但我很确定在PL / SQL块的情况下,它所拥有的唯一信息是提交块的事实以及它是成功还是失败,因为块被提交给了服务器作为单个单元,并且调用接口中没有结构记录其中的每个步骤以及状态和受影响的行计数。根本没有捕获该信息的机制。 (对于Oracle来说,实现这样的接口也是不明智的,因为在PL / SQL块中执行的语句数量可能是任意大的,例如,如果它执行超过一百万行表的循环。)
我认为您可以在适当的粒度级别启用审核,然后在每次调用后查询DBA_AUDIT_TRAIL
并按时间戳,用户和终端进行过滤,以将报告限制为当前最近的调用会话,但听起来好像即使你想要的还有一段距离。
答案 3 :(得分:-2)
spool "D:\test\test.txt"
-- YOUR ANONYMOUS PL/SQL Block here
spool off