/
Copyright All material contained herein is owned by Daniel Stober, the author of this Copyright All material contained herein is owned by Daniel Stober, the author of this

Copyright All material contained herein is owned by Daniel Stober, the author of this - PowerPoint Presentation

luanne-stotts
luanne-stotts . @luanne-stotts
Follow
349 views
Uploaded On 2019-11-22

Copyright All material contained herein is owned by Daniel Stober, the author of this - PPT Presentation

Copyright All material contained herein is owned by Daniel Stober the author of this presentation This presentation and the queries examples and original sample data may be shared with others for educational purposes only in the following circumstances ID: 766854

age date line loop date age loop line output put dbms number val ret emp select job return sal

Share:

Link:

Embed:

Download Presentation from below link

Download Presentation The PPT/PDF document "Copyright All material contained herein ..." is the property of its rightful owner. Permission is granted to download and print the materials on this web site for personal, non-commercial use only, and to display it on your personal computer provided you do not modify the materials and that you retain all copyright notices contained in the materials. By downloading content from our website, you accept the terms of this agreement.


Presentation Transcript

Copyright All material contained herein is owned by Daniel Stober, the author of this presentation. This presentation and the queries, examples, and original sample data may be shared with others for educational purposes only in the following circumstances: That the person or organization sharing the information is not compensated, or If shared in a circumstance with compensation, that the author has granted written permission to use this work Using examples from this presentation, in whole or in part, in another work without proper source attribution constitutes plagiarism. Use of this work implies acceptance of these terms

Six Bugs you can put into Your PLSQL ProgramsDan StoberIntermountain HealthcareSeptember 14, 2011

Dan StoberData Architect – Intermountain HealthcareAttended California State Univ., FresnoWorking in Oracle databases since 2001Frequent presenter at local and national user group conferencesOracle Open World twicePrivate Instructor for TrutekTeaching PLSQLOracle Certified SQL ExpertBoard of Trustees – Utah Oracle Users GroupEdit newsletterWrite SQL Tip column

Intermountain Healthcare23 hospitals in Utah and IdahoNon-profit integrated health care system750 Employed physicians32,000 employeesThe largest non-government employer in UtahOne of the largest and most complete clinical data warehouses in the world!

Session NormsQuestions? Interrupt Me!I learn something from every session I do!Set the record straight! Cell phones?OK!

Bugs?These are:Not bugs in PLSQLErrors in the code introduced by programmers who missed some of the nuances in the languageExamples of PLSQL behaving exactly as documentedHow did I learn these?I’ve made every one of these mistakes!My goal?Make you aware So you can avoid them

Bug AgendaFOR LOOP variablesNo_data_foundParameter defaultsFunction returned without valueDeclaration section exceptionsMore FOR LOOP issuesImplicit COMMIT

Bug #1FOR LOOP Variables

What will be the result?DECLARE i NUMBER := 10;BEGIN dbms_output.put_line ( ' i =' || i ) ; FOR i IN 1..5 LOOP dbms_output.put_line ( ' i =' || i ) ; END LOOP; dbms_output.put_line ( 'i=' || i ) ;END; i=10i=1i=2i=3i=4i=5i=10PL/SQL procedure successfully completed. This variable i is the same as this i But, this i is a different variable

Components of a LOOPDuties / FunctionsDeclarationInitializationIterationExit conditionDECLARE i NUMBER; BEGIN i := 1; LOOP dbms_output.put_line ( i ) ; i := i + 1; EXIT WHEN i > 5; END LOOP;END; BEGIN FOR i IN 1..5 LOOP dbms_output.put_line ( i ) ; END LOOP;END;DeclarationInitialization Iteration Exit Condition The FOR LOOP handles all of these

Components of a Cursor LoopDECLARE CURSOR emps IS SELECT ename FROM scott.emp; rec emps%TYPE ; BEGIN OPEN emps ; LOOP FETCH emps INTO rec ; EXIT WHEN emps%NOTFOUND; dbms_output.put_line ( rec.ename ) END LOOP; CLOSE emps;END;DeclarationOpen / CloseRecord Fetch Exit Condition Open / Close Iteration

Components of a Cursor FOR LoopBEGIN FOR rec IN ( SELECT ename FROM scott.emp ) LOOP dbms_output.put_line ( rec.ename ) END LOOP; END; Declaration Open / Close Record Fetch Exit Condition Iteration

How does this manifest as a bug?Scope of LOOP variablesDECLARE CURSOR emps IS SELECT empno , ename FROM scott.emp; rec emps%TYPE ; BEGIN FOR rec IN emps LOOP INSERT INTO myemps VALUES rec; END LOOP;EXCEPTION WHEN OTHERS THEN dbms_output.put_line ( SQLERRM || '>' || rec.empno );END;It creates the temptation to reference the loop variable outside of the loop, with the belief that the variable will retain the value that it held inside of the loop.

CURSOR FOR variablesThe same principle applies in a CURSOR FOR LOOPDECLARE CURSOR emps_cur is ( SELECT empno , ename , sal FROM emp ORDER BY empno ); rec emps_cur%TYPE ; BEGIN FOR rec IN emps_cur LOOP process_record ( rec.empno ); END LOOP; IF rec.sal > 1000 THEN ... etc etc etc The programmer probably intended to read the value from the last record processed. But this is not the same variable

One solutionBug #1: Never declare FOR LOOP variablesOn solution:Create a separate variableWith a different nameWith scope outside of the loopDECLARE CURSOR emps IS SELECT empno , ename FROM scott.emp; v_empno NUMBER; BEGIN FOR rec IN emps LOOP v_empno := rec.empno; INSERT INTO myemps VALUES rec; END LOOP;EXCEPTION WHEN OTHERS THEN dbms_output.put_line ( SQLERRM || '>' || v_empno );END;Assign the value from the cursor record to a variable with a scope which exceeds the loop boundary.

Bug #2No_data_found

No_data_foundBuilt-in PLSQL exception designed to be used with implicit cursorsImplicit cursor: Query structure using SELECT xxx INTO xxxOne and only one record is expectedIf more than one record:Too_many_rowsIf no records:No_data_foundBug: An aggregate function always returns one rowUnless used with GROUP BY

Some handlers are not neededGroup functions always return one rowFUNCTION get_emp_job_count ( p_job IN VARCHAR2 ) RETURN NUMBER IS v_ret_val NUMBER; BEGIN BEGIN SELECT COUNT(*) INTO v_ret_val FROM emp WHERE job = p_job ; EXCEPTION WHEN no_data_found THEN v_ret_val := 0; END; RETURN v_ret_val;END; This exception will never be raised SQL> SELECT COUNT(*) 2 FROM emp 3 WHERE job = 'DBA'; COUNT(*) --------- 01 row selected. So what?The handler isn’t needed. It might be extra clutter,but does that make it a bug?

Unused handlerThe attempt here is to return ZERO when there are no emps with the jobFUNCTION get_job_sal_sum ( p_job IN VARCHAR2 ) RETURN NUMBER IS v_ret_val NUMBER; BEGIN BEGIN SELECT SUM( sal ) INTO v_ret_val FROM emp WHERE job = p_job ; EXCEPTION WHEN no_data_found THEN v_ret_val := 0; END; RETURN v_ret_val;END; This exception will never be raised When called for a JOB with no records in the table, the function will return NULL. Not ZERO.

Calling the Buggy FunctionBEGIN dbms_output.put_line ( 'CLERK: ' || get_job_sal_sum ( 'CLERK')); dbms_output.put_line ( 'DBA: ' || get_job_sal_sum ( 'DBA')); dbms_output.put_line ( 'SALESMAN: ' || get_job_sal_sum ( 'SALESMAN')); END; BEGIN dbms_output.put_line ( 'CLERK: ' || get_job_sal_sum ( 'CLERK')); dbms_output.put_line ( 'DBA: ' || get_job_sal_sum ( 'DBA')); dbms_output.put_line ( 'SALESMAN: ' || get_job_sal_sum ( 'SALESMAN'));END;/CLERK: 4150DBA:SALESMAN: 5600PL/SQL procedure successfully completed. The function returned NULL SQL> SELECT SUM ( sal ) 2 FROM emp 3 WHERE job = 'DBA';SUM(SAL)---------- 1 row selected. The query DID return a record, thus no_data_found was not raised, so the function returned the value from the query. It did not get replaced with ZERO. Here’s the query that was executed on the second call

Fixing the Buggy FunctionFUNCTION get_job_sal_sum ( p_job IN VARCHAR2) RETURN NUMBER IS v_ret_val NUMBER; BEGIN SELECT SUM( sal ) INTO v_ret_val FROM emp WHERE job = p_job ; RETURN NVL ( v_ret_val , 0); END; BEGIN dbms_output.put_line ( 'CLERK: ' || get_job_sal_sum ( 'CLERK')); dbms_output.put_line ( 'DBA: ' || get_job_sal_sum ( 'DBA')); dbms_output.put_line ( 'SALESMAN: ' || get_job_sal_sum ( 'SALESMAN'));END; BEGIN dbms_output.put_line ( 'CLERK: ' || get_job_sal_sum ( 'CLERK')); dbms_output.put_line ( 'DBA: ' || get_job_sal_sum ( 'DBA')); dbms_output.put_line ( 'SALESMAN: ' || get_job_sal_sum ( 'SALESMAN'));END;/CLERK: 4150 DBA: 0SALESMAN: 5600 PL/SQL procedure successfully completed. If the variable contains NULL, it will return ZERO instead Excellent!

Another Way to Fix ItFUNCTION get_emp_job_count ( p_job IN VARCHAR2 ) RETURN NUMBERIS v_ret_val NUMBER; BEGIN BEGIN SELECT COUNT(*) INTO v_ret_val FROM emp WHERE job = p_job GROUP BY job; EXCEPTION WHEN no_data_found THEN v_ret_val := 0; END; RETURN v_ret_val;END; With a GROUP BY, the query will return no records, if there is no match! SQL> SELECT COUNT(*) 2 FROM emp 3 WHERE job = 'DBA' 4 GROUP BY job;no rows selected

Another issueDo you see it?PROCEDURE process_sal_increase ( p_empno IN NUMBER ) IS v_current_sal NUMBER; BEGIN SELECT sal INTO v_current_sal FROM emp WHERE empno = p_empno AND ROWNUM = 1; v_new_sal := calculate_new_sal ( p_empno, v_sal );END; Why is this here? If the WHERE clause returns more than one record, and you don’t care which one you get, then why bother selecting? - Tom Kyte, paraphrased When you see this, it usually means that too_many_rows was raised at some point. Instead of fixing the data or the WHERE clause, the developer added this

An Aside: Performance Bug DECLARE V_rec_cnt INTEGER; BEGIN SELECT COUNT(*) INTO V_rec_cnt FROM load_table ; IF V_rec_cnt > 0 THEN -- table is not empty, process records . . . What is the purpose? Does the program really need to know how many records are in the table? Or, is the intent just to make sure that the table is not empty? SELECT COUNT(*) INTO V_rec_cnt FROM load_table WHERE ROWNUM = 1;

Another variationDECLARE V_rec_count : CURSOR my_recs IS ( SELECT empi , fcilty_id , immu_dt FROM immu_vaccinations WHERE acct_no = '7623725' )   recs_t IS TABLE OF my_recs%ROWTYPE ; v_recs recs_t;BEGIN  OPEN my_recs; FETCH my_recs BULK COLLECT INTO v_recs; CLOSE my_recs ;  IF v_recs.COUNT = 1 THEN   FOR j IN my_recs LOOP << processing to do if the cursor returned only one records >> END LOOP; ELSE   FOR j IN my_recs LOOP << processing to do if the cursor returned multiple records >> END LOOP; END IF; END; Opens cursor to find out how many records will be returned Different logic for one record versus multiple records. Reexecutes the same query SELECT empi , fcilty_id , immu_dt , COUNT (*) OVER () rec_count FROM immu_vaccinations WHERE acct_no = '7623725' Use analytic function to collect the record count at the same time as query records

Bug #3Default Values for Parameters

Function to calculate ageFUNCTION get_age_in_years ( p_birth_date IN DATE , p_as_of_date IN DATE DEFAULT SYSDATE ) RETURN NUMBER IS v_ret_val NUMBER; BEGIN v_ret_val := TRUNC( MONTHS_BETWEEN ( p_as_of_date , p_birth_date )/12); RETURN v_ret_val ; END get_age_in_years ;Default value for second parameter means if no value is passed, then function will use sysdateBEGINdbms_output.put_line ( 'AGE=' || get_age_in_years ( DATE '1968-03-09' , DATE '2011-09-14' ) ); dbms_output.put_line ( 'AGE=' || get_age_in_years ( DATE '1968-03-09' ) ); END;/ AGE=43AGE=43PL/SQL procedure successfully completed. BEGIN dbms_output.put_line ( 'AGE=' || get_age_in_years ( DATE '1968-03-09' , DATE '2011-09-14' ) ); dbms_output.put_line ( 'AGE=' || get_age_in_years ( DATE '1968-03-09' ) );END; BEGIN dbms_output.put_line ( 'AGE=' || get_age_in_years ( DATE '1968-03-09' , DATE '2011-09-14' ) ); END; / AGE=43 PL/SQL procedure successfully completed. BEGIN dbms_output.put_line ( 'AGE=' || get_age_in_years ( DATE '1968-03-09' , DATE '2011-09-14' ) ); END; /

Parameter Defaults and NULLDECLARE v_from_date DATE; v_to_date DATE; BEGIN v_from_date := DATE '1968-03-09'; dbms_output.put_line ( 'AGE=' || get_age_in_years ( v_from_date , v_to_date ) ); dbms_output.put_line ( 'AGE=' || get_age_in_years ( v_from_date ) );END;/DECLARE v_from_date DATE; v_to_date DATE;BEGIN v_from_date := DATE '1968-03-09'; dbms_output.put_line ( 'AGE=' || get_age_in_years ( v_from_date, v_to_date ) ); dbms_output.put_line ( 'AGE=' || get_age_in_years ( v_from_date ) );END; /AGE=AGE=43 In the first call, a variable with a NULL value was passed to the second parameter. In the second call, the second parameter was not referenced at all. In the second call, the default value of the parameter was used by the function.

How can you avoid this?If you need to make sure that the parameter is not null, use a variableFUNCTION get_age_in_years ( p_birth_date IN DATE , p_as_of_date IN DATE DEFAULT SYSDATE ) RETURN NUMBER IS v_ret_val NUMBER; v_as_of_date DATE; BEGIN v_as_of_date := NVL( p_as_of_date , SYSDATE ); v_ret_val := TRUNC( MONTHS_BETWEEN ( v_as_of_date, p_birth_date )/12); RETURN v_ret_val;END get_age_in_years; Variable for param NVL to handle NULL value This is needed still. Why?

Calling With Function FixedDECLARE v_from_date DATE; v_to_date DATE; BEGIN v_from_date := DATE '1968-03-09'; dbms_output.put_line ( 'AGE=' || get_age_in_years ( v_from_date , v_to_date ) ); dbms_output.put_line ( 'AGE=' || get_age_in_years ( v_from_date ) );END;/DECLARE v_from_date DATE; v_to_date DATE;BEGIN v_from_date := DATE '1968-03-09'; dbms_output.put_line ( 'AGE=' || get_age_in_years ( v_from_date, v_to_date ) ); dbms_output.put_line ( 'AGE=' || get_age_in_years ( v_from_date ) );END; /AGE=43AGE=43 Success!

Bug #4Function returned without value

FunctionFUNCTION get_age_description ( p_birth_date IN DATE , p_as_of_date IN DATE ) RETURN VARCHAR2 IS v_age NUMBER; BEGIN v_age := TRUNC( MONTHS_BETWEEN ( p_as_of_date , p_birth_date )/12); IF v_age < 18 THEN RETURN 'Minor'; ELSIF v_age = 18 THEN RETURN 'Voting age'; ELSIF v_age = 21 THEN RETURN 'Drinking age'; ELSIF v_age BETWEEN 22 AND 65 THEN RETURN 'Adult'; ELSIF v_age > 66 THEN RETURN 'Eligible for Medicare'; END IF; END get_age_description ; SELECT get_age_description ( DATE '1993-07-04', SYSDATE ) AS test FROM DUAL; SELECT get_age_description ( DATE '1993-07-04', SYSDATE ) AS test FROM DUAL;TEST ---------------- Voting age 1 row selected. SELECT get_age_description ( DATE '1992-07-04', SYSDATE ) AS test FROM DUAL; SELECT get_age_description ( DATE '1992-07-04', SYSDATE ) AS test FROM DUAL; SELECT get_age_description * ERROR at line 1: ORA-06503: PL/SQL: Function returned without value ORA-06512: at "SCOTT.GET_AGE_DESCRIPTION", line 30 This error means that execution got all the way to the end of the function and never encountered a RETURN statement

2nd attemptFUNCTION get_age_description ( p_birth_date IN DATE , p_as_of_date IN DATE ) RETURN VARCHAR2 IS v_age NUMBER; v_ret_val VARCHAR2(25); BEGIN v_age := TRUNC( MONTHS_BETWEEN ( p_as_of_date , p_birth_date )/12); IF v_age < 18 THEN v_ret_val := 'Minor'; ELSIF v_age = 18 THEN v_ret_val := 'Voting age'; ELSIF v_age = 21 THEN v_ret_val := 'Drinking age'; ELSIF v_age > 66 THEN v_ret_val := 'Eligible for Medicare'; ELSIF v_age BETWEEN 22 AND 65 THEN v_ret_val := 'Adult'; END IF; RETURN v_ret_val;END get_age_description; Introduction of a variable to hold RETURN value Single point of RETURN from function SELECT get_age_description ( DATE '1992-07-04', SYSDATE ) AS test FROM DUAL; SELECT get_age_description ( DATE '1992-07-04', SYSDATE ) AS test FROM DUAL; TEST ---------------- 1 row selected. This time, the function returned NULL ELSE One possible fix

Using CASEFUNCTION get_age_description ( p_birth_date IN DATE , p_as_of_date IN DATE ) RETURN VARCHAR2 IS v_age NUMBER; v_ret_val VARCHAR2(25); BEGIN v_age := TRUNC( MONTHS_BETWEEN ( p_as_of_date , p_birth_date )/12); CASE WHEN v_age < 18 THEN v_ret_val := 'Minor'; WHEN v_age = 18 THEN v_ret_val := 'Voting age'; WHEN v_age = 21 THEN v_ret_val := 'Drinking age'; WHEN v_age > 66 THEN v_ret_val := 'Eligible for Medicare'; WHEN v_age BETWEEN 22 AND 65 THEN v_ret_val := 'Adult'; END CASE; RETURN v_ret_val;END get_age_description; SELECT get_age_description ( DATE '1992-07-04', SYSDATE ) AS test FROM DUAL; SELECT get_age_description ( DATE '1992-07-04', SYSDATE ) AS test FROM DUAL; SELECT get_age_description * ERROR at line 1: ORA-06592: CASE not found while executing CASE statement ORA-06512: at "SCOTT.GET_AGE_DESCRIPTION", line 13 In any PLSQL development, when you do not expect an “ELSE”, use CASE instead of IF for tighter coding. Unlike IF /ELSIF, PLSQL CASE requires that one of the branches be executed

Bug #5Declaration section exceptions

What will happen here?DECLARE x NUMBER :=10/0;BEGIN dbms_output.put_line('Successful run.'); EXCEPTION WHEN zero_divide THEN dbms_output.put_line ('zero_divide exception handler'); WHEN OTHERS THEN dbms_output.put_line ('others exception handler'); END; / DECLARE * ERROR at line 1: ORA-01476: divisor is equal to zero ORA-06512: at line 3 Neither of the exception handlers was raised! Division by zero here Two different handlers

Handlers Gotchas BEGIN DECLARE v NUMBER(1) := 10; BEGIN v:= 20; EXCEPTION WHEN OTHERS THEN dbms_output.put_line ( 'Handler 1'); END; EXCEPTION WHEN OTHERS THEN dbms_output.put_line ( 'Handler 2'); END; Handler 2 The exception was raised in the “green” block But, it was not handled in “green” block It was handled in “blue” block But, at least it was handled!

What can you do?Avoid variable assignment in declaration section when possibleNot possible with CONSTANTNest blocks in larger blocks so that errors can be handled

Bug #6More FOR LOOP problems

Loop BugsBEGIN FOR i IN -1..-5 LOOP dbms_output.put_line ( i ); END LOOP; END; BEGIN FOR i IN -5..-1 LOOP dbms_output.put_line ( i ); END LOOP; END; -5 -4 -3 -2 -1 The lower number must come first, or the loop is not executedNo output.Why?

Loop BugsBEGIN FOR i IN REVERSE 5..1 LOOP dbms_output.put_line ( i ); END LOOP; END ; BEGIN FOR i IN REVERSE 1..5 LOOP dbms_output.put_line ( i ); END LOOP; END ; 5 4 3 2 1 Even with REVERSE, the lower number must come firstAgain: No output. A backwards loop

FOR LOOP EndpointsDECLARE TYPE number_t IS TABLE OF NUMBER; v_empnos number_t ; BEGIN SELECT empno BULK COLLECT INTO v_empnos FROM scott.emp WHERE deptno = 20; FOR i IN v_empnos.FIRST .. v_empnos.LAST LOOP -- Loop through records for additional processing dbms_output.put_line ( v_empnos(i) ); END LOOP;END;/iempno (1) 7369(2)7566 (3)7788(4) 7876(5)7902 7369 75667788 78767902PL/SQL procedure successfully completed. 1 5

FOR LOOP EndpointsDECLARE TYPE number_t IS TABLE OF NUMBER; v_empnos number_t ; BEGIN SELECT empno BULK COLLECT INTO v_empnos FROM scott.emp WHERE deptno = 40; FOR i IN v_empnos.FIRST .. v_empnos.LAST LOOP -- Loop through records for additional processing dbms_output.put_line ( v_empnos(i) ); END LOOP;END;/iempno Empty array DECLARE *ERROR at line 1: ORA-06502: PL/SQL: numeric or value errorORA-06512: at line 10 NULL NULL No emps in dept 40

Fix #1DECLARE TYPE number_t IS TABLE OF NUMBER; v_empnos number_t ; BEGIN SELECT empno BULK COLLECT INTO v_empnos FROM scott.emp WHERE deptno = 40; FOR i IN 1..v_empnos.COUNT LOOP -- Loop through records for additional processing dbms_output.put_line ( v_empnos (i) ); END LOOP;END;/PL/SQL procedure successfully completed.FOR i IN 1..0 LOOP When the array is empty, COUNT returns 0. It does not return NULL Remember: If the second number is lower than the first number, the FOR LOOP does not execute.

Fix #2DECLARE TYPE number_t IS TABLE OF NUMBER; v_empnos number_t ; i NUMBER; BEGIN SELECT empno BULK COLLECT INTO v_empnos FROM scott.emp WHERE deptno = 40; i := v_empnos.FIRST ; WHILE i IS NOT NULL LOOP -- Loop through records for additional processing dbms_output.put_line ( v_empnos(i) ); i := v_empnos.NEXT (i ); END LOOP; END;/ PL/SQL procedure successfully completed. If the array is empty, this function returns NULL This is the only way to code this if the array is sparse When this is called after the last record, it returns NULL, too LOOP Exit mechanism Because it’s not a FOR LOOP, the variable MUST be declared.

Bug #7Implicit COMMIT

BEGIN EXECUTE IMMEDIATE 'TRUNCATE TABLE scott.emp_mgr'; FOR rec IN ( SELECT e.ename AS ename , m.ename AS mgrname FROM scott.emp e JOIN scott.emp m ON e.mgr = m.empno ) LOOP INSERT INTO scott.emp_mgr VALUES rec ; END LOOP; COMMIT; EXCEPTION WHEN OTHERS THEN dbms_output.put_line ( SQLERRM ); ROLLBACK;END;The set-up EMP_MGR EMP Loop through records Transform In case of load error, rollback Load Clear the table Clear Load Rollback DESC scott.emp_mgr EMP_MGR ENAME VARCHAR2(6) MGRNAME VARCHAR2(6)

ETL RunSELECT COUNT(*) FROM scott.emp_mgr; COUNT(*) ---------- 13 1 row selected. INSERT INTO scott.emp VALUES ( 7777, 'O''REILLY', 'CORPORAL' , 7902, DATE '1984-06-16', 900 , NULL, 10); 1 row created. BEGIN EXECUTE IMMEDIATE 'TRUNCATE TABLE scott.emp_mgr '; FOR rec IN ( SELECT e.ename AS ename , m.ename AS mgrname FROM scott.emp e JOIN scott.emp m ON e.mgr = m.empno ) LOOP INSERT INTO scott.emp_mgr VALUES rec; END LOOP; COMMIT;EXCEPTION WHEN OTHERS THEN dbms_output.put_line ( SQLERRM ); ROLLBACK; END;/ ORA-12899: value too large for column "SCOTT"."EMP_MGR"."ENAME" (actual: 8, maximum: 6) PL/SQL procedure successfully completed. SELECT COUNT(*) FROM scott.emp_mgr; COUNT(*) ---------- 0 1 row selected. Now, run ETL procedure After the error, the table is empty. Something went wrong

Why was the load table empty?DDL causes implicit COMMITTRUNCATE TABLE…CREATE INDEX… Remember that EXECUTE IMMEDIATE… ?This is Oracle database behaviorNot PLSQL

Bug KillersReview Checklist :FOR LOOP variablesNo_data_foundParameter defaultsFunction returned without value Declaration section exceptions More FOR LOOP issues Implicit COMMIT

Thank-you!Dan StoberQuestions? Comments?dan.stober@utoug.org

Copyright All material contained herein is owned by Daniel Stober, the author of this presentation. This presentation and the queries, examples, and original sample data may be shared with others for educational purposes only in the following circumstances: That the person or organization sharing the information is not compensated, or If shared in a circumstance with compensation, that the author has granted written permission to use this work Using examples from this presentation, in whole or in part, in another work without proper source attribution constitutes plagiarism. Use of this work implies acceptance of these terms