简体   繁体   中英

How to automatically display output of all SQL statements inside anonymous PL/SQL block

Our data migration scripts make use of anonymous PL/SQL blocks to help tidy up the code, mainly so we can set the create and updated user Id columns to a "system" user.

Our migration scripts look something like:

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;
/

The user who updated a record is a numeric Id from our users table, not the string username. This was a requirement from our data modeling team, otherwise I would just hard-code the username of our "system" account.

And as an additional side note, our DBA runs the scripts, and he should not be the one showing up as the person/user who updated or inserted records. Another requirement from the larger enterprise environment.

The output I would like to see from the sqlplus command line is something like:

Updated X rows
Inserted Y rows

Just like if you had run the INSERT and UPDATE statements outside of the PL/SQL block.

And I'm really hoping for a solution that doesn't require explicit calls to DBMS_OUTPUT.PUT_LINE .

How can I automatically display the output of each DML statement inside an anonymous PL/SQL block without explicit calls to DBMS_OUTPUT.PUT_LINE for each statement?

Compound triggers can count and display the number of rows modified without changing the original code.

There are still a few issues and challenges here. This solution probably won't work with parallel DML - it either won't count properly or the triggers will prevent direct-path writes. It probably will work in a multi-user environment but that needs to be tested. You'll also need to build code for DELETEs, and maybe MERGEs. And this will probably slow down DML.

Sample Schema

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);

Package to prevent excessive DBMS_OUTPUT

Constantly printing output can cause problems. So we want to disable output by default. And you probably don't simply want to use DBMS_OUTPUT.DISABLE , that might turn off something else and it's hard to always remember to run that.

Create a simple package with a global variable.

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;
/

Set it to TRUE before running import.

--Run this block first to enable printing.
begin
    print_feedback.g_print_output := true;
end;
/

PL/SQL Block to create INSERT and UPDATE triggers

This code dynamically generates triggers to capture INSERTs and UPDATEs.

Dynamic PL/SQL is a bit tricky. Notice I used templates and the alternative quoting mechanism to avoid concatenation hell. Once you understand those tricks the code becomes relatively readable. (And hopefully your IDE understands how the q'[ works better than the StackOverflow syntax highlighter.)

--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;
/

Sample run

Now your unchanged script will display some output. (I did make a few trivial changes to the script but only to make it runnable.)

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>

I'm not sure if there is some oracle parameter or config to change so that your PL/SQL will work the way you want, but you can create a procedure which accepts DML statement and run that DML statement. See sample below,

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.

SQL*Plus gets its status information about the number of rows affected etc by examining the OCI return status. OCI is not really my area, but I am pretty sure that in the case of a PL/SQL block the only information it holds will be the fact that a block was submitted and whether it succeeded or failed, as the block was submitted to the server as a single unit and there is no structure in the call interface documenting every step within it together with status and affected rowcounts. There is simply no mechanism to capture that information. (It would also be unwise for Oracle to implement such an interface as the number of statements executed within a PL/SQL block can be arbitrarily large, for example if it performs a loop over a million row table.)

I suppose you may be able to enable auditing at a suitable level of granularity, and then query DBA_AUDIT_TRAIL after each call and filtering by timestamp, user and terminal to limit the report to the most recent call in the current session, but it sounds as though even that would be some way off what you are looking for.

spool "D:\test\test.txt"

-- YOUR ANONYMOUS PL/SQL Block here

spool off  

this will give u your required out put and without using the DBMS_OUTPUT.PUT_LINE the ouput will be in the path specified

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM