如何自动显示匿名PL / SQL块中所有SQL语句的输出

时间:2018-03-28 14:24:24

标签: sql oracle plsql

我们的数据迁移脚本利用匿名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语句的输出?

4 个答案:

答案 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

的包

不断打印输出可能会导致问题。所以我们想默认禁用输出。你可能不想简单地使用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块

此代码动态生成触发器以捕获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  

这将为您提供所需的输出,而不使用DBMS_OUTPUT.PUT_LINE,输出将在指定的路径中