11 July 2010

Questions raised by players regarding 8 July quiz (LOG ERRORS and SAVE EXCEPTIONS)(464)

The quiz on 8 July asked this question: "You can use both LOG ERRORS and SAVE EXCEPTIONS to continue past errors that occur when executing DML statements (inserts, updates, deletes and merges). Which of the following statements describe what happens when you use both in a single FORALL statement (assume that DBMS_ERRLOG.CREATE_ERROR_LOG was used to create the error log table as required)?" We scored as correct the following choice: "If, within a FORALL statement, you include (in a syntactically valid fashion) both SAVE EXCEPTIONS and LOG ERRORS with the rejection limit set to UNLIMITED, then the SQL%BULK_EXCEPTIONS pseudo-collection will always be empty after executing that statement." Two players expressed a concern about this choice. I will invite them to post their comments in reply to this blog. Then I will reply to them! SF

10 comments:

  1. The following is my original email:

    The behavior of combining "SAVE EXCEPTIONS" and "LOG ERRORS" within a FORALL statement is undefined for Oracle 10g; however, in 11g an ORA-38909 runtime exception is raised. Although one might guess that "LOG ERRORS REJECT LIMIT UNLIMITED" would result in SQL%BULK_EXCEPTIONS always being empty (which is indeed the case for Oracle 10g), it seems unfair to penalize a player for not divining the effects of using statements with undocumented behavior. It is unlikely that one would experiment with the combined use of "SAVE EXCEPTIONS" and "LOG ERRORS" (except in conjunction with the PL/SQL Challenge). Given that Oracle added an exception in 11g to address this issue, it is likely that the ability to use the combination in 10g was an accident of implementation and not intended behavior.

    Although I am not happy with this quiz question, I am not advocating a change in scoring.

    I strongly encourage you to continue offering questions that explore unusual, but documented, aspects of the PL/SQL language. Please don’t let the PL/SQL Challenge devolve into a trivia contest instead of a competition based on the practical application of intended and documented behavior of the PL/SQL language.


    Steven asked me to supply a script to demonstrate the issue which I included with the following email:

    The attached script will not raise an exception when run on 10g but will raise an ORA-38909 exception when run on 11g. I encapsulated the "FORALL" in a stored procedure to demonstrate that it compiles successfully and that the exception occurs at runtime. If test_values is empty then no exception is raised.

    Had the question been based on using 11g instead of 10g I would have had no issue since the exception is documented in Error Messages. If oracle intended to permit the combined use of "SAVE EXCEPTIONS" and "LOG ERRORS" in 10g I doubt the capability would have been removed in 11g. When researching this I found reference to a message from Oracle at http://aspn.activestate.com/ASPN/Mail/Message/perl-dbi-dev/3837250. While the message indicates that Oracle is aware that the combination works in 10g, the lack of documentation in the Oracle manuals and the subsequent addition of an exception for this in 11g leads me to the conclusion that the capability "just happened" in 10g.

    Again I want to thank you for your prompt responses to email. I also appreciate the professional manner in which you have handled the issues that I and others have raised.

    Below is the script that I included:

    CREATE TABLE test_table (test_id INTEGER PRIMARY KEY)
    ORGANIZATION INDEX;

    EXECUTE sys.DBMS_ERRLOG.create_error_log('TEST_TABLE','TEST_ERRLOG');

    CREATE OR REPLACE PROCEDURE test_proc IS
    TYPE test_values_t IS TABLE OF test_table%ROWTYPE
    INDEX BY BINARY_INTEGER;

    test_values test_values_t;
    BEGIN
    test_values(1).test_id := 1;

    FORALL i IN 1 .. test_values.COUNT
    SAVE EXCEPTIONS
    INSERT INTO test_table
    VALUES test_values(i)
    LOG ERRORS INTO test_errlog REJECT LIMIT UNLIMITED;
    EXCEPTION
    WHEN OTHERS THEN
    ROLLBACK;
    RAISE;
    END test_proc;
    /

    EXECUTE test_proc;

    ReplyDelete
  2. Fascinating! By far the best part of the PL/SQL Challenge for me is when I learn something new about PL/SQL from a player. I was not even aware of this new error in Oracle11g.

    Having said that, I am a bit puzzled. When I run John's script I see the error. When I run the code below, I do not get an error. Can anybody see why that might be?

    DROP TABLE parts
    /

    CREATE TABLE parts
    (
    id NUMBER
    , name VARCHAR2 (10)
    )
    /

    BEGIN
    INSERT INTO parts VALUES (1, 'Gearbox');
    INSERT INTO parts VALUES (2, 'Box');
    COMMIT;
    END;
    /

    DROP TABLE err$_parts
    /

    BEGIN
    DBMS_ERRLOG.create_error_log (dml_table_name => 'PARTS');
    END;
    /

    CREATE OR REPLACE PROCEDURE test_se_le
    IS
    TYPE filters_t IS TABLE OF parts.name%TYPE;

    l_filters filters_t := filters_t ('G%', 'B%');
    BEGIN
    FORALL indx IN 1 .. l_filters.COUNT
    SAVE EXCEPTIONS
    UPDATE parts
    SET name = name || name
    WHERE name LIKE l_filters (indx)
    LOG ERRORS INTO err$_parts REJECT LIMIT UNLIMITED;
    END;
    /

    BEGIN
    test_se_le;
    END;
    /

    SELECT * FROM err$_parts
    /

    ReplyDelete
  3. Actually the part I had trouble with was "the SQL%BULK_EXCEPTIONS pseudo-collection will always be empty after executing that statement".

    An update that raises a unique key violation doesn't trigger DML error logging. So in this case, you can get errors in SQL%BULK_EXCEPTIONS

    http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/statements_9014.htm#BGBEIACB

    Example tested in 10.2.0.4 EE and XE:

    clear screen
    drop table test_errlog purge;
    drop table test_table purge;

    CREATE TABLE test_table (test_id INTEGER primary key, test_val integer unique);

    EXECUTE sys.DBMS_ERRLOG.create_error_log('TEST_TABLE','TEST_ERRLOG');

    insert into test_table select level, level from dual connect by level < 6;
    commit;

    CREATE OR REPLACE PROCEDURE test_proc IS
    TYPE test_values_t IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
    test_values test_values_t;
    BEGIN
    test_values(1) := 1;
    test_values(2) := 2;
    test_values(3) := 3;
    test_values(4) := 4;
    --
    FORALL i IN 1 .. test_values.COUNT
    SAVE EXCEPTIONS
    UPDATE test_table
    SET test_val = CASE WHEN test_id in (1,2) THEN 10 ELSE 20 END
    WHERE test_id = test_values(i)
    LOG ERRORS INTO test_errlog REJECT LIMIT UNLIMITED;
    dbms_output.put_line('A:'||sql%bulk_exceptions.count);
    EXCEPTION
    WHEN OTHERS THEN
    dbms_output.put_line('B:'||sql%bulk_exceptions.count);
    FOR i IN 1..sql%bulk_exceptions.count LOOP
    dbms_output.put_line('Err:'||SQL%BULK_EXCEPTIONS(i).ERROR_index||'-'||SQL%BULK_EXCEPTIONS(i).ERROR_CODE);
    END LOOP;
    FOR c_rec in (SELECT test_id, test_val from TEST_TABLE) loop
    dbms_output.put_line(c_rec.test_id||'-'||c_rec.test_val);
    END LOOP;
    ROLLBACK;
    RAISE;
    END test_proc;
    /

    show errors procedure test_proc

    EXECUTE test_proc;
    rollback;

    ReplyDelete
  4. Hi

    I also made a
    post
    in my blog about that issue in my blog.

    The exception doesn't occurs in 11g with update or delete.
    I didn't test with merge but I think it will occur.

    I'm almost sure that the behavior of combining "SAVE EXCEPTIONS" and "LOG ERRORS" within a FORALL statement is not undefined for Oracle 10g (like for instance the triggers).

    BTW my testing I used a trigger (insert, delete, update) in the table that raised an exception so I could test all the distinct DML (insert, delete, update)

    ReplyDelete
  5. Just like John, I was asked by Steven to reply to this post.

    As John already explained the situation I mentioned in the email, I'll just say I agree with everything he said (including the part that I'm not asking for a recount).

    This really seems like a hidden and not assumed bug, as there is pretty much zero documentation about this error, and it only applies to INSERTs (UPDATEs and DELETEs don't raise the exception, as first pointed out to me by Adriano Teixeira). Also, it's not backward compatible, which means everything that uses this will suddenly stop working after the upgrade.

    I actually agree with the only explanation I read coming from someone from Oracle (that this is not a good practice, and there's no reason to use both), but this is really a wicked way to "force" this.

    ReplyDelete
  6. The explanation given in the Quiz answer is perfectly logical in saying that the SQL layer
    evaluates the SQL outcome of the statement
    BEFORE the pl/sql layer, which means that if we use REJECT LIMIT UNLIMITED
    (and to this we should add that we also do not hit any of the cases excepted from the LOG ERRORS capturing as by Oracle's documentation),
    then we should in fact end with errors logged in the error table and an empty SQL%BULK_EXCEPTIONS pseudo-collection.

    Though I did not apply this reasoning, the question looked to me very challenging because
    I have never thought of using the two features together. Therefore I tried it out before asnwering the quiz, and it happened that I tried it on Oracle11gR1, which caused the
    ORA-38909 error... and I was sure of having chosen the right answer ...

    The misleading thing is that the text of this error says:

    ORA-38909: DML Error logging is not supported with BATCH ERROR mode

    so, not a single word about any of using SAVE EXCEPTIONS (is this what they call "batch mode" ?!?) or using LOG ERRORS or the two of them together.

    Since the same test worked ok with SAVE EXCEPTIONS removed, it seemed natural to consider this as the culprit.

    If this combination were illegal, then I would have expected a compilation error to be returned and not a run-time error.

    Anyway, a very instructive question, from which I learned very much.

    I completely agree with Steve's position,
    the best thing with this challenge is that we can always learn new things, even by doing small tests !

    Thanks & Best Regards,
    Iudith Mentzel

    ReplyDelete
  7. I am in agreement with Gary, I also sent the same scenario as he mentioned. The correct choice as per Steve was:
    "If, within a FORALL statement, you include (in a syntactically valid fashion) both SAVE EXCEPTIONS and LOG ERRORS with the rejection limit set to UNLIMITED, then the SQL%BULK_EXCEPTIONS pseudo-collection will always be empty after executing that statement."

    This is definitely not true "always". But I agree that this question has cleared a lot of concepts and I appreciate Steve's efforts to come up with these kind of tricky ones.

    Regards,
    Anuj Parashar

    ReplyDelete
  8. I am in agreement with Gary, I also sent the same scenario as he mentioned. The correct choice as per Steve was:
    "If, within a FORALL statement, you include (in a syntactically valid fashion) both SAVE EXCEPTIONS and LOG ERRORS with the rejection limit set to UNLIMITED, then the SQL%BULK_EXCEPTIONS pseudo-collection will always be empty after executing that statement."

    This is definitely not true "always". But I agree that this question has cleared a lot of concepts and I appreciate Steve's efforts to come up with these kind of tricky ones.

    Regards,
    Anuj Parashar

    ReplyDelete
  9. Sorry about the delay in responding. I've been busy getting ready for the playoff.

    So....if the scenario I described in my question is not "always" right, then that choice is wrong.

    I will issue a correction and adjust scores and ranks....very soon!

    This is also a great reinforcement for my new checklist for quizzes - that use of the word "always" should have been and now mos definitely IS a red flag for any future quizzes.

    Thanks for all the great analysis and learning experience!

    Steven

    ReplyDelete
  10. I too had raised the same question in my blog @ http://doubtregarding8thjulyquestion.blogspot.com/ on 8th jul.

    thank god i got more info here than i expected :-)

    ReplyDelete