MINI
C:
/
Quest Software
/
Toad for Oracle 11.5
/
Upload File:
files >> C:/Quest Software/Toad for Oracle 11.5/RuleUniverse.XML
<CODE_ANALYSIS_RULES><RULE><ID>2006</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Use Native Dynamic SQL (i.e. EXECUTE IMMEDIATE) rather than calls to DBMS_SQL.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>20</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //QNAME/IDENTIFIER [@value="dbms_sql"] </XPATH_QUERY><TIP_TEXT>This applies to Oracle 8i and above. Native dynamic SQL is easier to use and faster than the DBMS_SQL package. Consider, for example, the following procedure that can be used to drop a table: <pre>CREATE PROCEDURE drop_table (table_name IN VARCHAR2) AS cursor_name INTEGER; BEGIN cursor_name := dbms_sql.open_cursor; DBMS_SQL.PARSE(cursor_name, 'DROP TABLE ' || table_name, dbms_sql.native); DBMS_SQL.close_cursor(cursor_name); EXCEPTION WHEN OTHERS THEN DBMS_SQL.CLOSE_CURSOR(cursor_name); END;</pre> The same functionality can be accomplished with a single line using Native Dynamic SQL: <pre>CREATE PROCEDURE drop_table (table_name IN VARCHAR2) AS BEGIN EXECUTE IMMEDIATE 'DROP TABLE ' || table_name; END;</pre> Avoiding direct calls to DBMS_SQL.PARSE and DBMS_SQL.OPEN When using DBMS_SQL, you don't have to continually allocate new cursors. You can, instead, reuse existing cursors. In addition, you will want to make sure that you do not try to open cursors that have already been opened. Finally, it is extremely important to write error handling as close as possible to the parse step, since many hard-to-identify errors occur at that phase. Instead of making direct calls to DBMS_SQL.PARSE or DBMS_SQL.OPEN, you should consider calling a program that checks the status of your cursor, opening a new one only when necessary, and parses the SQL statement for you, with all the error handling you will ever need. Here is an example of such a utility: <pre>CREATE OR REPLACE PACKAGE dynalloc AUTHID CURRENT_USER IS PROCEDURE closecur (cur_inout IN OUT INTEGER); PROCEDURE initcur (cur_inout IN OUT INTEGER); PROCEDURE open_and_parse ( cur_inout IN OUT INTEGER ,sql_statement_in IN VARCHAR2 ,dbms_mode_in IN INTEGER := NULL ); FUNCTION open_and_parse ( sql_statement_in IN VARCHAR2 ,dbms_mode_in IN INTEGER := NULL ) RETURN INTEGER; END dynalloc; / CREATE OR REPLACE PACKAGE BODY dynalloc IS PROCEDURE closecur (cur_inout IN OUT INTEGER) IS BEGIN IF DBMS_SQL.is_open (cur_inout) THEN DBMS_SQL.close_cursor (cur_inout); END IF; END; PROCEDURE initcur (cur_inout IN OUT INTEGER) IS BEGIN IF NOT DBMS_SQL.is_open (cur_inout) THEN cur_inout := DBMS_SQL.open_cursor; END IF; EXCEPTION WHEN INVALID_CURSOR THEN cur_inout := DBMS_SQL.open_cursor; END; PROCEDURE open_and_parse ( cur_inout IN OUT INTEGER ,sql_statement_in IN VARCHAR2 ,dbms_mode_in IN INTEGER := NULL ) IS v_already_open BOOLEAN := DBMS_SQL.is_open (cur_inout); BEGIN initcur (cur_inout); DBMS_SQL.parse (cur_inout ,sql_statement_in ,NVL (dbms_mode_in, DBMS_SQL.native) ); EXCEPTION WHEN OTHERS THEN p.l (SQLERRM); p.l (sql_statement_in); IF NOT v_already_open THEN closecur (cur_inout); END IF; RAISE; END; FUNCTION open_and_parse ( sql_statement_in IN VARCHAR2 ,dbms_mode_in IN INTEGER := NULL ) RETURN INTEGER IS /* Declare cursor handle and assign it a pointer */ return_value PLS_INTEGER; BEGIN open_and_parse (return_value, sql_statement_in, dbms_mode_in); RETURN return_value; END; END dynalloc; /</pre> Here is a script demonstrating the functionality of this package: <pre>DECLARE n PLS_INTEGER; BEGIN dynalloc.open_and_parse (n, 'select * from employee'); DBMS_OUTPUT.put_line (n); dynalloc.open_and_parse (n, 'select * from emp'); DBMS_OUTPUT.put_line (n); n := dynalloc.open_and_parse ('select * from dept'); DBMS_OUTPUT.put_line (n); n := dynalloc.open_and_parse ('select * from department'); DBMS_OUTPUT.put_line (n); END; /</pre></TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE insert_into_table (table_name VARCHAR2, deptnumber NUMBER, deptname VARCHAR2, location VARCHAR2) IS cur_hdl INTEGER; stmt_str VARCHAR2 (200); rows_processed BINARY_INTEGER; BEGIN stmt_str := 'INSERT INTO ' || table_name || ' VALUES (:deptno, :dname, :loc)'; -- open cursor cur_hdl := DBMS_SQL.open_cursor; -- should raise -- parse cursor DBMS_SQL.parse (cur_hdl, stmt_str, DBMS_SQL.native); -- should raise twice -- supply binds DBMS_SQL.bind_variable -- should raise (cur_hdl, ':deptno', deptnumber); DBMS_SQL.bind_variable -- should raise (cur_hdl, ':dname', deptname); DBMS_SQL.bind_variable -- should raise (cur_hdl, ':loc', location); -- execute cursor rows_processed := DBMS_SQL.execute (cur_hdl); -- should raise -- close cursor DBMS_SQL.close_cursor (cur_hdl); -- should raise END; /</EXAMPLE_CODE><DB_VERSION>8i and greater.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R2006</TIP_LINK><WIKIREV>r35</WIKIREV><WIKITIMESTAMP>22 Mar 2012 - 20:27:46</WIKITIMESTAMP></RULE><RULE><ID>2007</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid direct calls to DBMS_OUTPUT.PUT_LINE.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY>//CALL/QNAME[IDENTIFIER[1][@value="DBMS_OUTPUT"] and IDENTIFIER[2][@value="PUT_LINE"]]</XPATH_QUERY><TIP_TEXT>Avoid direct calls to DBMS_OUTPUT.PUT_LINE.</TIP_TEXT><EXAMPLE_CODE>BEGIN DBMS_OUTPUT.PUT_LINE (x); -- should raise DBMS_OUTPUT.Put_LINE (x); -- should raise DBMS_OUTPUT.something_else (x); END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r24</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 01:43:14</WIKITIMESTAMP></RULE><RULE><ID>2401</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid nesting comment blocks.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>24</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //COMMENT[@type='block'][contains(substring(., 2), '/*')] </XPATH_QUERY><TIP_TEXT>A start-of-comment ( /* ) was found within comments. This can make the code more difficult to read. This situation can arise as you go through multiple modifications to code. You might, for example end up with something like this: <pre>/* We no longer will perform this calculation. /* Come up with monthly totals by department v_total := monthly_pkg.total_analysis (SYSDATE); */</pre> If you are going to comment out code, then get rid of all of the internal comment characters as well, as in: <pre>/* We no longer will perform this calculation. Come up with monthly totals by department v_total := monthly_pkg.total_analysis (SYSDATE); */</pre></TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE insert_into_table ( /* normal comment /* nested comment */ -- should raise table_name VARCHAR2, deptnumber NUMBER, deptname VARCHAR2, location VARCHAR2) IS BEGIN /* block comment -- open cursor end block comment */ my_proc_call; END; / /* normal comment /* nested comment */ -- should raise SHOW ERRORS;</EXAMPLE_CODE><DB_VERSION>None.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R2401</TIP_LINK><WIKIREV>r31</WIKIREV><WIKITIMESTAMP>20 Jan 2012 - 19:53:06</WIKITIMESTAMP></RULE><RULE><ID>2601</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Ensure locally opened cursors will be closed.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>26</CAT><SEVERITY>2</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>No statement closing a locally opened cursor could be found. Any cursors left open can consume additional System Global Area (i.e. SGA) memory space within the database instance, potentially in both the shared and private SQL pools. Furthermore, failure to explicitly close cursors may also eventually cause the owning session to exceed its maximum limit of open cursors as specified by the OPEN_CURSORS database initialization parameter, potentially resulting in the Oracle error of ORA-01000 maximum open cursors exceeded. For example, the following procedure opens and fetches, but does not close its cursor - which may cause problems like those described above: <pre>CREATE PROCEDURE not_close_cursor (v_count OUT INTEGER) AS CURSOR c1 IS SELECT COUNT (*) FROM all_users; BEGIN v_count := 0; OPEN c1; FETCH c1 INTO v_count; END; This procedure closes the cursor: CREATE PROCEDURE close_cursor (v_count OUT INTEGER) AS CURSOR c1 IS SELECT COUNT (*) FROM all_users; BEGIN v_count := 0; OPEN c1; FETCH c1 INTO v_count; CLOSE c1 END;</pre></TIP_TEXT><EXAMPLE_CODE>DECLARE CURSOR c IS SELECT dummy FROM DUAL; BEGIN OPEN c; -- should raise FOR a IN 1 .. 3 LOOP CLOSE c; OPEN c; -- should raise FETCH c INTO rec; END LOOP; END; -- c not closed</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R2601</TIP_LINK><WIKIREV>r25</WIKIREV><WIKITIMESTAMP>22 Mar 2012 - 20:27:52</WIKITIMESTAMP></RULE><RULE><ID>2603</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Avoid procedure or function calls between a SQL operation and an implicit cursor test.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>26</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>Oracle provides a variety of cursor attributes, such as %FOUND and SQL%ROWCOUNT, that you can use to obtain information about the status of your cursor, either implicit or explicit. You should avoid inserting any statements between the cursor operation (such as removing a row) and the use of an attribute against that cursor. Interposing such a statement can affect the value returned by the attribute, thereby potentially corrupting the logic of your program. In the following example, a procedure call is inserted between the DELETE statement (lines 6-7) and a check for the value of SQL%ROWCOUNT (line 11), which returns the number of rows modified by that last SQL statement executed in the session. <pre> 1 CREATE PROCEDURE remove_emp_and_process ( 2 id_in IN emp.empno%TYPE) 3 AS 4 5 BEGIN 6 DELETE FROM emp WHERE empno = id_in 7 RETURNING deptno INTO l_deptno; 8 9 process_department (...); 10 11 IF SQL%ROWCOUNT > 1 12 THEN 13 -- Too many rows deleted! Rollback and recover... 14 ROLLBACK; 15 END IF; 16* END;</pre> If the process_department procedure happens to execute a SQL statement, then the value returned by SQL%ROWCOUNT will reflect the outcome of that statement and not the DELETE statement.</TIP_TEXT><EXAMPLE_CODE></EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R2603</TIP_LINK><WIKIREV>r19</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:25:34</WIKITIMESTAMP></RULE><RULE><ID>2606</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Confirm that the cursor you are referencing has been opened.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>26</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>When working with package-based cursors and cursor variables, it is possible to reference a cursor (try to fetch from it or close it) before it has been opened. When using a package-based cursor, you should check to see if a cursor is opened before you try to reference it, as in: <pre>IF NOT my_package.my_cursor%ISOPEN THEN OPEN my_package.my_cursor; </pre> When working with cursor variables, you will usually call a function that will return the cursor variable, opened and ready for fetching. Or you will issue your own OPEN FOR statement to open the cursor and assign it to the cursor variable.</TIP_TEXT><EXAMPLE_CODE>DECLARE CURSOR c IS SELECT dummy FROM DUAL; BEGIN FOR a IN 1 .. 3 LOOP OPEN c; -- should raise END LOOP; FETCH c INTO rec; -- should raise CLOSE c; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R2606</TIP_LINK><WIKIREV>r19</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:25:37</WIKITIMESTAMP></RULE><RULE><ID>2607</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Avoid opening cursors that are already open.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>26</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>This cursor has already been opened in your session. An attempt to open it again will raise an exception. To avoid this problem, you might consider creating a local procedure to do the open, checking to see first if it has already been opened, as in: <pre>DECLARE CURSOR my_cursor IS SELECT * FROM emp; PROCEDURE open_cursor IS BEGIN IF NOT my_cursor%ISOPEN THEN OPEN my_package.my_cursor; END IF; END open_cursor; BEGIN open_cursor; END;</pre></TIP_TEXT><EXAMPLE_CODE>DECLARE CURSOR c1 IS SELECT * FROM y; BEGIN OPEN c1; OPEN c1; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R2607</TIP_LINK><WIKIREV>r20</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:25:40</WIKITIMESTAMP></RULE><RULE><ID>2608</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Ensure that any local cursors you are referencing have actually been opened.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>26</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>Before you can fetch from a local cursor or check its %FOUND, %NOTFOUND and %ROWCOUNT attributes, you must open that cursor using the OPEN or OPEN FOR statements.</TIP_TEXT><EXAMPLE_CODE>DECLARE CURSOR c IS SELECT dummy FROM DUAL; BEGIN FETCH c2 INTO d; -- should raise DECLARE CURSOR c IS SELECT dummy FROM DUAL; -- should raise BEGIN OPEN c; END; CLOSE c; -- should raise (it's a different c !) END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R2608</TIP_LINK><WIKIREV>r19</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:25:43</WIKITIMESTAMP></RULE><RULE><ID>2609</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Consider using a parameter list to reference variables declared outside this cursor.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>Using parameters makes a cursor more reusable. It avoids scoping problems too, i.e. the cursor becomes less tied to particular variables in the program. In addition, unit encapsulation makes maintenance a lot easier and cheaper.</TIP_TEXT><EXAMPLE_CODE>DECLARE a VARCHAR2 (1) := 'X'; CURSOR c IS SELECT dummy FROM DUAL WHERE dummy = a; -- should raise CURSOR c2 (a VARCHAR2) IS SELECT dummy FROM DUAL WHERE dummy = a; BEGIN DBMS_OUTPUT.put_line (a); END; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r21</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 01:17:06</WIKITIMESTAMP></RULE><RULE><ID>2610</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Limit use of weak REF CURSOR types.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>26</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//TYPE[@cat="ref_cursor"][count(RETURNS)=0]</XPATH_QUERY><TIP_TEXT>Your code contains a weak REF CURSOR type. A weak REF CURSOR is more prone to error and therefore should be used with caution. REF CURSOR types can be strong (restrictive) or weak (nonrestrictive). A strong REF CURSOR type definition specifies a return type, but a weak definition does not. The advantage of a strong REF CURSOR is that the PL/SQL compiler lets you associate a strongly typed cursor variable only with type-compatible queries. Thus, any mismatches between the RETURN type of the REF CURSOR and the variables into which a row is fetched are identified at compile time. With a weak REF CURSOR, type mismatches will occur at run-time, making them much harder to resolve. While weak REF CURSOR types are more flexible because the compiler lets you associate a weakly typed cursor variable with any query, this flexibility can make the program harder to initially understand, more complicated to maintain, and should a type mismatch occur at run-time, harder to resolve. Example: <pre>DECLARE TYPE EmpCurTyp IS REF CURSOR RETURN emp%ROWTYPE; -- strong TYPE Generic CurTyp IS REF CURSOR; -- weak</pre> In cases where weak REF CURSOR types are required, it is recommended that you use SYS_REFCURSOR rather than your own declarations. In Oracle9i Release 2 and above, Oracle provides a pre-defined weak REF CURSOR type named SYS_REFCURSOR. All weak REF CURSOR types have the same format, and differ only by name, as in: <pre>DECLARE TYPE my_weak_rct IS REF CURSOR; my_cur my_weak_rct; Rather than declare the TYPE statement again and again, you can simply reference the pre-defined type, as in: DECLARE my_cur SYS_REFCURSOR;</pre></TIP_TEXT><EXAMPLE_CODE>DECLARE -- strong cursor type is OK TYPE strong_cursor IS REF CURSOR RETURN DUAL%ROWTYPE; -- weak cursor type should raise rule #2610 TYPE weak_cursor IS REF CURSOR; -- should raise BEGIN NULL; END; / </EXAMPLE_CODE><DB_VERSION>Orace 8i and greater.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R2610</TIP_LINK><WIKIREV>r26</WIKIREV><WIKITIMESTAMP>21 Jan 2012 - 19:27:57</WIKITIMESTAMP></RULE><RULE><ID>2613</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Use %NOTFOUND cursor attribute instead of NOT %FOUND.</DEF><RULE_TYPE>3</RULE_TYPE><CAT>26</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//EXPR[@cat="not"] [EXPR[@cat="cursor_attr" and @attr=("found", "notfound")]]</XPATH_QUERY><TIP_TEXT>For readability purposes, it is better to use a cursor attribute of %NOTFOUND rather than NOT %FOUND. For example, the following: <pre>LOOP FETCH c1 INTO my_ename, my_sal, my_hiredate; EXIT WHEN NOT c1%FOUND; ... END LOOP;</pre> ...is better written as: <pre>LOOP FETCH c1 INTO my_ename, my_sal, my_hiredate; EXIT WHEN c1%NOTFOUND; ... END LOOP;</pre></TIP_TEXT><EXAMPLE_CODE>DECLARE CURSOR c1 IS SELECT dummy FROM DUAL; var_dummy CHAR (1); a INT := 0; b INT := 1; BEGIN OPEN c1; LOOP FETCH c1 INTO var_dummy; EXIT WHEN NOT c1%FOUND; -- should raise END LOOP; CLOSE c1; OPEN c1; LOOP FETCH c1 INTO var_dummy; EXIT WHEN (NOT (c1%FOUND)); -- should raise END LOOP; CLOSE c1; OPEN c1; LOOP FETCH c1 INTO var_dummy; IF ( (a != b) AND (NOT (c1%FOUND))) THEN -- should raise EXIT; END IF; END LOOP; CLOSE c1; END; /</EXAMPLE_CODE><DB_VERSION>N/A.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R2613</TIP_LINK><WIKIREV>r24</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:25:57</WIKITIMESTAMP></RULE><RULE><ID>2619</ID></RULE><RULE><ID>2620</ID></RULE><RULE><ID>2801</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Use of ROWID or UROWID.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>28</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//QNAME/IDENTIFIER[@value="rowid" or @value="urowid"]</XPATH_QUERY><TIP_TEXT>Be careful about your use of Oracle-specific datatypes like ROWID and UROWID. They might offer a slight improvement in performance over other means of identifying a single row (primary key or unique index value), but that is by no means guaranteed. Use of ROWID or UROWID means that your SQL statement will not be portable to other SQL databases. Many developers are also not familiar with these datatypes, which can make the code harder to maintain. If you do decide to use ROWID, be aware that the format of the ROWID datatype has changed in Oracle 8. A physical rowid datatype has one of two formats: The extended rowid format supports tablespace-relative data block addresses and efficiently identifies rows in partitioned tables and indexes as well as non-partitioned tables and indexes. Tables and indexes created by an Oracle8i (or higher) server always have extended rowids. A restricted rowid format is also available for backward compatibility with applications developed with Oracle7 or earlier releases.</TIP_TEXT><EXAMPLE_CODE>DECLARE u ROWID; -- should raise v ROWID; -- should raise w ROWID; -- should raise u2 UROWID; -- should raise v2 UROWID; -- should raise w2 UROWID; -- should raise BEGIN v := w; v2 := w2; u2 := v2; SELECT a, ROWID, b -- should raise INTO u, v, w FROM tab WHERE b = c; DELETE FROM tab WHERE tab.ROWID = 'AAAADeAABAAAAZSAAA'; -- should raise UPDATE TAB SET a = ROWID -- should raise WHERE b = c; INSERT INTO tab VALUES (a, ROWID); -- should raise MERGE INTO tmain USING tdelta ON (a = b) WHEN MATCHED THEN UPDATE SET a = tdelta.ROWID -- should raise WHEN NOT MATCHED THEN INSERT (x, y) VALUES (1, 2); END; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R2801</TIP_LINK><WIKIREV>r24</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:26:07</WIKITIMESTAMP></RULE><RULE><ID>2802</ID></RULE><RULE><ID>2803</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid use of LONG; consider LOB columns.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>28</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//TYPE/QNAME[@value="long"]</XPATH_QUERY><TIP_TEXT>LONG column support will be discontinued in a future Oracle release. You are advised to migrate LONG data into LOB columns. See Oracle documentation for more information about how to perform this migration.</TIP_TEXT><EXAMPLE_CODE>DECLARE a LONG; -- should raise b LONG; -- should raise BEGIN NULL; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R2803</TIP_LINK><WIKIREV>r19</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:26:13</WIKITIMESTAMP></RULE><RULE><ID>2804</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Limit use of CHAR.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>28</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//TYPE/QNAME[@value= ("char", "character")]</XPATH_QUERY><TIP_TEXT>Because CHAR is fixed length, PL/SQL will right-pad any value assigned to a CHAR variable with spaces to the maximum length specified in the declaration. Prior to Oracle7, the CHAR datatype was variable-length; Oracle did not support a fixed-length character string datatype. To improve compatibility with IBM relational databases and to comply with ANSI standards, Oracle7 reintroduced CHAR as a fixed-length datatype and offered VARCHAR2 as the variable-length datatype. You will rarely need or want to use the CHAR datatype in Oracle-based applications. In fact, it is not recommended that you ever use CHAR unless there is a specific requirement for fixed-length strings.</TIP_TEXT><EXAMPLE_CODE>DECLARE a CHAR; -- should raise b CHAR (100); -- should raise a CHAR; -- should raise b CHAR (100); -- should raise a char2; a mychar; BEGIN NULL; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R2804</TIP_LINK><WIKIREV>r23</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:26:17</WIKITIMESTAMP></RULE><RULE><ID>2805</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Limit use of VARCHAR.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>28</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//TYPE/QNAME[@value="varchar"]</XPATH_QUERY><TIP_TEXT>The VARCHAR datatype is actually a subtype of VARCHAR2, with the same range of values found in VARCHAR2. VARCHAR, in other words, is currently synonymous with VARCHAR2. Use of VARCHAR offers compatibility with ANSI and IBM relational databases. There is a strong possibility, however, that VARCHAR's meaning might change in a new version of the ANSI SQL standards. Oracle recommends that you avoid using VARCHAR if at all possible, and instead use VARCHAR2 to declare variable-length PL/SQL variables (and table columns as well).</TIP_TEXT><EXAMPLE_CODE>DECLARE a VARCHAR; -- should raise b VARCHAR (100); -- should raise a VARCHAR; -- should raise b VARCHAR (100); -- should raise a VARCHAR2; a myvarchar; BEGIN NULL; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R2805</TIP_LINK><WIKIREV>r21</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:26:20</WIKITIMESTAMP></RULE><RULE><ID>2812</ID><STATUS>5</STATUS><COLOR>Yellow</COLOR><DEF>Consider defining subtypes for variables with identical datatypes.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>28</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>In general, you should avoid hard-coded declarations such as the following: <pre> DECLARE v_ename VARCHAR2(20); - column information v_totsal NUMBER(12,2); - dollar amount v_note VARCHAR2(32767); - maximum size string, for now v_salary NUMBER(10,2); - column information BEGIN </pre> In most cases, when you declare a variable it is based on some existing type or has the same type as other variables that you will declare. In the above example, we declare a variable to hold an employee name and hard-code the maximum length. What if the DBA expands that maximum length? The code can break. The basic rules to follow are: Whenever you declare a variable to hold information from the column in a table, use the %TYPE anchor. Whenever you declare a derived value or one that is not related to a table structure use SUBTYPE. These rules are demonstrated in the following code and rewrite of the above example: <pre>CREATE OR REPLACE PACKAGE config IS SUBTYPE dollar_amt_t IS NUMBER (12, 2); SUBTYPE big_string_t IS VARCHAR2(32767); END config; / DECLARE v_ename emp.ename%TYPE; v_totsal config.dollar_amt_t; v_note config.big_string_t; v_salary emp.salary%TYPE; </pre></TIP_TEXT><EXAMPLE_CODE>DECLARE -- These 3 variables should raise CHC rulke #2812 -- They could benefit from creating a new sub-type -- such as: subtype number_10_2 is number(10,2) a NUMBER (10, 2); -- should raise b NUMBER (10, 2); -- should raise c NUMBER (10, 2); -- should raise SUBTYPE short_text IS VARCHAR2 (100); d short_text; -- These 3 variables should raise CHC rulke #2812 -- They could benefit from using an exisitng sub-type e VARCHAR2 (100); -- should raise f VARCHAR2 (100); -- should raise g VARCHAR2 (100); -- should raise -- These should raise CHC rulke #2812 -- We have two cases here: -- user_array1 could use an existing subtype -- user_record could use creating new subtype TYPE user_array1 IS TABLE OF VARCHAR2 (100) INDEX BY PLS_INTEGER; r user_array1; TYPE user_record IS RECORD ( r1 NUMBER (10, 2), -- should raise r2 NUMBER (10, 2), -- should raise r3 NUMBER (10, 2), -- should raise r4 NUMBER (10, 2) -- should raise ); s user_record; TYPE user_array2 IS VARRAY (100) OF user_record; t user_array2; BEGIN NULL; END; / CREATE OR REPLACE PACKAGE pack_2812 IS SUBTYPE pack_int IS INTEGER; -- These are OK -- we have procs/funcs using the existing sub-type PROCEDURE p01 (a pack_int); PROCEDURE p02 (a IN pack_int); PROCEDURE p03 (a OUT pack_int); PROCEDURE p04 (a IN OUT pack_int); FUNCTION f01 (a pack_int) RETURN pack_int; FUNCTION f02 (a IN pack_int) RETURN pack_int; FUNCTION f03 (a OUT pack_int) RETURN pack_int; FUNCTION f04 (a IN OUT pack_int) RETURN pack_int; -- These procs/funcs should raise CHC rule # 2812 -- They could benefit from using an existing sub-type PROCEDURE p05 (a INTEGER); PROCEDURE p06 (a IN INTEGER); PROCEDURE p07 (a OUT INTEGER); PROCEDURE p08 (a IN OUT INTEGER); FUNCTION f05 (a INTEGER) RETURN INTEGER; FUNCTION f06 (a IN INTEGER) RETURN INTEGER; FUNCTION f07 (a OUT INTEGER) RETURN INTEGER; FUNCTION f08 (a OUT INTEGER) RETURN INTEGER; -- These procs/funcs should raise CHC rule # 2812 -- They could benefit from creating a new sub-type -- such as: subtype pack_num is NUMBER; PROCEDURE p09 (a NUMBER); PROCEDURE p10 (a IN NUMBER); PROCEDURE p11 (a OUT NUMBER); PROCEDURE p12 (a IN OUT NUMBER); FUNCTION f09 (a NUMBER) RETURN NUMBER; FUNCTION f10 (a IN NUMBER) RETURN NUMBER; FUNCTION f11 (a OUT NUMBER) RETURN NUMBER; FUNCTION f12 (a OUT NUMBER) RETURN NUMBER; END; /</EXAMPLE_CODE><DB_VERSION>Not applicable.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R2812</TIP_LINK><WIKIREV>r23</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:26:24</WIKITIMESTAMP></RULE><RULE><ID>2827</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid CHAR, NCHAR, VARCHAR, VARCHAR2, NVARCHAR2 declarations with no precision.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>28</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //(VAR_DECL, ATTRIBUTE_SPEC)/.[not(DEFAULT)]/TYPE/QNAME[not(../SIZE) and @value = ("char", "nchar", "varchar", "varchar2", "nvarchar2")]</XPATH_QUERY><TIP_TEXT>The following table offers some concrete advice on potential issues you might encounter: Datatype Issues and Recommendations CHAR This is a fixed-length character string and is mostly available for compatibility purposes with code written in earlier versions of Oracle. The values assigned to CHAR variables are right-padded with spaces, which can result in unexpected behavior. Avoid CHAR unless it is specifically needed. VARCHAR This variation on the VARCHAR2 variable-length declaration is provided by Oracle for compatibility purposes. Eschew VARCHAR in favor of VARCHAR2. VARCHAR2 The greatest challenge you will run into with VARCHAR2 is to avoid the tendency to hard-code a maximum length, as in VARCHAR2(30). Use %TYPE and SUBTYPE instead. Also, prior to Oracle8, VARCHAR2 variables are treated like variable-length strings for purposes of manipulation and evaluation, but Oracle does allocate the full amount of memory upon declaration. Prior to Oracle 8, if you declare a variable of VARCHAR2(2000), then Oracle allocates 2000 bytes, even if you only use three.</TIP_TEXT><EXAMPLE_CODE>DECLARE a CHAR; -- should raise b CHAR (100); c NCHAR; -- should raise d NCHAR (100); e VARCHAR; -- should raise f VARCHAR (20); g VARCHAR2; -- should raise h VARCHAR2 (20); i NVARCHAR2; -- should raise j NVARCHAR2 (20); -- These are special cases that are OK since they have a default value -- which can be used to ascertain both the value and the implied length a4 CHAR := 'a'; ---- Note: next few lines will raise oralce error ---- ORA-06502: PL/SQL: numeric or value error: character string buffer too small. ---- Some CHC rule should handle this!!! (can comment out if desired) a5 CHAR := 'aaaaaaaaaa'; TYPE user_array1 IS TABLE OF CHAR -- This has one issue INDEX BY PLS_INTEGER; -- 1. the table type r user_array1; TYPE user_record IS RECORD ( r1 CHAR, -- should raise r2 NCHAR -- should raise ); s user_record; TYPE user_array2 IS VARRAY (100) OF user_record; t user_array2; BEGIN :x := a6; END; / PRINT x</EXAMPLE_CODE><DB_VERSION>Not applicable.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R2827</TIP_LINK><WIKIREV>r29</WIKIREV><WIKITIMESTAMP>27 Feb 2012 - 21:30:21</WIKITIMESTAMP></RULE><RULE><ID>2829</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid declaring NUMBER or NUMBER subtype with no precision.</DEF><RULE_TYPE>2</RULE_TYPE><CAT>28</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//TYPE [count(*) = 1] [(../../VAR_DECL, ../../TYPE, ../../ATTRIBUTE_SPEC)]/QNAME [@value = ("number", "real", "double precision", "float", "integer", "int", "smallint", "numeric", "decimal", "natural")] </XPATH_QUERY><TIP_TEXT>NUMBER has floating-point subtypes of REAL, DOUBLE PRECISION and FLOAT. If you do not specify precision, it defaults to 38 or the maximum supported by your system, whichever is less. NUMBER has fixed-point subtypes of DEC, DECIMAL and NUMERIC. If you do not specify precision, it defaults to 38 or the maximum supported by your system, whichever is less. You may well need all these digits of precision, but if you know you do not, create a SUBTYPE to hold the definition of the restricted precision and then use that.</TIP_TEXT><EXAMPLE_CODE>DECLARE -- These are OK, we have -- a number with precision and no scale -- a number with precision and scale -- a float with precision -- a numeric with precision and no scale -- a numeric with precision and scale -- a decimal with precision and no scale -- a decimal with precision and scale a NUMBER (9); b NUMBER (9, 2); c FLOAT (9); d NUMERIC (9); e NUMERIC (9, 2); f DECIMAL (9); g DECIMAL (9, 2); -- These all should raise CHC rule #2829, we have -- a number with no precision (thus defaults to 38 digits) -- a real, which is a floating-point number with a binary precision of 63, or 18 decimal -- a double precision, which is a floating-point number with binary precision 126 -- a float with no precision (thus defaults precision is 126 binary, or 38 decimal) -- a integer, which defaults to number with 38 digits precision -- a int, which defaults to number with 38 digits precision -- a smallint, which defaults to number with 38 digits precision -- a numeric with no precision (thus defaults to number with 38 digits) -- a decimal with no precision (thus defaults to number with 38 digits) -- a float (see sixth item above for why) via a user defined sub-type h NUMBER; -- should raise i REAL; -- should raise j DOUBLE PRECISION; -- should raise k FLOAT; -- should raise l INTEGER; -- should raise m INT; -- should raise n SMALLINT; -- should raise o NUMERIC; -- should raise p DECIMAL; -- should raise SUBTYPE big_number IS FLOAT; -- should raise q big_number; TYPE user_array1 IS TABLE OF REAL -- should raise -- This has one issue INDEX BY PLS_INTEGER; -- 1. the table type r user_array1; TYPE user_record IS RECORD ( r1 SMALLINT, -- should raise r2 INTEGER, -- should raise r3 NUMBER (4, 0), r4 NATURAL -- should raise ); s user_record; TYPE user_array2 IS VARRAY (100) OF user_record; t user_array2; BEGIN NULL; END; / CREATE OR REPLACE PACKAGE pack_2829 IS -- These all should raise CHC rule #2829, we have -- we have a sub-type that uses 38 digits precision SUBTYPE pack_smallint IS SMALLINT; -- should raise PROCEDURE p01 (a pack_smallint); PROCEDURE p02 (a IN pack_smallint); PROCEDURE p03 (a OUT pack_smallint); PROCEDURE p04 (a IN OUT pack_smallint); FUNCTION f01 (a pack_smallint) RETURN pack_smallint; FUNCTION f02 (a IN pack_smallint) RETURN pack_smallint; FUNCTION f03 (a OUT pack_smallint) RETURN pack_smallint; FUNCTION f04 (a IN OUT pack_smallint) RETURN pack_smallint; END; /</EXAMPLE_CODE><DB_VERSION>Not applicable.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R2829</TIP_LINK><WIKIREV>r30</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:26:31</WIKITIMESTAMP></RULE><RULE><ID>2830</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid declaring CHAR, NCHAR, VARCHAR, VARCHAR2, NVARCHAR2 with length greater than 500 characters.</DEF><RULE_TYPE>2</RULE_TYPE><CAT>28</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//TYPE/SIZE[@value>500] [../QNAME/@value = ("char", "nchar", "varchar", "varchar2", "nvarchar2")]</XPATH_QUERY><TIP_TEXT>Many developers declare CHAR variables larger than they really need to be to avoid possible overruns. If you declare a variable VARCHAR2(2000), for example, then Oracle does allocate 2000 bytes, even if you only use three. This practice can result in excessive memory usage. Review large variable declarations to determine if they are necessary and/or appropriate.</TIP_TEXT><EXAMPLE_CODE>DECLARE -- These are OK SUBTYPE small_text IS VARCHAR2 (100); a CHAR (100); b NCHAR (100); c VARCHAR (100); d VARCHAR2 (100); e NVARCHAR2 (100); f small_text; -- These all should raise CHC rule #2830 SUBTYPE big_text IS VARCHAR2 (600); -- should raise g CHAR (600); -- should raise h NCHAR (600); -- should raise i VARCHAR (600); -- should raise j VARCHAR2 (600); -- should raise k NVARCHAR2 (600); -- should raise l big_text; TYPE user_array1 IS TABLE OF VARCHAR2 (600) -- should raise -- This has two issues INDEX BY VARCHAR2 (600); -- should raise -- 1. the table type r user_array1; -- 2. the index type TYPE user_record IS RECORD ( r1 CHAR (600), -- should raise r2 NCHAR (600), -- should raise r3 VARCHAR (600), -- should raise r4 VARCHAR2 (600) -- should raise ); s user_record; TYPE user_array2 IS VARRAY (100) OF user_record; t user_array2; BEGIN NULL; END; / CREATE OR REPLACE PACKAGE pack_2830 IS -- These all should raise CHC rule #2830 SUBTYPE pack_big_text IS VARCHAR2 (600); -- should raise PROCEDURE p01 (a pack_big_text); PROCEDURE p02 (a IN pack_big_text); PROCEDURE p03 (a OUT pack_big_text); PROCEDURE p04 (a IN OUT pack_big_text); FUNCTION f01 (a pack_big_text) RETURN pack_big_text; FUNCTION f02 (a IN pack_big_text) RETURN pack_big_text; FUNCTION f03 (a OUT pack_big_text) RETURN pack_big_text; FUNCTION f04 (a IN OUT pack_big_text) RETURN pack_big_text; END; /</EXAMPLE_CODE><DB_VERSION>Not applicable.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R2830</TIP_LINK><WIKIREV>r24</WIKIREV><WIKITIMESTAMP>26 Sep 2011 - 21:36:30</WIKITIMESTAMP></RULE><RULE><ID>2831</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Use PLS_INTEGER instead of INTEGER or equivalent subtypes.</DEF><RULE_TYPE>2</RULE_TYPE><CAT>28</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> ( //TYPE/QNAME [@value=("integer", "int", "smallint", "binary_integer", "natural", "naturaln", "positive", "positiven", "signtype")], //TYPE/QNAME [@value=("number", "numeric", "decimal")] [../SIZE/@value=("1", "2", "3", "4", "5", "6", "7", "8", "9")] [../SCALE/@value="0" or not(../SCALE)] )</XPATH_QUERY><TIP_TEXT>PLS_INTEGER offers the most efficient datatype for manipulating integer values that fall between -2,147,483,647 and 2,147,483,647 (between -2**31+1 and 2**31-1). In addition, PLS_INTEGER throws an exception in case of overflow. Whenever you are working with integers that fall within this range, declare those variables as PLS_INTEGER. If you are performing intensive manipulation of integers, you may see an improvement in performance of your code simply by changing the datatype. According to Oracle, the distinction between BINARY_INTEGER and PLS_INTEGER has vanished, therefore it is only recommended to replace BINARY_INTEGER with Oracle versions 9.x and older. Note: NUMBER has INTEGER-like subtypes of INT and SMALLINT. They basically equate to INTEGER. BINARY_INTEGER has subtypes of NATURAL, NATURALN, POSITIVE, POSITIVEN and SIGNTYPE.</TIP_TEXT><EXAMPLE_CODE>DECLARE -- all below should raise a INTEGER; -- should raise b INT; -- should raise c SMALLINT; -- should raise SUBTYPE user_int IS INTEGER; -- should raise d user_int; -- should raise /* note: numbers with precision and no scale are essentially integers, although we may want to limit firing the rule to when precision <= 10 or some other threshold that we can agree upon */ e NUMBER (4); -- should raise f NUMBER (4, 0); -- should raise g NUMERIC (4); -- should raise h DECIMAL (4); -- should raise i NUMERIC (4, 0); -- should raise j DECIMAL (4, 0); -- should raise k BINARY_INTEGER; -- should raise l NATURAL; -- should raise m NATURALN := 1; -- should raise n POSITIVE; -- should raise o POSITIVEN := 1; -- should raise p SIGNTYPE; -- should raise TYPE user_array1 IS TABLE OF INTEGER -- This has two issues -- 1. the table type -- 2. the index type INDEX BY BINARY_INTEGER; -- should raise r user_array1; -- should raise TYPE user_record IS RECORD ( r1 SMALLINT, -- should raise r2 INTEGER, -- should raise r3 NUMBER (4, 0), -- should raise r4 NATURAL -- should raise ); -- should not raise s user_record; TYPE user_array2 IS VARRAY (100) OF user_record; t user_array2; BEGIN NULL; END; / CREATE OR REPLACE PACKAGE pack_2831 IS -- These all should raise CHC rule #2831 SUBTYPE pack_num4 IS NUMBER (4, 0); -- should raise PROCEDURE p01 (a pack_num4); PROCEDURE p02 (a IN pack_num4); PROCEDURE p03 (a OUT pack_num4); PROCEDURE p04 (a IN OUT pack_num4); FUNCTION f01 (a pack_num4) RETURN pack_num4; FUNCTION f02 (a IN pack_num4) RETURN pack_num4; FUNCTION f03 (a OUT pack_num4) RETURN pack_num4; FUNCTION f04 (a IN OUT pack_num4) RETURN pack_num4; END; /</EXAMPLE_CODE><DB_VERSION>Not applicable.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R2831</TIP_LINK><WIKIREV>r24</WIKIREV><WIKITIMESTAMP>03 Oct 2011 - 02:14:45</WIKITIMESTAMP></RULE><RULE><ID>2832</ID><STATUS>3</STATUS><COLOR>Pink</COLOR><DEF>Avoid hard-coded constraints on VARCHAR2 variables by anchoring the declaration to a variable or column with %TYPE, or by defining a SUBTYPE and then declaring the variable based on that subtype.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>28</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//TYPE/SIZE [../QNAME/@value = "varchar2"]</XPATH_QUERY><TIP_TEXT>Avoid hard-coded constraints on VARCHAR2 variables by anchoring the declaration to a variable or column with %TYPE, or by defining a SUBTYPE and then declaring the variable based on that subtype.</TIP_TEXT><EXAMPLE_CODE>DECLARE -- These are OK SUBTYPE small_text IS VARCHAR2 (100); a CHAR (100); b NCHAR (100); c VARCHAR (100); d VARCHAR2 (100); e NVARCHAR2 (100); f small_text; SUBTYPE big_text IS VARCHAR2 (600); -- should raise g CHAR (600); h NCHAR (600); i VARCHAR (600); j VARCHAR2 (600); -- should raise k NVARCHAR2 (600); l big_text; TYPE user_array1 IS TABLE OF VARCHAR2 (600) -- should raise -- This has two issues INDEX BY VARCHAR2 (600); -- should raise -- 1. the table type r user_array1; -- 2. the index type TYPE user_record IS RECORD ( r1 CHAR (600), r2 NCHAR (600), r3 VARCHAR (600), r4 VARCHAR2 (600) -- should raise ); s user_record; TYPE user_array2 IS VARRAY (100) OF user_record; t user_array2; BEGIN NULL; END; / CREATE OR REPLACE PACKAGE pack_2830 IS -- These all should raise CHC rule #2830 SUBTYPE pack_big_text IS VARCHAR2 (600); -- should raise PROCEDURE p01 (a pack_big_text); PROCEDURE p02 (a IN pack_big_text); PROCEDURE p03 (a OUT pack_big_text); PROCEDURE p04 (a IN OUT pack_big_text); FUNCTION f01 (a pack_big_text) RETURN pack_big_text; FUNCTION f02 (a IN pack_big_text) RETURN pack_big_text; FUNCTION f03 (a OUT pack_big_text) RETURN pack_big_text; FUNCTION f04 (a IN OUT pack_big_text) RETURN pack_big_text; END; /</EXAMPLE_CODE><DB_VERSION>Not applicable.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R2830</TIP_LINK><WIKIREV>r4</WIKIREV><WIKITIMESTAMP>23 Feb 2012 - 21:33:50</WIKITIMESTAMP></RULE><RULE><ID>3001</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid using a WHEN OTHERS clause in an exception section without any other specific handlers.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>30</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//EXCEPTION_BLOCK[count(EXCEPTION) = 1]/EXCEPTION[count(QNAME) = 0]/TOKEN[2]</XPATH_QUERY><TIP_TEXT>There isn't necessarily anything wrong with using WHEN OTHERS, but it can cause you to "lose" error information unless your handler code is relatively sophisticated. Generally, you should use WHEN OTHERS to grab any and every error only after you have thought about your executable section and decided that you are not able to trap any specific exceptions. If you know, on the other hand, that a certain exception might be raised, include a handler for that error. Here is an exception section that clearly expects a DUP_VAL_ON_INDEX error to be raised, but buries that information in WHEN OTHERS: <pre> EXCEPTION WHEN OTHERS THEN IF SQLCODE = -1 THEN update_instead (...); ELSE err.log; RAISE; END IF; </pre> This is a much better way to support the same functionality: <pre>EXCEPTION WHEN DUP_VAL_ON_INDEX THEN update_instead (...); WHEN OTHERS THEN err.log; RAISE; </pre> By declaring two different exception handlers, the code more clearly states what we expect to have happen and how we want to handle the errors. That makes it easier to maintain and enhance. We also avoid hard-coding error numbers in checks against SQLCODE.</TIP_TEXT><EXAMPLE_CODE>BEGIN NULL; EXCEPTION WHEN OTHERS THEN -- should raise NULL; END; BEGIN NULL; EXCEPTION WHEN NO_DATA_FOUND THEN some_proc; WHEN OTHERS THEN NULL; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R3001</TIP_LINK><WIKIREV>r20</WIKIREV><WIKITIMESTAMP>23 Feb 2012 - 21:36:06</WIKITIMESTAMP></RULE><RULE><ID>3002</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Avoid use of EXCEPTION_INIT pragma for a -20,NNN error.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>30</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>If you are not very organized in the way you allocate, define and use the error numbers between -20,999 and -20,000 (those reserved by Oracle for its user community), it is very easy to end up with conflicting usages. You should assign these error numbers to named constants and consolidate all definitions within a single package. Oracle allocates 1000 error numbers, between -20,000 and -20,999, for users to use for their own application-specific errors (such as "Employee must be 18 years old" or "Reservation date must be in the future"). Define all error numbers and their associated messages in a database table or operating system file. Build a package that gives names to these errors, and then raise the errors using those names and not any hard-coded values. Here is a fairly typical hard-coded, error-prone program with RAISE_APPLICATION_ERROR. This procedure is designed to stop updates and inserts when an employee is younger than 18 and is based on the assumption that no one has used error 20734 yet: <pre>CREATE OR REPLACE PROCEDURE check_hiredate ( date_in IN DATE) IS BEGIN IF date_in < ADD_MONTHS (SYSDATE, -1 * 12 * 18) THEN RAISE_APPLICATION_ERROR ( -20734, 'Employee must be 18 years old.'); END IF; END;</pre> Note all of the hard-coding. This will cause problems if another programmer also decides to use error number 20734. Here is a much cleaner approach: <pre>CREATE OR REPLACE PROCEDURE check_hiredate ( date_in IN DATE) IS BEGIN IF emp_rules.emp_too_young (date_in) THEN err.raise (errnums.emp_too_young); END IF; END;</pre> First, the logic defining a "too young" employee has been moved to a function. For error handling, the err.raise procedure is called to raise the error. Which error? The user goes to the list of predefined exceptions (either in documentation or via a GUI interface) and picks, by name, the one that matches. By following this practice, developers avoid conflicts over the same error number, which leads to massive confusion. Developers also don't have to decide the best way to raise an error. They can call the err.raise procedure and let it do the work. However, you will need to build code and data to maintain known errors, associated with numbers and error text.</TIP_TEXT><EXAMPLE_CODE>DECLARE e EXCEPTION; PRAGMA EXCEPTION_INIT (e, -20123); -- should raise PRAGMA EXCEPTION_INIT (e, -21234); -- not -20,NNN PRAGMA EXCEPTION_INIT (e, -12345); -- not -20,NNN PRAGMA EXCEPTION_INIT (e, -2000); -- not -20,NNN PRAGMA EXCEPTION_INIT (e, -20000); -- should raise PRAGMA EXCEPTION_INIT (e, 20123); -- not -20,NNN a NUMBER DEFAULT 1; BEGIN IF a = 1 THEN BEGIN RAISE e; END; END IF; EXCEPTION WHEN e THEN DBMS_OUTPUT.put_line ('other exception raised'); END; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R3002</TIP_LINK><WIKIREV>r24</WIKIREV><WIKITIMESTAMP>18 Jan 2012 - 09:01:43</WIKITIMESTAMP></RULE><RULE><ID>3003</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid use of RAISE_APPLICATION_ERROR built-in with a hard-coded -20,NNN error number or hard-coded message.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>30</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //CALL/*[IDENTIFIER/@value="RAISE_APPLICATION_ERROR"]/*/(PARAM[1]/EXPR[@cat="unary_minus"], PARAM[2][not(QNAME)]) </XPATH_QUERY><TIP_TEXT>If you are not very organized in the way you allocate, define and use the error numbers between -20,999 and -20,000 (those reserved by Oracle for its user community), it is very easy to end up with conflicting usages. You should assign these error numbers to named constants and consolidate all definitions within a single package. When you call RAISE_APPLICATION_ERROR, you should reference these named elements and error message text stored in a table. Use your own raise procedure in place of explicit calls to RAISE_APPLICATION_ERROR. If you are raising a "system" exception like NO_DATA_FOUND, you use RAISE. But when you want to raise an application-specific error, you use RAISE_APPLICATION_ERROR. If you use the latter, you then have to provide an error number and message. This leads to unnecessary and damaging hard coding. A more fail-safe approach is to provide a predefined raise procedure that automatically checks the error number and determines the correct way to raise the error. Instead of writing code like this: <pre>RAISE_APPLICATION_ERROR ( -20734, 'Employee must be 18 years old.');</pre> you should write code like this: <pre>err.raise (errnums.emp_too_young);</pre> Here is an example of how you might construct a generic exception raiser: <pre>PROCEDURE raise ( errcode IN PLS_INTEGER := NULL, errmsg IN VARCHAR2 := NULL ) IS l_errcode PLS_INTEGER := NVL (errcode, SQLCODE); l_errmsg PLS_INTEGER := NVL (errmsg, SQLERRM); BEGIN IF l_errcode BETWEEN -20999 AND -20000 THEN RAISE_APPLICATION_ERROR (l_errcode, l_errmsg); /* Use positive error numbers -- lots to choose from! */ ELSIF l_errcode > 0 AND l_errcode NOT IN (1, 100) THEN RAISE_APPLICATION_ERROR (-20000, l_errcode || '-' || l_errmsg); /* Re-raise any other exception using dynamic PL/SQL. */ ELSIF l_errcode != 0 THEN EXECUTE IMMEDIATE 'DECLARE myexc EXCEPTION; ' || ' PRAGMA EXCEPTION_INIT (myexc, ' || TO_CHAR (l_errcode) || ');' ||'BEGIN RAISE myexc; END;' ; END IF; END;</pre> There are several benefits to using raise procedures. Individual developers do not have to make judgment calls about how they should raise the exception (RAISE? RAISE_APPLICATION_ERROR?). They simply pass the appropriate error number (hopefully identified via a named constant) and let the raise "engine" do the work. Also, you can choose to use positive error numbers for your own application-specific exceptions. By taking this approach, you are not constrained to error numbers between -20,999 and -20,000, some of which Oracle also uses. The err.raise procedure intercepts positive error numbers and passes them back to the calling program as an exception by bundling the error number and message into a single string in the call to RAISE_APPLICATION_ERROR.</TIP_TEXT><EXAMPLE_CODE>BEGIN raise_application_error (-20010, 'Number Too Large'); -- should raise raise_application_error (123, 'My exception'); raise_application_error (-20011, 'Number Too Large'); -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R3003</TIP_LINK><WIKIREV>r20</WIKIREV><WIKITIMESTAMP>18 Jan 2012 - 09:19:22</WIKITIMESTAMP></RULE><RULE><ID>3004</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid masking exceptions with use of NULL statement in an exception handler.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>30</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//EXCEPTION_BLOCK//NULL</XPATH_QUERY><TIP_TEXT>This NULL statement appears to be the only statement in the exception handler; you have in effect written exception handler in which all errors will be ignored. This may be what you want, but you should check to be sure. If you are writing a program in which you can predict that a certain error will occur, you should include a handler in your code for that, allowing for a graceful and informative failure. The form that this failure takes does not necessarily need to be an exception. When writing functions, you may well decide that in the case of certain exceptions, you will want to return a value such as NULL, rather than allow an exception to propagate out of the function. This recommendation is easily demonstrated with the SELECT INTO lookup query. An error that often occurs is NO_DATA_FOUND, indicating that the query did not identify any rows. In the example below, the SELECT INTO is inside a function, but the NO_DATA_FOUND exception is not allowed to propagate out of the function: <pre>CREATE FUNCTION book_title (isbn_in IN book.isbn%TYPE) RETURN book.title%TYPE IS l_title book.title%TYPE; BEGIN SELECT title INTO l_title FROM book WHERE isbn = isbn_in; RETURN l_rec.title; EXCEPTION WHEN NO_DATA_FOUND THEN RETURN NULL; END;</pre> In other words, if the ISBN passed to the function finds no book, return NULL for the title. This is an unambiguous indicator of failure; a book must have a title. The example above does not allow NO_DATA_FOUND to propagate (go un-handled) out of the function. It uses a SELECT INTO (implicit query) to fetch the book title; Oracle's implementation of implicit queries means that NO_DATA_FOUND (as well as TOO_MANY_ROWS) might be raised. That doesn't necessarily mean that there really is an exception in my function when no row is found. In fact, I might be expecting to not find a match. By returning NULL rather than propagating an exception, it is left up to the user of the function to decide how they would like to deal with a "no row found" situation. They might raise an exception, as in: <pre>BEGIN l_title := book_title (l_isbn); IF l_title IS NULL THEN RAISE NO_DATA_FOUND; END IF; or they might decide that such a result means that everything is, in fact, as desired: BEGIN l_title := book_title (l_isbn); IF l_title IS NULL THEN process_new_book (l_isbn); END IF;</pre> Programs that properly handle exceptions are better behaved and more likely to be useful and used. If the exception is allowed to propagate out, this function would be unpredictable and hard to integrate into one's application, since exception handlers must be coded in the caller's code block.</TIP_TEXT><EXAMPLE_CODE>BEGIN NULL; EXCEPTION WHEN NO_DATA_FOUND THEN NULL; -- should raise WHEN other THEN IF some_condition THEN NULL; -- should raise ELSE NULL; -- should raise END IF; END; BEGIN f; EXCEPTION WHEN NO_DATA_FOUND THEN <<LAB>> -- should raise NULL; WHEN OTHERS THEN g; END; / BEGIN f; EXCEPTION WHEN NO_DATA_FOUND THEN <<lab1>> -- should raise NULL; WHEN some_other_exception THEN <<lab1>> -- should raise <<lab2>> NULL; WHEN OTHERS THEN g; END; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R3004</TIP_LINK><WIKIREV>r20</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:26:52</WIKITIMESTAMP></RULE><RULE><ID>3005</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Avoid unhandled exceptions.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>30</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>This exception is not handled by any exception handler in the block in which the error was raised. This may be your intention, but you should review the code to confirm this behavior. If you are raising an error in a program, then you are clearly predicting a situation in which that error will occur. You should consider including a handler in your code for predictable errors, allowing for a graceful and informative failure. After all, it is much more difficult for an enclosing block to be aware of the various errors you might raise and more importantly, what should be done in response to the error. The form that this failure takes does not necessarily need to be an exception. When writing functions, you may well decide that in the case of certain exceptions, you will want to return a value such as NULL, rather than allow an exception to propagate out of the function. This recommendation is easily demonstrated with the SELECT INTO lookup query. An error that often occurs is NO_DATA_FOUND, indicating that the query did not identify any rows. In the example below, the SELECT INTO is inside a function, but the NO_DATA_FOUND exception is not allowed to propagate out of the function: <pre>CREATE FUNCTION book_title (isbn_in IN book.isbn%TYPE) RETURN book.title%TYPE IS l_title book.title%TYPE; BEGIN SELECT title INTO l_title FROM book WHERE isbn = isbn_in; RETURN l_rec.title; EXCEPTION WHEN NO_DATA_FOUND THEN RETURN NULL; END;</pre> In other words, if the ISBN passed to the function finds no book, return NULL for the title. This is an unambiguous indicator of failure; a book must have a title. The example above does not allow NO_DATA_FOUND to propagate (go un-handled) out of the function. It uses a SELECT INTO (implicit query) to fetch the book title; Oracle's implementation of implicit queries means that NO_DATA_FOUND (as well as TOO_MANY_ROWS) might be raised. That doesn't necessarily mean that there really is an exception in my function when no row is found. In fact, I might be expecting to not find a match. By returning NULL rather than propagating an exception, it is left up to the user of the function to decide how they would like to deal with a "no row found" situation. They might raise an exception, as in: <pre>BEGIN l_title := book_title (l_isbn); IF l_title IS NULL THEN RAISE NO_DATA_FOUND; END IF;</pre> or they might decide that such a result means that everything is, in fact, as desired: <pre>BEGIN l_title := book_title (l_isbn); IF l_title IS NULL THEN process_new_book (l_isbn); END IF;</pre> Programs that properly handle exceptions are better behaved and more likely to be useful and used. If the exception is allowed to propagate out, this function would be unpredictable and hard to integrate into one's application, since exception handlers must be coded in the caller's code block.</TIP_TEXT><EXAMPLE_CODE>BEGIN f; > BEGIN RAISE some_exception; END l0; RAISE some_data_found; -- should raise (unhandled and not predefined) > BEGIN RAISE some_exception; END l1; EXCEPTION WHEN some_exception THEN g; END; / DECLARE some_exception EXCEPTION; a NUMBER := 1; BEGIN BEGIN BEGIN a := 1; IF (a = 1) THEN BEGIN RAISE some_exception; -- should raise END; END IF; END; BEGIN a := 2; EXCEPTION WHEN some_exception THEN DBMS_OUTPUT.put_line ('some exception raised'); END; END; END; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R3005</TIP_LINK><WIKIREV>r18</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:26:55</WIKITIMESTAMP></RULE><RULE><ID>3006</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid using Oracle's predefined exceptions.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>30</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//RAISE//QNAME[@value= ("access_into_null", "collection_is_null", "cursor_already_open", "dup_val_on_index", "invalid_cursor", "invalid_number", "login_denied", "no_data_found", "not_logged_on", "program_error", "rowtype_mismatch", "self_is_null", "storage_error", "subscript_beyond_count", "subscript_outside_limit", "sys_invalid_rowid", "too_many_rows", "timeout_on_resource", "transaction_backed_out", "value_error", "zero_divide") ]</XPATH_QUERY><TIP_TEXT>You have raised an exception whose name was defined by Oracle. While it is possible that you have a good reason for "using" one of Oracle's predefined exceptions, you should make sure that you would not be better off declaring your own exception and raising that instead. If you decide to change the exception you are using, you should apply the same consideration to your own exceptions. Specifically, do not "re-use" exceptions. You should define a separate exception for each error condition, rather than use the same exception for different circumstances. Being as specific as possible with the errors raised will allow developers to check for, and handle, the different kinds of errors the code might produce.</TIP_TEXT><EXAMPLE_CODE>BEGIN RAISE e; RAISE access_into_null; -- should raise RAISE collection_is_null; -- should raise RAISE cursor_already_open; -- should raise RAISE dup_val_on_index; -- should raise RAISE invalid_cursor; -- should raise RAISE invalid_number; -- should raise RAISE login_denied; -- should raise RAISE no_data_found; -- should raise RAISE not_logged_on; -- should raise RAISE program_error; -- should raise RAISE rowtype_mismatch; -- should raise RAISE self_is_null; -- should raise RAISE storage_error; -- should raise RAISE subscript_beyond_count; -- should raise RAISE subscript_outside_limit; -- should raise RAISE sys_invalid_rowid; -- should raise RAISE timeout_on_resource; -- should raise RAISE too_many_rows; -- should raise RAISE transaction_backed_out; -- should raise RAISE value_error; -- should raise RAISE zero_divide; -- should raise RAISE something_else; END; DECLARE a NUMBER; BEGIN a := 0; IF (TRUE) THEN RAISE NO_DATA_FOUND; -- should raise END IF; EXCEPTION WHEN NO_DATA_FOUND THEN g; END; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R3006</TIP_LINK><WIKIREV>r21</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:26:58</WIKITIMESTAMP></RULE><RULE><ID>3007</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Ensure use of raising and handling an exception in the same scope.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>30</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>While it is perfectly legitimate to raise an exception and then trap that exception in the same scope, developers sometimes do this when they misuse the exception handling mechanism as a kind of GOTO command. One good indicator of this problem may be found in the declaration of the exception. If the name of the exception describes an action as opposed to an error, then there is a very good chance that exceptions are being misused. Only RAISE exceptions for errors, not to branch execution control. The RAISE statement is an easy and powerful way to abort normal processing in a program and immediately "go to" the appropriate WHEN handler. You should, however, never use RAISE in this way. You should only raise an exception when an error has occurred, not to control program flow. The following program demonstrates the problem; it performs a full table scan of a collection and immediately exits when it finds a match. The exit_function exception is used to abort the function if the input title is NULL; it is also used as the last line in the function: <pre>CREATE OR REPLACE FUNCTION book_from_list ( list_in IN book_tabtype, title_in IN book.title%TYPE) RETURN book%ROWTYPE IS exit_function EXCEPTION; BEGIN IF title_in IS NULL THEN RAISE exit_function; END IF; FOR indx IN list_in.FIRST .. list_in.LAST LOOP IF list_in(indx).title = title_in THEN RETURN list_in(indx); END IF; END LOOP; RAISE exit_function; EXCEPTION WHEN exit_function THEN RETURN NULL; END;</pre> The program executes successfully, only to have an exception raised at the end. This is very poorly structured code: hard to understand and hard to maintain. A better approach follows: <pre>CREATE OR REPLACE FUNCTION book_from_list ( list_in IN book_tabtype, title_in IN book.title%TYPE) RETURN book%ROWTYPE IS indx PLS_INTEGER; retval book%ROWTYPE; BEGIN IF title_in IS NOT NULL THEN indx := list_in.FIRST; LOOP IF list_in(indx).title = title_in THEN retval := list_in(indx); END IF; indx := list_in.NEXT (indx); END LOOP; END IF; RETURN retval; END;</pre> Be on the lookout for a clear symptom of this misuse of error handling: declared exceptions whose names describe actions ("exit_function") rather than errors ("null_title"). Avoiding this incorrect use of RAISE will make your code more straightforward and easier to read, debug, and maintain.</TIP_TEXT><EXAMPLE_CODE>BEGIN f; RAISE some_data_found; -- should raise EXCEPTION WHEN some_data_found THEN g; END; / BEGIN RAISE e; -- should raise EXCEPTION WHEN e THEN f; END; / DECLARE a NUMBER; e EXCEPTION; BEGIN a := 2; IF (a = 2) THEN BEGIN a := 3; IF (a >= 3) THEN BEGIN RAISE e; -- should raise EXCEPTION WHEN e THEN DBMS_OUTPUT.put_line ( 'We raise the exception on the same level'); END; END IF; EXCEPTION WHEN no_e THEN DBMS_OUTPUT.put_line ( 'We raise the exception on the next level'); END; END IF; EXCEPTION WHEN e THEN DBMS_OUTPUT.put_line ( 'We raise the exception on the upper (global) level'); END; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R3007</TIP_LINK><WIKIREV>r18</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:27:02</WIKITIMESTAMP></RULE><RULE><ID>3010</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid naming exceptions the same as Oracle's predefined exceptions.</DEF><RULE_TYPE>3</RULE_TYPE><CAT>30</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//EXCEPTION_DECL/QNAME[@value= ("access_into_null", "collection_is_null", "cursor_already_open", "dup_val_on_index", "invalid_cursor", "invalid_number", "login_denied", "no_data_found", "not_logged_on", "program_error", "rowtype_mismatch", "self_is_null", "storage_error", "subscript_beyond_count", "subscript_outside_limit", "sys_invalid_rowid", "too_many_rows", "timeout_on_resource", "transaction_backed_out", "value_error", "zero_divide", "case_not_found")]</XPATH_QUERY><TIP_TEXT>Your code contains a declaration of an exception named the same as one of Oracle's predefined exceptions. This is error prone because your local declaration overrides the global declaration. While it's technically possible to use the same names, it causes confusion for others needing to read and maintain this code. Additionally, you will need to be very careful to prefix "STANDARD." in front of any reference that needs to use Oracle's default exception behavior. In general, you should avoid using any of the names of exceptions, datatypes, programs and other constructs that are defined in the two default packages, STANDARD and DBMS_STANDARD. Here is an example of the kind of code YOU SHOULD AVOID: <pre>DECLARE d VARCHAR2(1); no_data_found EXCEPTION; BEGIN SELECT dummy INTO d FROM dual WHERE 1=2; IF d IS NULL THEN RAISE no_data_found; END IF; EXCEPTION WHEN no_data_found THEN DBMS_OUTPUT.PUT_LINE ('No dummy!'); END; /</pre></TIP_TEXT><EXAMPLE_CODE>DECLARE e EXCEPTION; access_into_null EXCEPTION; -- should raise collection_is_null EXCEPTION; -- should raise cursor_already_open EXCEPTION; -- should raise dup_val_on_index EXCEPTION; -- should raise invalid_cursor EXCEPTION; -- should raise invalid_number EXCEPTION; -- should raise login_denied EXCEPTION; -- should raise no_data_found EXCEPTION; -- should raise not_logged_on EXCEPTION; -- should raise program_error EXCEPTION; -- should raise rowtype_mismatch EXCEPTION; -- should raise self_is_null EXCEPTION; -- should raise storage_error EXCEPTION; -- should raise subscript_beyond_count EXCEPTION; -- should raise subscript_outside_limit EXCEPTION; -- should raise sys_invalid_rowid EXCEPTION; -- should raise timeout_on_resource EXCEPTION; -- should raise too_many_rows EXCEPTION; -- should raise transaction_backed_out EXCEPTION; -- should raise value_error EXCEPTION; -- should raise zero_divide EXCEPTION; -- should raise case_not_found EXCEPTION; -- should raise something_else EXCEPTION; BEGIN NULL; END;</EXAMPLE_CODE><DB_VERSION>Although there are different standard exceptions for the various Oracle versions, we can simply catch any user defined exception which is on our list and is current for the most recent database version. The reason is that we can tell users that it&#8217;s advisable to correct those issues to eliminate the poosibility of errors later when upgrading their database version.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R3010</TIP_LINK><WIKIREV>r23</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:27:05</WIKITIMESTAMP></RULE><RULE><ID>3013</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Use DBMS_UTILITY.FORMAT_ERROR_STACK in place of SQLERRM.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY>//QNAME[@value="SQLERRM"]</XPATH_QUERY><TIP_TEXT>Use DBMS_UTILITY.FORMAT_ERROR_STACK in place of SQLERRM.</TIP_TEXT><EXAMPLE_CODE>BEGIN SQLERRM; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r20</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 01:50:40</WIKITIMESTAMP></RULE><RULE><ID>3014</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Blocks containing EXECUTE IMMEDIATE should trap exceptions.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY>//EXECUTE_IMMEDIATE[not(ancestor::BLOCK[EXCEPTION_BLOCK])]</XPATH_QUERY><TIP_TEXT>Blocks containing EXECUTE IMMEDIATE should trap exceptions.</TIP_TEXT><EXAMPLE_CODE>BEGIN EXECUTE IMMEDIATE ('truncate table location'); -- should raise END; / BEGIN EXECUTE IMMEDIATE ('truncate table location'); EXCEPTION WHEN OTHERS THEN NULL; END; / BEGIN BEGIN EXECUTE IMMEDIATE 'hello'; EXCEPTION WHEN OTHERS THEN NULL; END; -- BEGIN EXECUTE IMMEDIATE 'hello'; -- should raise END; -- IF a = 3 THEN EXECUTE IMMEDIATE 'hello'; -- should raise END IF; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r20</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 01:44:08</WIKITIMESTAMP></RULE><RULE><ID>3015</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Use the SAVE EXCEPTIONS clause with FORALL and check for error -24381 to improve error handling.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //LOOP [@cat="forall"] [not(@save_exceptions="true") or not(following::EXCEPTION/QNAME[@value=ancestor-or-self::*/preceding-sibling::PRAGMA/* /PRAGMA_PARAM[following-sibling::PRAGMA_PARAM[1]/@value="-24381"]/@value])] </XPATH_QUERY><TIP_TEXT>Use the SAVE EXCEPTIONS clause with FORALL and check for error -24381 to improve error handling.</TIP_TEXT><EXAMPLE_CODE>PROCEDURE upd_for_dept (dept_in IN employee.department_id%TYPE, newsal IN employee.salary%TYPE) IS bulk_errors EXCEPTION; PRAGMA EXCEPTION_INIT (bulk_errors, -24381); --... BEGIN FORALL indx IN employees.FIRST .. employees.LAST SAVE EXCEPTIONS INSERT INTO employee_history (employee_id, salary, hire_date) VALUES ( employees (indx), salaries (indx), hire_dates (indx)); EXCEPTION WHEN bulk_errors THEN FOR indx IN 1 .. SQL%BULK_EXCEPTIONS.COUNT LOOP log_error (SQL%BULK_EXCEPTIONS (indx).ERROR_CODE); END LOOP; END upd_for_dept; PROCEDURE upd_for_dept (dept_in IN employee.department_id%TYPE, newsal IN employee.salary%TYPE) IS bulk_errors EXCEPTION; PRAGMA EXCEPTION_INIT (bulk_errors, -1); --... BEGIN DECLARE -- this definition should not count bulk_errors EXCEPTION; PRAGMA EXCEPTION_INIT (bulk_errors, -24381); BEGIN NULL; END; FORALL indx IN employees.FIRST .. employees.LAST SAVE EXCEPTIONS INSERT INTO employee_history (employee_id, salary, hire_date) VALUES ( employees (indx), salaries (indx), hire_dates (indx)); -- should raise EXCEPTION WHEN bulk_errors THEN FOR indx IN 1 .. SQL%BULK_EXCEPTIONS.COUNT LOOP log_error (SQL%BULK_EXCEPTIONS (indx).ERROR_CODE); END LOOP; END upd_for_dept;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r26</WIKIREV><WIKITIMESTAMP>23 Feb 2012 - 21:40:35</WIKITIMESTAMP></RULE><RULE><ID>3017</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Consider calling DBMS_UTILITY.FORMAT_ERROR_BACKTRACE in your exception section.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //EXCEPTION_BLOCK [not(descendant::QNAME [IDENTIFIER[last() - 1][@value="DBMS_UTILITY"] and IDENTIFIER[last()][@value="FORMAT_ERROR_BACKTRACE"] ] )] /TOKEN[1] </XPATH_QUERY><TIP_TEXT>Consider calling DBMS_UTILITY.FORMAT_ERROR_BACKTRACE in your exception section.</TIP_TEXT><EXAMPLE_CODE>BEGIN NULL; EXCEPTION WHEN NO_DATA_FOUND THEN NULL; WHEN OTHERS THEN DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; END; BEGIN NULL; EXCEPTION -- should raise WHEN NO_DATA_FOUND THEN NULL; WHEN OTHERS THEN NULL; END; BEGIN NULL; EXCEPTION WHEN NO_DATA_FOUND THEN NULL; WHEN OTHERS THEN BEGIN NULL; EXCEPTION WHEN NO_DATA_FOUND THEN NULL; WHEN OTHERS THEN DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; END; BEGIN NULL; EXCEPTION -- should raise WHEN NO_DATA_FOUND THEN NULL; WHEN OTHERS THEN NULL; END; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r29</WIKIREV><WIKITIMESTAMP>10 Mar 2012 - 20:42:29</WIKITIMESTAMP></RULE><RULE><ID>3801</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Use RETURN as the last statement in a function.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>38</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>A good general rule to follow as you write your PL/SQL programs is: "one way in and one way out". In other words, there should be just one way to enter or call a program, and there should be one way out, one exit path from a program (or loop) on successful termination. By following this rule, you end up with code that is much easier to trace, debug, and maintain. For a function, this means that you should think of the executable section as a funnel: all the lines of code narrow down to the last executable statement: RETURN <return value>; Note the following: You could, and should, still have RETURN statements in your exception handlers. Not every exception should be passed un-handled from your function. It is possible (i.e., acceptable syntax) to use an "unqualified" RETURN statement in a procedure, as follows: <pre>IF all_done THEN RETURN; END IF;</pre> and the procedure will immediately terminate and return control. You should not do this, however, as it results in very unstructured code that is hard to debug and maintain. This same recommendation holds for the initialization section of a package. Here is a very simple function that relies on multiple RETURNs: <pre>CREATE OR REPLACE FUNCTION status_desc ( cd_in IN VARCHAR2 ) RETURN VARCHAR2 IS BEGIN IF cd_in = 'C' THEN RETURN 'CLOSED'; ELSIF cd_in = 'O' THEN RETURN 'OPEN'; ELSIF cd_in = 'I' THEN RETURN 'INACTIVE'; END IF; END;</pre> At first glance, this function looks fine. Yet this function has a deep flaw, due to the reliance on separate RETURNs: if you do not pass in C, O, or I for the cd_in argument, the function raises: ORA-06503: PL/SQL: Function returned without value Here is a rewrite that relies on (a) a standard types package to avoid hard-coding a VARCHAR2 variable length and also gives names to literal values, and (b) a single RETURN at the end of the function: <pre>CREATE OR REPLACE FUNCTION status_desc ( cd_in IN VARCHAR2 ) RETURN stdtypes.description_t IS retval stdtypes.description_t; BEGIN IF cd_in = stdtypes.c_closed_abbrev THEN retval := stdtypes.c_closed; ELSIF cd_in = stdtypes.c_open_abbrev THEN retval := stdtypes.c_open; ELSIF cd_in = stdtypes.c_inactive_abbrev ' THEN retval := stdtypes.c_inactive; END IF; RETURN retval; END;</pre> This program also safely and correctly returns NULL if the program does not receive a value of 'C', 'O', or 'I', unlike the first implementation. A single RETURN function is easier to trace and debug, since you don't have to worry about multiple exit pathways from the function.</TIP_TEXT><EXAMPLE_CODE>PROCEDURE p IS FUNCTION f RETURN NUMBER IS BEGIN IF a -- should raise THEN RETURN 2; END IF; END; BEGIN IF a THEN RETURN 2; END IF; RETURN 1; END; FUNCTION f RETURN NUMBER IS PROCEDURE p IS FUNCTION f RETURN NUMBER IS BEGIN IF a -- should raise THEN RETURN 2; END IF; END; BEGIN IF a THEN RETURN; END IF; END; BEGIN IF a -- should raise THEN RETURN 2; END IF; END; CREATE OR REPLACE FUNCTION status_desc (cd_in IN VARCHAR2) RETURN VARCHAR2 IS -- Version of lookup function that relies on multiple RETURNs. BEGIN IF cd_ = 'C' -- should raise THEN RETURN 'a'; ELSIF cd_in = 'O' THEN NULL; ELSIF cd_in = 'A' THEN RAISE NO_DATA_FOUND; ELSIF cd_in = 'I' THEN RETURN x + 3 * y + f (z); END IF; EXCEPTION WHEN NO_DATA_FOUND THEN x := 1; RETURN x; WHEN OTHER_ERROR THEN x := y1 + y2 + 2; -- should raise WHEN SOME_OTHER_ERROR THEN RAISE THIRD_EXCEPTION; -- should raise WHEN EX2 THEN proc; -- should raise WHEN OTHERS THEN NULL; -- should raise END; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R3801</TIP_LINK><WIKIREV>r16</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:37:51</WIKITIMESTAMP></RULE><RULE><ID>3802</ID></RULE><RULE><ID>3803</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid using more than one RETURN statement in a function's executable section.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>38</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //RETURN [count(*) > 0] [not(ancestor::EXCEPTION) ] [ancestor::FUNCTION_BODY[1]/BLOCK[count(descendant::RETURN [count(*) > 0] [not(ancestor::EXCEPTION) ]) > 1] ] </XPATH_QUERY><TIP_TEXT>Excessive use of returns can make code confusing and unreadable. Only use return statements where warranted. Too many returns from a subprogram may be an indicator of cluttered logic. If the application requires multiple returns, use them at the same level (that is, as in different branches of a case statement), rather than scattered throughout the subprogram code. Some rework can often reduce the number of return statements to one and make the code more clear. A good general rule to follow as you write your PL/SQL programs is: "one way in and one way out". In other words, there should be just one way to enter or call a program, and there should be one way out, one exit path from a program (or loop) on successful termination. By following this rule, you end up with code that is much easier to trace, debug, and maintain. For a function, this means that you should think of the executable section as a funnel: all the lines of code narrow down to the last executable statement: <pre>RETURN <return value>;</pre> Note the following: You could, and should, still have RETURN statements in your exception handlers. Not every exception should be passed un-handled from your function. It is possible (i.e., acceptable syntax) to use an "unqualified" RETURN statement in a procedure, as follows: <pre>IF all_done THEN RETURN; END IF;</pre> and the procedure will immediately terminate and return control. You should not do this, however, as it results in very unstructured code that is hard to debug and maintain. This same recommendation holds for the initialization section of a package. Here is a very simple function that relies on multiple RETURNs: <pre>CREATE OR REPLACE FUNCTION status_desc ( cd_in IN VARCHAR2 ) RETURN VARCHAR2 IS BEGIN IF cd_in = 'C' THEN RETURN 'CLOSED'; ELSIF cd_in = 'O' THEN RETURN 'OPEN'; ELSIF cd_in = 'I' THEN RETURN 'INACTIVE'; END IF; END;</pre> At first glance, this function looks fine. Yet this function has a deep flaw, due to the reliance on separate RETURNs: if you do not pass in C, O, or I for the cd_in argument, the function raises: ORA-06503: PL/SQL: Function returned without value Here is a rewrite that relies on (a) a standard types package to avoid hard-coding a VARCHAR2 variable length and also gives names to literal values, and (b) a single RETURN at the end of the function: <pre>CREATE OR REPLACE FUNCTION status_desc ( cd_in IN VARCHAR2 ) RETURN stdtypes.description_t IS retval stdtypes.description_t; BEGIN IF cd_in = stdtypes.c_closed_abbrev THEN retval := stdtypes.c_closed; ELSIF cd_in = stdtypes.c_open_abbrev THEN retval := stdtypes.c_open; ELSIF cd_in = stdtypes.c_inactive_abbrev ' THEN retval := stdtypes.c_inactive; END IF; RETURN retval; END;</pre> This program also safely and correctly returns NULL if the program does not receive a value of 'C', 'O', or 'I', unlike the first implementation. A single RETURN function is easier to trace and debug, since you don't have to worry about multiple exit pathways from the function.</TIP_TEXT><EXAMPLE_CODE>PROCEDURE p1a IS FUNCTION f RETURN NUMBER IS BEGIN IF a THEN RETURN 2; -- should raise END IF; RETURN 1; -- should raise END; BEGIN IF a THEN RETURN; END IF; RETURN; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R3803</TIP_LINK><WIKIREV>r33</WIKIREV><WIKITIMESTAMP>07 Nov 2011 - 20:45:11</WIKITIMESTAMP></RULE><RULE><ID>3804</ID></RULE><RULE><ID>3805</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Confirm that function does not need parameters.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>38</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //FUNCTION_BODY[not(PARAM_SPEC_LIST)]/QNAME </XPATH_QUERY><TIP_TEXT>While this is not necessarily a problem, a function without any parameters is more likely to depend on global variables for data or be insufficiently generic to be of use in multiple circumstances. Use package globals judiciously and only in package bodies. A global variable is a data structure that can be referenced outside of the scope or block in which it is declared. In the following block, for example, the l_publish_date is global to the local display_book_info procedure: <pre>DECLARE l_publish_date DATE; ... PROCEDURE display_book_info IS BEGIN DBMS_OUTPUT.PUT_LINE (l_publish_date); END;</pre> Globals are dangerous and to be avoided as they create hidden "dependencies" or side effects. A global does not have to be passed through the parameter list, so it is hard to even know that a global is referenced in a program without looking at the implementation. Globals are most often defined in packages. If a variable is declared at the package level (not within any specific program), that variable exists and retains its value for the duration of the session. The general solution to this problem is to pass the global as a parameter in the procedure and function; do not reference it directly within the program. Another general technique to keep in mind is to declare variables, cursors, functions, and other objects as deep as possible (i.e., in the block nearest to where, or within which, that object will be used), in order to reduce the chance of unintended use by other sections of the code. Reliance on global data structures can be a particularly acute problem in Oracle Developer's Formsbuilder (previously known as Oracle Forms). Developers have historically relied on :GLOBAL data structures to pass information between forms. In the latest versions of Oracle Developer, avoid :GLOBAL variables. Instead, build and share packages (and variables declared within those packages) among forms. Here is an example of a function with a hidden dependency on a global variable: <pre>CREATE OR REPLACE FUNCTION overdue_fine ( isbn_in IN book.isbn%TYPE) RETURN NUMBER IS l_days_overdue NUMBER; BEGIN l_days_overdue := overdue_pkg.days_overdue (isbn_in, SYSDATE); RETURN (l_days_overdue * overdue_pkg.g_daily_fine); END;</pre> The global is the amount of the daily fine. It is buried inside the function. By writing the function this way, two things happen: (a) there is no flexibility to pass in a different daily fine amount, as may be required, and (b) if the daily fine has not been set within the overdue package, the function doesn't work properly. The dependency can be eliminated by adding a parameter: <pre>CREATE OR REPLACE FUNCTION overdue_fine ( isbn_in IN book.isbn%TYPE, daily_fine_in IN NUMBER) RETURN NUMBER IS l_days_overdue NUMBER; BEGIN l_days_overdue := overdue_pkg.days_overdue (isbn_in, SYSDATE); RETURN (l_days_overdue * daily_fine_in); END;</pre> By reducing the inter-dependencies between programs, it is easier and safer make a change to one without worrying about all the others being affected. Of course, existing programs may need to be revamped to pull out global references and replace them with either parameters or calls to "get and set" programs that encapsulate the global data.</TIP_TEXT><EXAMPLE_CODE>CREATE OR REPLACE FUNCTION z -- should raise RETURN NUMBER IS BEGIN RETURN 3; END z; / CREATE OR REPLACE FUNCTION z (a IN NUMBER) RETURN NUMBER IS BEGIN RETURN a + 3; END z; / PACKAGE BODY pb IS FUNCTION f (a IN NUMBER) RETURN NUMBER IS FUNCTION f2 -- should raise RETURN NUMBER IS BEGIN NULL; END; BEGIN NULL; END; FUNCTION f2 -- should raise RETURN NUMBER IS FUNCTION f2 -- should raise RETURN NUMBER IS BEGIN NULL; END; FUNCTION f (a IN NUMBER) RETURN NUMBER IS FUNCTION f2 -- should raise RETURN NUMBER IS BEGIN NULL; END; BEGIN NULL; END; BEGIN NULL; END; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R3805</TIP_LINK><WIKIREV>r25</WIKIREV><WIKITIMESTAMP>09 Mar 2012 - 17:56:59</WIKITIMESTAMP></RULE><RULE><ID>3806</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid coding functions with OUT parameters.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>38</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //FUNCTION_BODY/PARAM_SPEC_LIST/PARAM_SPEC[OUT][not(IN, INOUT)] </XPATH_QUERY><TIP_TEXT>A function should return all of its data through its RETURN clause. If data is also passed through the parameter list as OUT or IN OUT parameters, then (a) it is likely to be less flexibly used under different circumstances and (b) you will not be able to call that function in an SQL statement. The whole point of a function is to return a value (whether it is a single, scalar value or a composite, such as a record or a collection). If you also return data back through the parameter list with OUT or IN OUT arguments, the purpose and usage of the function is obscured. Oracle also places restrictions on how you can use functions that have OUT and IN OUT parameters -- namely, you cannot call that function from within a SQL statement. If you need to return multiple pieces of information, take one of the following approaches: Return a record or collection of values. Make sure to publish the structure of your record or collection (the TYPE statement) in a package specification so that developers can understand and use the function more easily. Note that you will not be able to call this function in an SQL statement if it returns a record or index-by table. Break up the single function into multiple functions, all returning scalar values. By taking this approach, you will be able to call the functions from within SQL statements. Change your function into a procedure. Unless you really need to be able to call a function to return this information, just change it to a procedure returning multiple pieces of information. As an example, here is a function that returns several pieces of information about a book: <pre>FUNCTION book_info ( isbn_in IN book.isbn%TYPE, author_out OUT book.author%TYPE, page_count_out OUT book.page_count%TYPE) RETURN book.title%TYPE; And now I will use this function: l_title book.title%TYPE; BEGIN l_title := book_info (l_isbn, l_author, l_page_count); </pre> This function is very confusing. It seems to return a title, but what else does it do? It is hard to tell what is happening with the other parameters. If, on the other hand, the function is restructured to return a record: <pre>FUNCTION book_info ( isbn_in IN book.isbn%TYPE) RETURN book%ROWTYPE;</pre> the intent of the resulting code is much more clear: <pre> one_book_rec book%ROWTYPE; BEGIN one_book_rec := book_info (l_isbn); </pre> Benefits: The functions are more likely to be used and reused, because they are defined in ways that make them easy to understand and apply in one's own code. The function may then be callable from within a SQL statement, which encourages even wider use of this program. A function with an OUT argument can never be called from within SQL, Please note, though, that there are other restrictions on function calls from SQL. You may not, for example, call a function that returns a record (as shown in the above example). One challenge that this practice presents is that you many need to pass back status information as well as the data returned by the function. This comes up when calling PL/SQL code from non-Oracle languages like Visual Basic. In this case, consider using a procedure instead of a function.</TIP_TEXT><EXAMPLE_CODE>CREATE OR REPLACE FUNCTION z (a IN NUMBER, b IN OUT NUMBER, c OUT NUMBER) -- should raise RETURN NUMBER IS BEGIN f (a, b, c); b := 22; c := 37; RETURN 3; END z; / PACKAGE BODY pb IS FUNCTION f (a IN NUMBER) RETURN NUMBER IS FUNCTION f2 (b OUT NUMBER) -- should raise RETURN NUMBER IS BEGIN NULL; END; BEGIN NULL; END; FUNCTION f2 RETURN NUMBER IS FUNCTION f2 (c IN NUMBER, d OUT NUMBER, -- should raise e IN NUMBER) RETURN NUMBER IS BEGIN NULL; END; FUNCTION f (a IN NUMBER, f OUT NUMBER, -- should raise g OUT VARCHAR2 (200)) -- should raise RETURN NUMBER IS FUNCTION f2 ( g OUT NUMBER) -- should raise RETURN NUMBER IS BEGIN NULL; END; BEGIN NULL; END; BEGIN NULL; END; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R3806</TIP_LINK><WIKIREV>r24</WIKIREV><WIKITIMESTAMP>09 Mar 2012 - 18:40:15</WIKITIMESTAMP></RULE><RULE><ID>4001</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid using GOTO in a loop.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>40</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//LOOP//GOTO</XPATH_QUERY><TIP_TEXT>The GOTO statement should generally be avoided, as it leads to unstructured code design that is hard to analyze and debug. There are scenarios in which a GOTO can be justified; these mostly relate to going into existing overly-complex code to fix a bug or enhance the code. Here is a use of GOTO that calls attention to itself: <pre>CREATE OR REPLACE PROCEDURE someone_elses_mess /* || Author: Longgone Consultant || Maintained by: Sad Employee || || Modification History || When Who What || -------------------------------------------- || 11/2000 Sad E. Fixed bug in overdue logic. || Used GOTO to bypass Gordian || Knot of code left by L.C. */ IS BEGIN IF ... THEN IF ... THEN FOR rec IN cur LOOP -- 11/2000 Bypass with GOTO GOTO &lt;&lt;quick_exit&gt;&gt; END LOOP; ... lots more code END IF; -- 11/2000 GOTO Target &lt;&lt;quick_exit&gt;&gt; END IF;</pre> Even if you can, at times, justify the use of a GOTO, you can almost always achieve the same effect with a more structured and more easily understood use of conditional and loop logic.</TIP_TEXT><EXAMPLE_CODE>BEGIN WHILE i < 10 LOOP i := i + 1; GOTO lp2; -- should raise proc; <<lp2>> proc2; END LOOP; END; / BEGIN LOOP GOTO z; -- should raise IF cond THEN GOTO z; -- should raise END IF; <<z>> NULL; END LOOP; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R4001</TIP_LINK><WIKIREV>r22</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:38:12</WIKITIMESTAMP></RULE><RULE><ID>4002</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Avoid using a backward GOTO.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>40</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>A forward GOTO may be allowable as it is equivalent to an IF/ELSE, but a backward GOTO disguises a real LOOP and is, as such, a very bad programming practice.</TIP_TEXT><EXAMPLE_CODE>PROCEDURE p IS z NUMBER; BEGIN <<lab>> BEGIN z := 1; GOTO lab; -- should raise a; <<lab2>> b; END; END; PROCEDURE p IS z NUMBER; BEGIN BEGIN z := 1; GOTO lab; a; <<lab>> b; END; <<lab>> c; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R4002</TIP_LINK><WIKIREV>r17</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:38:15</WIKITIMESTAMP></RULE><RULE><ID>4003</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid use of GOTO statements.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>40</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//GOTO</XPATH_QUERY><TIP_TEXT>A GOTO statement is an unstructured change in the control flow. Worse, the label does not require an indicator of where the corresponding GOTO statement(s) are. This makes code unreadable and makes its correct execution suspect. Other languages use GOTO statements to implement loop exits and exception handling. PL/SQL's support of these constructs makes the GOTO statement extremely rare. There are scenarios in which a GOTO can be justified; these mostly relate to going into existing overly-complex code to fix a bug or enhance the code. Here is a use of GOTO that calls attention to itself: <pre>CREATE OR REPLACE PROCEDURE someone_elses_mess /* || Author: Longgone Consultant || Maintained by: Sad Employee || || Modification History || When Who What || -------------------------------------------- || 11/2000 Sad E. Fixed bug in overdue logic. || Used GOTO to bypass Gordian || Knot of code left by L.C. */ IS BEGIN IF ... THEN IF ... THEN FOR rec IN cur LOOP -- 11/2000 Bypass with GOTO GOTO &lt;&lt;quick_exit&gt;&gt; END LOOP; ... lots more code END IF; -- 11/2000 GOTO Target &lt;&lt;quick_exit&gt;&gt; END IF; </pre> Even if you can, at times, justify the use of a GOTO, you can almost always achieve the same effect with a more structured and more easily understood use of conditional and loop logic.</TIP_TEXT><EXAMPLE_CODE>BEGIN LOOP GOTO z; -- should raise IF cond THEN GOTO z; -- should raise END IF; <<z>> NULL; END LOOP; GOTO a; -- should raise END; BEGIN <<z>> LOOP GOTO z; -- should raise --GOTO z; IF cond THEN GOTO z; -- should raise END IF; END LOOP; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R4003</TIP_LINK><WIKIREV>r23</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:38:18</WIKITIMESTAMP></RULE><RULE><ID>4201</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Limit a CASE statement or expression to 128 choices.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>42</CAT><SEVERITY>2</SEVERITY><XPATH_QUERY> //EXPR[@cat="case"]//CASE_WHEN[129]/TOKEN[1], //CASE_STMT//CASE_STMT_WHEN[129]/TOKEN[1] </XPATH_QUERY><TIP_TEXT>Although not enforced by Oracle it is a good idea to limit the number of choices to avoid performance penalties.</TIP_TEXT><EXAMPLE_CODE>DECLARE x INT; y INT := 130; BEGIN ---------------------------------------------------------------- -- case expression should cause error x := CASE y WHEN 1 THEN 1 WHEN 2 THEN 1 WHEN 3 THEN 1 ELSE 2 END; x := CASE y WHEN 1 THEN 1 WHEN 2 THEN 1 WHEN 3 THEN 1 WHEN 4 THEN 1 WHEN 5 THEN 1 WHEN 6 THEN 1 WHEN 7 THEN 1 WHEN 8 THEN 1 WHEN 9 THEN 1 WHEN 10 THEN 1 WHEN 11 THEN 1 WHEN 12 THEN 1 WHEN 13 THEN 1 WHEN 14 THEN 1 WHEN 15 THEN 1 WHEN 16 THEN 1 WHEN 17 THEN 1 WHEN 18 THEN 1 WHEN 19 THEN 1 WHEN 20 THEN 1 WHEN 21 THEN 1 WHEN 22 THEN 1 WHEN 23 THEN 1 WHEN 24 THEN 1 WHEN 25 THEN 1 WHEN 26 THEN 1 WHEN 27 THEN 1 WHEN 28 THEN 1 WHEN 29 THEN 1 WHEN 30 THEN 1 WHEN 31 THEN 1 WHEN 32 THEN 1 WHEN 33 THEN 1 WHEN 34 THEN 1 WHEN 35 THEN 1 WHEN 36 THEN 1 WHEN 37 THEN 1 WHEN 38 THEN 1 WHEN 39 THEN 1 WHEN 40 THEN 1 WHEN 41 THEN 1 WHEN 42 THEN 1 WHEN 43 THEN 1 WHEN 44 THEN 1 WHEN 45 THEN 1 WHEN 46 THEN 1 WHEN 47 THEN 1 WHEN 48 THEN 1 WHEN 49 THEN 1 WHEN 50 THEN 1 WHEN 51 THEN 1 WHEN 52 THEN 1 WHEN 53 THEN 1 WHEN 54 THEN 1 WHEN 55 THEN 1 WHEN 56 THEN 1 WHEN 57 THEN 1 WHEN 58 THEN 1 WHEN 59 THEN 1 WHEN 60 THEN 1 WHEN 61 THEN 1 WHEN 62 THEN 1 WHEN 63 THEN 1 WHEN 64 THEN 1 WHEN 65 THEN 1 WHEN 66 THEN 1 WHEN 67 THEN 1 WHEN 68 THEN 1 WHEN 69 THEN 1 WHEN 70 THEN 1 WHEN 71 THEN 1 WHEN 72 THEN 1 WHEN 73 THEN 1 WHEN 74 THEN 1 WHEN 75 THEN 1 WHEN 76 THEN 1 WHEN 77 THEN 1 WHEN 78 THEN 1 WHEN 79 THEN 1 WHEN 80 THEN 1 WHEN 81 THEN 1 WHEN 82 THEN 1 WHEN 83 THEN 1 WHEN 84 THEN 1 WHEN 85 THEN 1 WHEN 86 THEN 1 WHEN 87 THEN 1 WHEN 88 THEN 1 WHEN 89 THEN 1 WHEN 90 THEN 1 WHEN 91 THEN 1 WHEN 92 THEN 1 WHEN 93 THEN 1 WHEN 94 THEN 1 WHEN 95 THEN 1 WHEN 96 THEN 1 WHEN 97 THEN 1 WHEN 98 THEN 1 WHEN 99 THEN 1 WHEN 100 THEN 1 WHEN 101 THEN 1 WHEN 102 THEN 1 WHEN 103 THEN 1 WHEN 104 THEN 1 WHEN 105 THEN 1 WHEN 106 THEN 1 WHEN 107 THEN 1 WHEN 108 THEN 1 WHEN 109 THEN 1 WHEN 110 THEN 1 WHEN 111 THEN 1 WHEN 112 THEN 1 WHEN 113 THEN 1 WHEN 114 THEN 1 WHEN 115 THEN 1 WHEN 116 THEN 1 WHEN 117 THEN 1 WHEN 118 THEN 1 WHEN 119 THEN 1 WHEN 120 THEN 1 WHEN 121 THEN 1 WHEN 122 THEN 1 WHEN 123 THEN 1 WHEN 124 THEN 1 WHEN 125 THEN 1 WHEN 126 THEN 1 WHEN 127 THEN 1 WHEN 128 THEN 1 WHEN 129 THEN 1 -- should raise WHEN 130 THEN 1 ELSE 2 END; ---------------------------------------------------------------- -- case statement should cause error CASE y WHEN 1 THEN x := 1; WHEN 2 THEN x := 1; WHEN 3 THEN x := 1; ELSE x := 2; END CASE; CASE y WHEN 1 THEN x := 1; WHEN 2 THEN x := 1; WHEN 3 THEN x := 1; WHEN 4 THEN x := 1; WHEN 5 THEN x := 1; WHEN 6 THEN x := 1; WHEN 7 THEN x := 1; WHEN 8 THEN x := 1; WHEN 9 THEN x := 1; WHEN 10 THEN x := 1; WHEN 11 THEN x := 1; WHEN 12 THEN x := 1; WHEN 13 THEN x := 1; WHEN 14 THEN x := 1; WHEN 15 THEN x := 1; WHEN 16 THEN x := 1; WHEN 17 THEN x := 1; WHEN 18 THEN x := 1; WHEN 19 THEN x := 1; WHEN 20 THEN x := 1; WHEN 21 THEN x := 1; WHEN 22 THEN x := 1; WHEN 23 THEN x := 1; WHEN 24 THEN x := 1; WHEN 25 THEN x := 1; WHEN 26 THEN x := 1; WHEN 27 THEN x := 1; WHEN 28 THEN x := 1; WHEN 29 THEN x := 1; WHEN 30 THEN x := 1; WHEN 31 THEN x := 1; WHEN 32 THEN x := 1; WHEN 33 THEN x := 1; WHEN 34 THEN x := 1; WHEN 35 THEN x := 1; WHEN 36 THEN x := 1; WHEN 37 THEN x := 1; WHEN 38 THEN x := 1; WHEN 39 THEN x := 1; WHEN 40 THEN x := 1; WHEN 41 THEN x := 1; WHEN 42 THEN x := 1; WHEN 43 THEN x := 1; WHEN 44 THEN x := 1; WHEN 45 THEN x := 1; WHEN 46 THEN x := 1; WHEN 47 THEN x := 1; WHEN 48 THEN x := 1; WHEN 49 THEN x := 1; WHEN 50 THEN x := 1; WHEN 51 THEN x := 1; WHEN 52 THEN x := 1; WHEN 53 THEN x := 1; WHEN 54 THEN x := 1; WHEN 55 THEN x := 1; WHEN 56 THEN x := 1; WHEN 57 THEN x := 1; WHEN 58 THEN x := 1; WHEN 59 THEN x := 1; WHEN 60 THEN x := 1; WHEN 61 THEN x := 1; WHEN 62 THEN x := 1; WHEN 63 THEN x := 1; WHEN 64 THEN x := 1; WHEN 65 THEN x := 1; WHEN 66 THEN x := 1; WHEN 67 THEN x := 1; WHEN 68 THEN x := 1; WHEN 69 THEN x := 1; WHEN 70 THEN x := 1; WHEN 71 THEN x := 1; WHEN 72 THEN x := 1; WHEN 73 THEN x := 1; WHEN 74 THEN x := 1; WHEN 75 THEN x := 1; WHEN 76 THEN x := 1; WHEN 77 THEN x := 1; WHEN 78 THEN x := 1; WHEN 79 THEN x := 1; WHEN 80 THEN x := 1; WHEN 81 THEN x := 1; WHEN 82 THEN x := 1; WHEN 83 THEN x := 1; WHEN 84 THEN x := 1; WHEN 85 THEN x := 1; WHEN 86 THEN x := 1; WHEN 87 THEN x := 1; WHEN 88 THEN x := 1; WHEN 89 THEN x := 1; WHEN 90 THEN x := 1; WHEN 91 THEN x := 1; WHEN 92 THEN x := 1; WHEN 93 THEN x := 1; WHEN 94 THEN x := 1; WHEN 95 THEN x := 1; WHEN 96 THEN x := 1; WHEN 97 THEN x := 1; WHEN 98 THEN x := 1; WHEN 99 THEN x := 1; WHEN 100 THEN x := 1; WHEN 101 THEN x := 1; WHEN 102 THEN x := 1; WHEN 103 THEN x := 1; WHEN 104 THEN x := 1; WHEN 105 THEN x := 1; WHEN 106 THEN x := 1; WHEN 107 THEN x := 1; WHEN 108 THEN x := 1; WHEN 109 THEN x := 1; WHEN 110 THEN x := 1; WHEN 111 THEN x := 1; WHEN 112 THEN x := 1; WHEN 113 THEN x := 1; WHEN 114 THEN x := 1; WHEN 115 THEN x := 1; WHEN 116 THEN x := 1; WHEN 117 THEN x := 1; WHEN 118 THEN x := 1; WHEN 119 THEN x := 1; WHEN 120 THEN x := 1; WHEN 121 THEN x := 1; WHEN 122 THEN x := 1; WHEN 123 THEN x := 1; WHEN 124 THEN x := 1; WHEN 125 THEN x := 1; WHEN 126 THEN x := 1; WHEN 127 THEN x := 1; WHEN 128 THEN x := 1; WHEN 129 THEN x := 1; -- should raise WHEN 130 THEN x := 1; ELSE x := 2; END CASE; ---------------------------------------------------------------- END; /</EXAMPLE_CODE><DB_VERSION>None.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R4201</TIP_LINK><WIKIREV>r22</WIKIREV><WIKITIMESTAMP>23 Feb 2012 - 21:46:46</WIKITIMESTAMP></RULE><RULE><ID>4204</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Use a Boolean rather than a complex condition.</DEF><RULE_TYPE>3</RULE_TYPE><CAT>42</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //IF [count(ASSIGN) = 1] [count(ELSIF) = 0 and count(ELSE) = 1 and count(ELSE/ASSIGN) = 1] [ASSIGN/LHS/QNAME/@value = ELSE/ASSIGN/LHS/QNAME/@value] [ASSIGN/RHS/LITERAL] [not(ASSIGN/RHS/LITERAL/@type!="boolean")] [ELSE/ASSIGN/RHS/LITERAL] [not(ELSE/ASSIGN/RHS/LITERAL/@type!="boolean")] [(ASSIGN/RHS/LITERAL/@value="false" and ELSE/ASSIGN/RHS/LITERAL/@value="true") or (ASSIGN/RHS/LITERAL/@value="true" and ELSE/ASSIGN/RHS/LITERAL/@value="false")] /TOKEN[1] </XPATH_QUERY><TIP_TEXT>The expression... <pre>IF a > 10 THEN x := true; ELSE x := false; END IF;</pre> ...should be written as: <pre>x := (a > 10);</pre> At first, you may feel that this code is less clear in its intention, but as you become more comfortable with Boolean variables and syntax, you will fully appreciate the compact clarity of the single assignment.</TIP_TEXT><EXAMPLE_CODE>DECLARE x INT := 1; y INT := 1; z BOOLEAN := FALSE; -- note the init to false a BOOLEAN; -- note no init value at all BEGIN ---------------------------------------------------------------- -- simple expression, toggle one state, no init so assign -- note: should evaluate to true a := FALSE; -- often this is done either just before -- the loop or much before, for example -- maybe right after the current scope's -- begin block (i.e. init's all at top) IF (x >= y) THEN -- should raise a := TRUE; END IF; -- should instead be a := (x >= y); -- this also elimates the need for the assign -- as long as not referenced anywhere else -- simple expression, toggle one state, init so no assign -- note: should evaluate to true IF (x >= y) THEN -- should raise z := TRUE; END IF; -- should instead be z := (x >= y); -- this also elimates the need for the init -- as long as not referenced anywhere else ---------------------------------------------------------------- -- simple expression, toggle two states, no init so assign -- note: should evaluate to true a := FALSE; -- often this is done either just before -- the loop or much before, for example -- maybe right after the current scope's -- begin block (i.e. init's all at top) IF (x >= y) THEN -- should raise a := TRUE; ELSE a := FALSE; -- note this is superfluous since assign -- but when there is distance between the -- assign and conditional control construct -- it's not uncommon to see this overkill END IF; -- should instead be a := (x >= y); -- this also elimates the need for the assign -- as long as not referenced anywhere else -- simple expression, toggle two states, init so no assign -- note: should evaluate to true IF (x >= y) THEN -- should raise z := TRUE; ELSE z := FALSE; -- note this is superfluous since init -- but when there is distance between the -- declare and conditional control construct -- it's not uncommon to see this overkill END IF; -- should instead be z := (x >= y); -- this also elimates the need for the init -- as long as not referenced anywhere else ---------------------------------------------------------------- END; /</EXAMPLE_CODE><DB_VERSION>Not applicable.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R4204</TIP_LINK><WIKIREV>r29</WIKIREV><WIKITIMESTAMP>23 Feb 2012 - 21:48:13</WIKITIMESTAMP></RULE><RULE><ID>4207</ID><STATUS>5</STATUS><COLOR>Yellow</COLOR><DEF>Consider resequencing conditional logic for better performance.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>Consider resequencing conditional logic for better performance.</TIP_TEXT><EXAMPLE_CODE>-- Note: basic idea is that a function call (whether a user function or builtin) -- is more expensive to call at runtime than a simple numeric expression, -- so where possible it's best to resequence or reorder the expression to -- yield a logically equivalent, but better performing run-time expression DECLARE x INT := 10; y INT := 1; result BOOLEAN; BEGIN -- it can happen in an assignment result := ( (MOD (x, y) > 1) AND (x != y)); -- should raise -- is better written as result := ( (x != y) AND (MOD (x, y) > 1)); -- it can happen in an IF THEN ELSE IF ( (MOD (x, y) > 1) AND (x != y)) THEN -- should raise result := TRUE; ELSE result := FALSE; END IF; -- is better written as IF ( (x != y) AND (MOD (x, y) > 1)) THEN result := TRUE; ELSE result := FALSE; END IF; -- it can be a complex expression of equal levels result := ( (MOD (x, y) > 1) AND (x != y) AND (x > 1) AND (Y > 1)); -- should raise -- is better written as result := ( (x != y) AND (x > 1) AND (Y > 1) AND (MOD (x, y) > 1)); -- it can be a complex expression of equal levels with varying costs -- note: a two function subexpression is more costly than a one function subexpression result := ( (MOD (x, y) != MOD (y, x)) AND (MOD (x, y) > 1) AND (x != y) AND (x > 1) AND (Y > 1)); -- should raise -- is better written as result := ( (x != y) AND (x > 1) AND (Y > 1) AND (MOD (x, y) > 1) AND (MOD (x, y) != MOD (y, x))); -- it can be a complex expression with varying levels and varying costs (worst case) -- note: simply added additional levels of parenthesis to complicate expression evaluation result := ( ( MOD (x, y) / MOD (y, x) > 0 AND MOD (y, x) / MOD (x, y) > 0) AND -- should raise ( (MOD (x, y) != MOD (y, x)) AND (MOD (x, y) > 1)) AND ( (x != y) AND (x > 1) AND (Y > 1))); -- is better written as result := ( ( (x != y) AND (x > 1) AND (Y > 1)) AND ( (MOD (x, y) != MOD (y, x)) AND (MOD (x, y) > 1)) AND ( MOD (x, y) / MOD (y, x) > 0 AND MOD (y, x) / MOD (x, y) > 0)); END; /</EXAMPLE_CODE><DB_VERSION>Not applicable.</DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r19</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 01:45:11</WIKITIMESTAMP></RULE><RULE><ID>4210</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Use the implicit value of boolean datatypes (True/False) in conditional logic.</DEF><RULE_TYPE>3</RULE_TYPE><CAT>42</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//EXPR[@cat="eq" or @cat="ne"] [count(LITERAL[@type="boolean"])=1]</XPATH_QUERY><TIP_TEXT>Your IF statement uses an explicit, hard-coded test for TRUE/FALSE. While there is nothing technically incorrect with this method, a better practice is to use an implicit Boolean reference which makes the code easier to read. For example, the expression <pre>IF a = FALSE THEN ... END IF;</pre> could be written as <pre>IF NOT a THEN ... END IF;</pre> and the expression <pre>IF a = TRUE THEN ... END IF;</pre> should be written as <pre>IF a THEN ... END IF;</pre></TIP_TEXT><EXAMPLE_CODE>DECLARE x BOOLEAN := TRUE; y BOOLEAN := FALSE; result INT; BEGIN ---------------------------------------------------------------- -- different tests for true cases IF (x = TRUE) THEN -- should raise result := 1; END IF; IF (x != FALSE) THEN -- should raise result := 1; END IF; IF NOT (x = FALSE) THEN -- should raise result := 1; END IF; -- are all better written as IF (x) THEN result := 1; END IF; ---------------------------------------------------------------- -- different tests for false cases IF (x = FALSE) THEN -- should raise result := 1; END IF; IF (x != TRUE) THEN -- should raise result := 1; END IF; IF NOT (x != FALSE) THEN -- should raise result := 1; END IF; -- are all better written as IF (NOT x) THEN result := 1; END IF; ---------------------------------------------------------------- -- this is just an example to show multiple issues same line IF (x = TRUE AND y = FALSE) THEN -- should raise twice result := 2; END IF; -- is better written as IF (x AND NOT y) THEN result := 2; END IF; ---------------------------------------------------------------- END; /</EXAMPLE_CODE><DB_VERSION>Not applicable.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R4210</TIP_LINK><WIKIREV>r21</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:38:36</WIKITIMESTAMP></RULE><RULE><ID>4213</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Use CASE statement rather than a complex conditional IF statement.</DEF><RULE_TYPE>3</RULE_TYPE><CAT>42</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//IF[count(ELSIF) > 0]/TOKEN[1]</XPATH_QUERY><TIP_TEXT>For a long time the only way to code complex conditional control constructs within PL/SQL code was via the IF THEN ELSIF ELSE syntax. With the introduction of CASE expression and statement syntaxes, such complex conditional constructs are quite often unnecessary but this technique is not yet widely known. It's always much easier to read CASE expressions and statement syntax rather than the corresponding complex conditional constructs, especially for more complex scenarios such as deeply nested levels of conditional logic. And for some cases, utilizing CASE expression and statement syntax can be handled more efficiently by the PL/SQL execution engine than calling complex IF THEN ELSIF ELSE constructs.</TIP_TEXT><EXAMPLE_CODE>BEGIN IF (x = 1) -- should raise THEN result := 10; ELSIF (y = 2) THEN result := 20; ELSE result := 30; END IF; IF ( (x = 1) AND (y = 2)) -- should raise THEN result := x + 10; ELSIF ( (x = 3) AND (y = 4)) THEN result := x + 20; ELSE result := x + 30; END IF; IF (x = 1) -- should raise THEN result := 10; ELSIF (y = 2) THEN result := 20; ELSIF (y = 3) THEN result := 25; ELSE result := 30; END IF; IF ( (x = 1) AND (y = 2)) -- should raise THEN result := x + 10; ELSIF ( (x = 3) AND (y = 4)) THEN result := x + 20; ELSIF (y = 3) THEN result := 25; ELSE result := x + 30; END IF; IF (x = 1) -- should raise THEN DBMS_OUTPUT.PUT_LINE ('Value = 1'); result := 10; ELSIF (x = 2) THEN BEGIN DBMS_OUTPUT.PUT_LINE ('Value = 2'); result := 20; END; ELSE DBMS_OUTPUT.PUT_LINE ('Value = other'); result := 30; END IF; IF (x = 1) -- should raise THEN result := 10; ELSIF (x = 2) THEN result := 20; ELSE result := 30; END IF; END;</EXAMPLE_CODE><DB_VERSION>CASE expression and statement syntax introduced in 9i.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R4213</TIP_LINK><WIKIREV>r24</WIKIREV><WIKITIMESTAMP>23 Feb 2012 - 21:49:02</WIKITIMESTAMP></RULE><RULE><ID>4401</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Avoid undefined labels.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>44</CAT><SEVERITY>2</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>At least one statement referred to the label, but its definition was not found in the same scope. This could cause an error similar to the following: PLS-00219 label 'string' reference is out of scope Ensure that the label has been defined.</TIP_TEXT><EXAMPLE_CODE>BEGIN GOTO a; -- should raise END; BEGIN > GOTO a; END; BEGIN DECLARE PROCEDURE p IS BEGIN GOTO a; -- should raise proc; END; BEGIN > proc; END; > proc; END; BEGIN GOTO a; -- should raise BEGIN > proc; END; END; BEGIN BEGIN GOTO a; -- should raise END; BEGIN > proc; END; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R4401</TIP_LINK><WIKIREV>r18</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:38:43</WIKITIMESTAMP></RULE><RULE><ID>4402</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid unlabeled nested LOOPs.</DEF><RULE_TYPE>3</RULE_TYPE><CAT>44</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //LOOP[LOOP or parent::LOOP] [not(preceding-sibling::*[1]) or preceding-sibling::*[1]/name() != "LABEL"] /TOKEN[1] </XPATH_QUERY><TIP_TEXT>The choice of a good name for the loop documents its purpose, reducing the need for explanatory comments. If a name for a loop is very difficult to choose, this could indicate a need for more thought about the algorithm. For example, you may need to consider adding an outer or inner loop so that the exit statement does not become ambiguous. While PL/SQL labels (identifiers within double angle brackets, like &lt;&lt;yearly_analysis&gt;&gt;) are most often associated with GOTOs, they can be a big help in improving the readability of code. Use a label directly in front of loops and nested anonymous blocks: To give a name to that portion of code and thereby self-document what it is doing. So that you can repeat that name with the END statement of that block or loop. This recommendation is especially important when you have multiple nestings of loops (and possibly inconsistent indentation), as in the following: <pre>LOOP &lt;body&gt; WHILE &lt;condition&gt; LOOP &lt;while body&gt; END LOOP; END LOOP;</pre> You use labels for a block and two nested loops, and then apply them in the appropriate END statements. You can now easily see which loop and block is ending, regardless of how much the code is indented. <pre>CREATE OR REPLACE PROCEDURE display_book_usage IS BEGIN &lt;&lt;best_seller_review&gt;&gt; DECLARE CURSOR yearly_analysis_cur IS SELECT ...; CURSOR monthly_analysis_cur IS SELECT ...; BEGIN &lt;&lt;yearly_analysis&gt;&gt; FOR book_rec IN yearly_analysis_cur (2000) LOOP &lt;&lt;monthly_analysis&gt;&gt; FOR month_rec IN monthly_analysis_cur ( yearly_analysis_cur%rowcount) LOOP ... lots of month-related code ... END LOOP monthly_analysis; ... lots of year-related code ... END LOOP yearly_analysis; END best_seller_review; END display_book_usage;</pre> If you use labels, it will be much easier to read your code, especially if it contains loops and nested blocks that have long bodies (i.e., the loop starts on page 2 and ends on page 7, with three other loops inside that outer loop).</TIP_TEXT><EXAMPLE_CODE>DECLARE a NUMBER; BEGIN LOOP LOOP -- should raise a := 1; END LOOP; END LOOP; END; / DECLARE a NUMBER; BEGIN LOOP LOOP LOOP a := 1; END LOOP; END LOOP; END LOOP; -- this loop is not nested END; / BEGIN LOOP LOOP -- should raise LOOP -- should raise NULL; END LOOP; END LOOP; END LOOP; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R4402</TIP_LINK><WIKIREV>r24</WIKIREV><WIKITIMESTAMP>24 Jan 2012 - 20:23:01</WIKITIMESTAMP></RULE><RULE><ID>4403</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Avoid unlabeled nested blocks.</DEF><RULE_TYPE>3</RULE_TYPE><CAT>44</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>When there is a nested block structure, it can be difficult to determine which end corresponds to which block. Naming blocks alleviates this confusion. The choice of a good name for the block documents its purpose, reducing the need for explanatory comments. If a name for the block is very difficult to choose, this could indicate a need for more thought about the algorithm. This guideline is also useful if nested blocks are broken over a screen or page boundary. While PL/SQL labels (identifiers within double angle brackets, like &lt;&lt;yearly_analysis&gt;&gt;) are most often associated with GOTOs, they can be a big help in improving the readability of code. Use a label directly in front of loops and nested anonymous blocks: To give a name to that portion of code and thereby self-document what it is doing. So that you can repeat that name with the END statement of that block or loop. This recommendation is especially important when you have multiple nestings of loops (and possibly inconsistent indentation), as in the following: <pre>LOOP &lt;body&gt; WHILE &lt;condition&gt; LOOP &lt;while body&gt; END LOOP; END LOOP;</pre> You use labels for a block and two nested loops, and then apply them in the appropriate END statements. You can now easily see which loop and block is ending, regardless of how much the code is indented. <pre>CREATE OR REPLACE PROCEDURE display_book_usage IS BEGIN &lt;&lt;best_seller_review&gt;&gt; DECLARE CURSOR yearly_analysis_cur IS SELECT ...; CURSOR monthly_analysis_cur IS SELECT ...; BEGIN &lt;&lt;yearly_analysis&gt;&gt; FOR book_rec IN yearly_analysis_cur (2000) LOOP &lt;&lt;monthly_analysis&gt;&gt; FOR month_rec IN monthly_analysis_cur ( yearly_analysis_cur%rowcount) LOOP ... lots of month-related code ... END LOOP monthly_analysis; ... lots of year-related code ... END LOOP yearly_analysis; END best_seller_review; END display_book_usage;</pre> If you use labels, it will be much easier to read your code, especially if it contains loops and nested blocks that have long bodies (i.e., the loop starts on page 2 and ends on page 7, with three other loops inside that outer loop).</TIP_TEXT><EXAMPLE_CODE>BEGIN a := 2; IF (a = 2) THEN BEGIN -- should raise a := 3; BEGIN -- should raise a := 4; END; END; END IF; END; / DECLARE a NUMBER; BEGIN BEGIN -- should raise BEGIN -- should raise a := 1; END; END; END; / DECLARE a NUMBER; BEGIN BEGIN -- should raise BEGIN -- should raise BEGIN -- should raise a := 1; END; END; END; END; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R4403</TIP_LINK><WIKIREV>r22</WIKIREV><WIKITIMESTAMP>19 Dec 2011 - 20:06:05</WIKITIMESTAMP></RULE><RULE><ID>4404</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid unlabeled END of program units, packages or types.</DEF><RULE_TYPE>3</RULE_TYPE><CAT>44</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //(PROCEDURE_BODY, FUNCTION_BODY, PACKAGE_SPEC, PACKAGE_BODY, TYPE_BODY)/. [not(END_LABEL) and not(BLOCK/END_LABEL)] /(., BLOCK)/TOKEN[last()][@value="end"] </XPATH_QUERY><TIP_TEXT>Repeating names on the end of these compound statements ensures consistency throughout the code. In addition, the named end provides a reference for the reader if the unit spans a page or screen boundary or if it contains a nested unit. Every program has an END statement. You can, and should, append the name of the program to the end statement: <pre>CREATE OR REPLACE PACKAGE BODY &lt;pkgname&gt; IS PROCEDURE &lt;procname&gt; (...) IS BEGIN ... END &lt;procname&gt;; PROCEDURE &lt;funcname&gt; (...) IS BEGIN ... END &lt;funcname&gt;; END &lt;pkgname&gt;;</pre> Imagine a package that consists of 243 procedures and functions, stretching to over 5,000 lines. Without END labels, you could easily be confronted with code like this: <pre> END LOOP; END; END;</pre> It would be much better if the code had been written like this: <pre> END LOOP yearly_analysis; END best_seller_review; END book_usage_pkg;</pre> This is good form for stand-alone programs. For packaged procedures and functions with code that goes on for hundreds or thousands of lines, however, named ENDs are crucial to improving the readability of that package.</TIP_TEXT><EXAMPLE_CODE>CREATE OR REPLACE PACKAGE pa IS PROCEDURE a; END; -- should raise / CREATE PROCEDURE z (a IN NUMBER) IS BEGIN f (a); END; -- should raise / CREATE PACKAGE BODY pack2 AS PROCEDURE z (a IN NUMBER) IS BEGIN f (a); END; FUNCTION f1 (aa IN NUMBER) -- should raise RETURN NUMBER AS a NUMBER; BEGIN a := aa; RETURN a; END; -- should raise FUNCTION f2 (aa IN NUMBER) RETURN NUMBER AS a NUMBER; BEGIN a := aa; RETURN a; END f2; END; -- should raise /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R4404</TIP_LINK><WIKIREV>r32</WIKIREV><WIKITIMESTAMP>28 Mar 2012 - 17:38:31</WIKITIMESTAMP></RULE><RULE><ID>4405</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid unlabeled END of labeled blocks.</DEF><RULE_TYPE>3</RULE_TYPE><CAT>44</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //BLOCK[preceding-sibling::*[1]/name() = "LABEL"][not(END_LABEL)]/TOKEN[last()] </XPATH_QUERY><TIP_TEXT>Repeating names on the end of these compound statements ensures consistency throughout the code. In addition, the named end provides a reference for the reader if the unit spans a page or screen boundary or if it contains a nested unit. While PL/SQL labels (identifiers within double angle brackets, like &lt;&lt;yearly_analysis&gt;&gt;) are most often associated with GOTOs, they can be a big help in improving the readability of code. Use a label directly in front of loops and nested anonymous blocks: To give a name to that portion of code and thereby self-document what it is doing. So that you can repeat that name with the END statement of that block or loop. This recommendation is especially important when you have multiple nestings of loops (and possibly inconsistent indentation), as in the following: <pre>LOOP &lt;body&gt; WHILE &lt;condition&gt; LOOP &lt;while body&gt; END LOOP; END LOOP;</pre> You use labels for a block and two nested loops, and then apply them in the appropriate END statements. You can now easily see which loop and block is ending, regardless of how much the code is indented. <pre>CREATE OR REPLACE PROCEDURE display_book_usage IS BEGIN &lt;&lt;best_seller_review&gt;&gt; DECLARE CURSOR yearly_analysis_cur IS SELECT ...; CURSOR monthly_analysis_cur IS SELECT ...; BEGIN &lt;&lt;yearly_analysis&gt;&gt; FOR book_rec IN yearly_analysis_cur (2000) LOOP &lt;&lt;monthly_analysis&gt;&gt; FOR month_rec IN monthly_analysis_cur ( yearly_analysis_cur%rowcount) LOOP ... lots of month-related code ... END LOOP monthly_analysis; ... lots of year-related code ... END LOOP yearly_analysis; END best_seller_review; END display_book_usage;</pre> If you use labels, it will be much easier to read your code, especially if it contains loops and nested blocks that have long bodies (i.e., the loop starts on page 2 and ends on page 7, with three other loops inside that outer loop).</TIP_TEXT><EXAMPLE_CODE><<the_block>> BEGIN f; END; -- should raise /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R4405</TIP_LINK><WIKIREV>r27</WIKIREV><WIKITIMESTAMP>04 Jan 2012 - 18:30:59</WIKITIMESTAMP></RULE><RULE><ID>4406</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid unlabeled END of labeled loops.</DEF><RULE_TYPE>3</RULE_TYPE><CAT>44</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//LOOP[preceding-sibling::*[1]/name() = "LABEL"][not(END_LABEL)]/TOKEN[last()]</XPATH_QUERY><TIP_TEXT>Repeating names on the end of these compound statements ensures consistency throughout the code. In addition, the named end provides a reference for the reader if the unit spans a page or screen boundary or if it contains a nested unit. While PL/SQL labels (identifiers within double angle brackets, like &lt;&lt;yearly_analysis&gt;&gt;) are most often associated with GOTOs, they can be a big help in improving the readability of code. Use a label directly in front of loops and nested anonymous blocks: To give a name to that portion of code and thereby self-document what it is doing. So that you can repeat that name with the END statement of that block or loop. This recommendation is especially important when you have multiple nestings of loops (and possibly inconsistent indentation), as in the following: <pre>LOOP &lt;body&gt; WHILE &lt;condition&gt; LOOP &lt;while body&gt; END LOOP; END LOOP;</pre> You use labels for a block and two nested loops, and then apply them in the appropriate END statements. You can now easily see which loop and block is ending, regardless of how much the code is indented. <pre>CREATE OR REPLACE PROCEDURE display_book_usage IS BEGIN &lt;&lt;best_seller_review&gt;&gt; DECLARE CURSOR yearly_analysis_cur IS SELECT ...; CURSOR monthly_analysis_cur IS SELECT ...; BEGIN &lt;&lt;yearly_analysis&gt;&gt; FOR book_rec IN yearly_analysis_cur (2000) LOOP &lt;&lt;monthly_analysis&gt;&gt; FOR month_rec IN monthly_analysis_cur ( yearly_analysis_cur%rowcount) LOOP ... lots of month-related code ... END LOOP monthly_analysis; ... lots of year-related code ... END LOOP yearly_analysis; END best_seller_review; END display_book_usage;</pre> If you use labels, it will be much easier to read your code, especially if it contains loops and nested blocks that have long bodies (i.e., the loop starts on page 2 and ends on page 7, with three other loops inside that outer loop).</TIP_TEXT><EXAMPLE_CODE>BEGIN <<the_lp>> LOOP f; END LOOP; -- should raise END; </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R4406</TIP_LINK><WIKIREV>r25</WIKIREV><WIKITIMESTAMP>04 Jan 2012 - 20:30:32</WIKITIMESTAMP></RULE><RULE><ID>4407</ID></RULE><RULE><ID>4408</ID></RULE><RULE><ID>4409</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Ensure END [LOOP] statement is not missing a semi-colon.</DEF><RULE_TYPE>3</RULE_TYPE><CAT>44</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>Your END or END LOOP statement is followed by code that is being interpreted as a label. Because this code is not on the same line as the END statement, you should validate that it is in fact a label and not a call to a procedure. If the code is not a label, you should add a semicolon after END [LOOP] to terminate the statement. While PL/SQL labels (identifiers within double angle brackets, like &lt;&lt;yearly_analysis&gt;&gt;) are most often associated with GOTOs, they can be a big help in improving the readability of code. Labels can be used directly in front of loops and nested anonymous blocks to: give a name to that portion of code and thereby self-document what it is doing. associate the beginning of a block or loop with its ending. Using labels is especially important when you have multiple nested loops (and possibly inconsistent indentation), as in the following: <pre>LOOP &lt;body&gt; WHILE &lt;condition&gt; LOOP &lt;while body&gt; END LOOP; END LOOP;</pre> In the example below we show how using labels for the block, two nested loops and their associated END statements makes it easy to see which loop and block is ending, regardless of how much the code is indented. <pre>CREATE OR REPLACE PROCEDURE display_book_usage IS BEGIN &lt;&lt;best_seller_review&gt;&gt; DECLARE CURSOR yearly_analysis_cur IS SELECT ...; CURSOR monthly_analysis_cur IS SELECT ...; BEGIN &lt;&lt;yearly_analysis&gt;&gt; FOR book_rec IN yearly_analysis_cur (2000) LOOP &lt;&lt;monthly_analysis&gt;&gt; FOR month_rec IN monthly_analysis_cur ( yearly_analysis_cur%rowcount) LOOP ... lots of month-related code ... END LOOP monthly_analysis; ... lots of year-related code ... END LOOP yearly_analysis; END best_seller_review; END display_book_usage;</pre></TIP_TEXT><EXAMPLE_CODE>BEGIN BEGIN a := 1; END probably_a_label; END; / BEGIN BEGIN a := 1; END perhaps_a_proc_call; -- should raise END; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R4409</TIP_LINK><WIKIREV>r21</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:39:11</WIKITIMESTAMP></RULE><RULE><ID>4501</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Use DBMS_SCHEDULER rather than DBMS_JOB.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY>//QNAME/IDENTIFIER[@value="dbms_job"]</XPATH_QUERY><TIP_TEXT>DBMS_JOB package has been superseded by the DBMS_SCHEDULER package, use DBMS_SCHEDULER rather than DBMS_JOB.</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN DBMS_JOB.abcdef; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r16</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 01:51:25</WIKITIMESTAMP></RULE><RULE><ID>4502</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>DBMS_DDL.ALTER_COMPILE procedure deprecated, use Native Dynamic SQL instead.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="dbms_ddl"] [IDENTIFIER[2]/@value="alter_compile"]</XPATH_QUERY><TIP_TEXT>DBMS_DDL.ALTER_COMPILE procedure deprecated, use Native Dynamic SQL instead.</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN DBMS_DDL.ALTER_COMPILE; -- should raise DBMS_DDL; ALTER_COMPILE; DBMS_DDD.ALTER_COMPILE; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r16</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 01:51:36</WIKITIMESTAMP></RULE><RULE><ID>4503</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Calls to DBMS_ADM require optional OEM packs for DIAGNOSTIC + TUNING.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="dbms_adm"]</XPATH_QUERY><TIP_TEXT>Calls to DBMS_ADM require optional OEM packs for DIAGNOSTIC + TUNING.</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN DBMS_ADM.ALTER_COMPILE; -- should raise DBMS_ADM; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r16</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 01:24:45</WIKITIMESTAMP></RULE><RULE><ID>4504</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Calls to DBMS_ADVISOR require optional OEM packs for DIAGNOSTIC + TUNING.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="dbms_advisor"]</XPATH_QUERY><TIP_TEXT>Calls to DBMS_ADVISOR require optional OEM packs for DIAGNOSTIC + TUNING.</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN DBMS_ADVISOR.ALTER_COMPILE; -- should raise DBMS_ADM; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r16</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 01:25:09</WIKITIMESTAMP></RULE><RULE><ID>4505</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Using one of the DBMS_ADVISOR package's deprecated calls.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="dbms_advisor"] [IDENTIFIER[2]/@value= ("add_sqlwkld_ref", "create_sqlwkld", "delete_sqlwkld", "delete_sqlwkld_ref", "delete_sqlwkld_statement", "import_sqlwkld_schema", "import_sqlwkld_sqlcache", "import_sqlwkld_sts", "import_sqlwkld_sumadv", "import_sqlwkld_user", "reset_sqlwkld", "set_sqlwkld_parameter", "update_sqlwkld_attributes", "update_sqlwkld_statement") ]</XPATH_QUERY><TIP_TEXT>Using one of the DBMS_ADVISOR package's deprecated calls: ADD_SQLWKLD_REF, CREATE_SQLWKLD, DELETE_SQLWKLD, DELETE_SQLWKLD_REF, DELETE_SQLWKLD_STATEMENT, IMPORT_SQLWKLD_SCHEMA, IMPORT_SQLWKLD_SQLCACHE, IMPORT_SQLWKLD_STS, IMPORT_SQLWKLD_SUMADV, IMPORT_SQLWKLD_USER, RESET_SQLWKLD, SET_SQLWKLD_PARAMETER, UPDATE_SQLWKLD_ATTRIBUTES, UPDATE_SQLWKLD_STATEMENT</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN DBMS_ADVISOR.ADD_SQLWKLD_REF; -- should raise DBMS_ADVISOR.CREATE_SQLWKLD; -- should raise DBMS_ADVISOR.DELETE_SQLWKLD; -- should raise DBMS_ADVISOR.DELETE_SQLWKLD_REF; -- should raise DBMS_ADVISOR.DELETE_SQLWKLD_STATEMENT; -- should raise DBMS_ADVISOR.IMPORT_SQLWKLD_SCHEMA; -- should raise DBMS_ADVISOR.IMPORT_SQLWKLD_SQLCACHE; -- should raise DBMS_ADVISOR.IMPORT_SQLWKLD_STS; -- should raise DBMS_ADVISOR.IMPORT_SQLWKLD_SUMADV; -- should raise DBMS_ADVISOR.IMPORT_SQLWKLD_USER; -- should raise DBMS_ADVISOR.RESET_SQLWKLD; -- should raise DBMS_ADVISOR.SET_SQLWKLD_PARAMETER; -- should raise DBMS_ADVISOR.UPDATE_SQLWKLD_ATTRIBUTES; -- should raise DBMS_ADVISOR.UPDATE_SQLWKLD_STATEMENT; -- should raise DBMS_JOB.IMPORT_SQLWKLD_USER; DBMS_ADVISOR.ABCDEFGH; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r17</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 04:34:35</WIKITIMESTAMP></RULE><RULE><ID>4506</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Using one of the DBMS_CDC_PUBLISH package's deprecated calls.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="dbms_cdc_publish"] [IDENTIFIER[2]/@value= ("drop_subscription", "drop_subscriber_view") ]</XPATH_QUERY><TIP_TEXT>Using one of the DBMS_CDC_PUBLISH package's deprecated calls: DROP_SUBSCRIPTION, DROP_SUBSCRIBER_VIEW</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN DBMS_CDC_PUBLISH.DROP_SUBSCRIPTION; -- should raise DBMS_CDC_PUBLISH.DROP_SUBSCRIPTION; -- should raise DBMS_JOB.DROP_SUBSCRIPTION; DBMS_CDC_PUBLISH.ABCDEFGH; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r17</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 04:34:38</WIKITIMESTAMP></RULE><RULE><ID>4507</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Using one of the DBMS_CDC_SUBSCRIBE package's deprecated calls.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="dbms_cdc_subscribe"] [IDENTIFIER[2]/@value= ("drop_subscriber_view", "get_subscription_handle", "prepare_subscriber_view") ]</XPATH_QUERY><TIP_TEXT>Using one of the DBMS_CDC_SUBSCRIBE package's deprecated calls: DROP_SUBSCRIBER_VIEW, GET_SUBSCRIPTION_HANDLE, PREPARE_SUBSCRIBER_VIEW</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN DBMS_CDC_SUBSCRIBE.DROP_SUBSCRIBER_VIEW; -- should raise DBMS_CDC_SUBSCRIBE.GET_SUBSCRIPTION_HANDLE; -- should raise DBMS_CDC_SUBSCRIBE.PREPARE_SUBSCRIBER_VIEW; -- should raise DBMS_JOB.DROP_SUBSCRIBER_VIEW; DBMS_CDC_SUBSCRIBE.ABCDEFGH; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r17</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 04:34:41</WIKITIMESTAMP></RULE><RULE><ID>4508</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Using one of the DBMS_DATA_MINING package's deprecated calls.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="dbms_data_mining"] [IDENTIFIER[2]/@value= ("get_default_settings", "get_model_settings", "get_model_signature", "dm_user_models") ]</XPATH_QUERY><TIP_TEXT>Using one of the DBMS_DATA_MINING package's deprecated calls: GET_DEFAULT_SETTINGS, GET_MODEL_SETTINGS, GET_MODEL_SIGNATURE, DM_USER_MODELS</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN DBMS_DATA_MINING.GET_DEFAULT_SETTINGS; -- should raise DBMS_DATA_MINING.GET_MODEL_SETTINGS; -- should raise DBMS_DATA_MINING.GET_MODEL_SIGNATURE; -- should raise DBMS_DATA_MINING.DM_USER_MODELS; -- should raise DBMS_JOB.GET_MODEL_SETTINGS; DBMS_DATA_MINING.ABCDEFGH; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r18</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 04:34:44</WIKITIMESTAMP></RULE><RULE><ID>4509</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Package DBMS_IOT has been deprecated since Oracle 8.0.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="dbms_iot"]</XPATH_QUERY><TIP_TEXT>Package DBMS_IOT has been deprecated since Oracle 8.0.</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN DBMS_IOT.ABCDEFGH; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r16</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 01:27:04</WIKITIMESTAMP></RULE><RULE><ID>4510</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Using one of the DBMS_MGWADM package's deprecated calls.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="dbms_mgwadm"] [IDENTIFIER[2]/@value= ("add_subscriber", "alter_propagation_schedule", "alter_subscriber", "db_connect_info", "disable_propagation_schedule", "enable_propagation_schedule", "remove_subscriber", "reset_subscriber", "schedule_propagation", "unschedule_propagation") ]</XPATH_QUERY><TIP_TEXT>Using one of the DBMS_MGWADM package's deprecated calls: ADD_SUBSCRIBER, ALTER_PROPAGATION_SCHEDULE, ALTER_SUBSCRIBER, DB_CONNECT_INFO, DISABLE_PROPAGATION_SCHEDULE, ENABLE_PROPAGATION_SCHEDULE, REMOVE_SUBSCRIBER, RESET_SUBSCRIBER, SCHEDULE_PROPAGATION, UNSCHEDULE_PROPAGATION</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN DBMS_MGWADM.ADD_SUBSCRIBER; -- should raise DBMS_MGWADM.ALTER_PROPAGATION_SCHEDULE; -- should raise DBMS_MGWADM.ALTER_SUBSCRIBER; -- should raise DBMS_MGWADM.DB_CONNECT_INFO; -- should raise DBMS_MGWADM.DISABLE_PROPAGATION_SCHEDULE; -- should raise DBMS_MGWADM.ENABLE_PROPAGATION_SCHEDULE; -- should raise DBMS_MGWADM.REMOVE_SUBSCRIBER; -- should raise DBMS_MGWADM.RESET_SUBSCRIBER; -- should raise DBMS_MGWADM.SCHEDULE_PROPAGATION; -- should raise DBMS_MGWADM.UNSCHEDULE_PROPAGATION; -- should raise DBMS_JOB.SCHEDULE_PROPAGATION; DBMS_MGWADM.ABCDEFGH; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r17</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 04:34:48</WIKITIMESTAMP></RULE><RULE><ID>4511</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Package DBMS_OUTLN is being superseded by Plan Management.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="dbms_outln"]</XPATH_QUERY><TIP_TEXT>Package DBMS_OUTLN is being superseded by Plan Management. Stored outlines will be desupported in a future release in favor of SQL plan management. In Oracle Database 11g Release 1 (11.1), stored outlines continue to function as in past releases. However, Oracle strongly recommends that you use SQL plan management for new applications. SQL plan management creates SQL plan baselines, which offer superior SQL performance and stability compared with stored outlines.</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN DBMS_OUTLN.ABCDEFG; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r17</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 04:34:51</WIKITIMESTAMP></RULE><RULE><ID>4512</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Using one of the DBMS_RESOURCE_MANAGER package's deprecated calls.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="dbms_resource_manager"] [IDENTIFIER[2]/@value= "set_initial_consumer_group"]</XPATH_QUERY><TIP_TEXT>Using one of the DBMS_RESOURCE_MANAGER package's deprecated calls: SET_INITIAL_CONSUMER_GROUP</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN DBMS_RESOURCE_MANAGER.SET_INITIAL_CONSUMER_GROUP; -- should raise DBMS_JOB.SET_INITIAL_CONSUMER_GROUP; DBMS_RESOURCE_MANAGER.ABCDEFG; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r18</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 01:28:36</WIKITIMESTAMP></RULE><RULE><ID>4513</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Package DBMS_ROWID should be used sparingly.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="dbms_rowid"]</XPATH_QUERY><TIP_TEXT>Package DBMS_ROWID should be used sparingly.</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN DBMS_ROWID; -- should raise xyz.DBMS_ROWID; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r15</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 15:24:45</WIKITIMESTAMP></RULE><RULE><ID>4514</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Calls to DBMS_SQLPA require optional OEM packs for RAT + TUNING.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="dbms_sqlpa"]</XPATH_QUERY><TIP_TEXT>Calls to DBMS_SQLPA require optional OEM packs for RAT + TUNING.</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN DBMS_SQLPA.ABCDEFGH; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r16</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 15:25:03</WIKITIMESTAMP></RULE><RULE><ID>4515</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Calls to DBMS_SQLTUNE require optional OEM packs for TUNING.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="dbms_sqltune"]</XPATH_QUERY><TIP_TEXT>Calls to DBMS_SQLTUNE require optional OEM packs for TUNING.</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN DBMS_SQLTUNE.ABCDEFGH; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r17</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 15:25:18</WIKITIMESTAMP></RULE><RULE><ID>4516</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Using one of the DBMS_STATS package's deprecated calls.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="dbms_stats"] [IDENTIFIER[2]/@value= ("get_param", "set_param", "reset_param_defaults") ]</XPATH_QUERY><TIP_TEXT>Using one of the DBMS_STATS package's deprecated calls: GET_PARAM, SET_PARAM, RESET_PARAM_DEFAULTS</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN DBMS_STATS.GET_PARAM; -- should raise DBMS_STATS.SET_PARAM; -- should raise DBMS_STATS.RESET_PARAM_DEFAULTS; -- should raise DBMS_JOB.SET_PARAM; DBMS_STATS.ABCDEFGH; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r18</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 04:35:16</WIKITIMESTAMP></RULE><RULE><ID>4517</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Calls to DBMS_WORKLOAD_CAPTURE require optional OEM packs for RAT.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="dbms_workload_capture"]</XPATH_QUERY><TIP_TEXT>Calls to DBMS_WORKLOAD_CAPTURE require optional OEM packs for RAT.</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN DBMS_WORKLOAD_CAPTURE.ABCDEFGH; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r16</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 15:25:45</WIKITIMESTAMP></RULE><RULE><ID>4518</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Calls to DBMS_WORKLOAD_REPLAY require optional OEM packs for RAT.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="dbms_workload_replay"]</XPATH_QUERY><TIP_TEXT>Calls to DBMS_WORKLOAD_REPLAY require optional OEM packs for RAT.</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN DBMS_WORKLOAD_REPLAY.ABCDEFGH; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r16</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 15:26:01</WIKITIMESTAMP></RULE><RULE><ID>4519</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Calls to DBMS_WORKLOAD_REPOSITORY require optional OEM packs for DIAGNOSTIC + TUNING.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="dbms_workload_repository"]</XPATH_QUERY><TIP_TEXT>Calls to DBMS_WORKLOAD_REPOSITORY require optional OEM packs for DIAGNOSTIC + TUNING.</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN DBMS_WORKLOAD_REPOSITORY.ABCDEFGH; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r16</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 15:26:15</WIKITIMESTAMP></RULE><RULE><ID>4520</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Calls to UTL_RAW indicate using older RAW data type rather than BLOB and CLOB.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="utl_raw"]</XPATH_QUERY><TIP_TEXT>Calls to UTL_RAW indicate using older RAW data type rather than BLOB and CLOB.</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN UTL_RAW.ABCDEFGH; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r17</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 15:28:37</WIKITIMESTAMP></RULE><RULE><ID>4521</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Calls to UTL_SMTP pose security risks.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="utl_smtp"]</XPATH_QUERY><TIP_TEXT>Calls to UTL_SMTP pose security risks as this is a powerful SYS owned package.</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN UTL_SMTP.ABCDEFGH; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r16</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 01:30:20</WIKITIMESTAMP></RULE><RULE><ID>4522</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Calls to UTL_TCP pose security risks.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="utl_tcp"]</XPATH_QUERY><TIP_TEXT>Calls to UTL_TCP pose security risks as this is a powerful SYS owned package.</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN UTL_TCP.ABCDEFGH; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r17</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 04:35:49</WIKITIMESTAMP></RULE><RULE><ID>4523</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Calls to UTL_HTTP pose security risks.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="utl_http"]</XPATH_QUERY><TIP_TEXT>Calls to UTL_HTTP pose security risks as this is a powerful SYS owned package.</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN UTL_HTTP.ABCDEFGH; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r17</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 04:35:52</WIKITIMESTAMP></RULE><RULE><ID>4524</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Calls to UTL_FILE pose security risks.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="utl_file"]</XPATH_QUERY><TIP_TEXT>Calls to UTL_FILE pose security risks as this is a powerful SYS owned package.</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN UTL_FILE.ABCDEFGH; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r16</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 01:31:30</WIKITIMESTAMP></RULE><RULE><ID>4525</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Calls to DBMS_RANDOM pose security risks.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="dbms_random"]</XPATH_QUERY><TIP_TEXT>Calls to DBMS_RANDOM pose security risks as this is a powerful SYS owned package.</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN DBMS_RANDOM.ABCDEFGH; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r17</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 04:35:56</WIKITIMESTAMP></RULE><RULE><ID>4526</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Calls to DBMS_LOB pose security risks.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="dbms_lob"]</XPATH_QUERY><TIP_TEXT>Calls to DBMS_LOB pose security risks as this is a powerful SYS owned package.</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN DBMS_LOB.ABCDEFGH; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r16</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 01:32:23</WIKITIMESTAMP></RULE><RULE><ID>4527</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid calls to DBMS_SYS_SQL.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="dbms_sys_sql"]</XPATH_QUERY><TIP_TEXT>Calls to DBMS_SYS_SQL pose security risks as this is a powerful SYS owned package.</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN DBMS_SYS_SQL.ABCDEFGH; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r17</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 01:23:20</WIKITIMESTAMP></RULE><RULE><ID>4528</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Calls to DBMS_BACKUP_RESTORE pose security risks.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="dbms_backup_restore"]</XPATH_QUERY><TIP_TEXT>Calls to DBMS_BACKUP_RESTORE pose security risks as this is a powerful SYS owned package.</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN DBMS_BACKUP_RESTORE.ABCDEFGH; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r17</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 04:36:02</WIKITIMESTAMP></RULE><RULE><ID>4529</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Calls to EMD_SYSTEM pose security risks.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="emd_system"]</XPATH_QUERY><TIP_TEXT>Calls to EMD_SYSTEM pose security risks as this is a powerful SYS owned package.</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN EMD_SYSTEM.ABCDEFGH; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r17</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 04:36:05</WIKITIMESTAMP></RULE><RULE><ID>4530</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Calls to DBMS_NAMESPACE pose security risks.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="dbms_namespace"]</XPATH_QUERY><TIP_TEXT>Calls to DBMS_NAMESPACE pose security risks as this is a powerful SYS owned package.</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN DBMS_NAMESPACE.ABCDEFGH; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r17</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 04:36:08</WIKITIMESTAMP></RULE><RULE><ID>4531</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Calls to DBMS_SCHEDULER pose security risks.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY>//QNAME [IDENTIFIER[1]/@value="dbms_scheduler"]</XPATH_QUERY><TIP_TEXT>Calls to DBMS_SCHEDULER pose security risks as this is a powerful SYS owned package.</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN DBMS_SCHEDULER.ABCDEFGH; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r16</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 01:34:01</WIKITIMESTAMP></RULE><RULE><ID>4532</ID></RULE><RULE><ID>4533</ID></RULE><RULE><ID>4534</ID></RULE><RULE><ID>4535</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>A compound trigger should have at least two timing points.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY>//TRIGGER_BODY [COMPOUND_TRIGGER] [count(TIMED_BLOCK) < 2]/TOKEN[1]</XPATH_QUERY><TIP_TEXT>A compound trigger should have at least two timing points, else a simple trigger will suffice.</TIP_TEXT><EXAMPLE_CODE>CREATE OR REPLACE TRIGGER testtrg -- should raise FOR DELETE OR INSERT OR UPDATE ON cr81142 COMPOUND TRIGGER anyerr EXCEPTION; PRAGMA EXCEPTION_INIT (anyerr, -20005); BEFORE EACH ROW IS BEGIN NULL; RAISE anyerr; EXCEPTION WHEN anyerr THEN NULL; END BEFORE EACH ROW; END; / CREATE OR REPLACE TRIGGER testtrg FOR DELETE OR INSERT OR UPDATE ON cr81142 COMPOUND TRIGGER anyerr EXCEPTION; PRAGMA EXCEPTION_INIT (anyerr, -20005); BEFORE EACH ROW IS BEGIN NULL; RAISE anyerr; EXCEPTION WHEN anyerr THEN NULL; END BEFORE EACH ROW; AFTER STATEMENT IS BEGIN NULL; EXCEPTION WHEN OTHERS THEN NULL; END AFTER STATEMENT; END; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r18</WIKIREV><WIKITIMESTAMP>28 Mar 2012 - 17:40:07</WIKITIMESTAMP></RULE><RULE><ID>4536</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Keep RESULT_CACHE enabled functions to a reasonable minimum.</DEF><RULE_TYPE>2</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY>//RESULT_CACHE</XPATH_QUERY><TIP_TEXT>Keep RESULT_CACHE enabled functions to a reasonable minimum (i.e. use intelligently and sparingly), since every combination of input values can result in a row within the cache.</TIP_TEXT><EXAMPLE_CODE>CREATE OR REPLACE FUNCTION tf_get_writeoff (inrec IN mytype.in_value) RETURN NUMBER RESULT_CACHE -- should raise ORDER inrec BY (customer, employee) IS BEGIN NULL; END; / CREATE OR REPLACE FUNCTION tf_get_writeoff (inrec IN NUMBER) RETURN NUMBER RESULT_CACHE RELIES_ON (a, b, c) -- should raise ORDER inrec BY (customer, employee) IS BEGIN NULL; END; / CREATE OR REPLACE PACKAGE PKG_RC AS FUNCTION FC (emp IN NUMBER) RETURN NUMBER RESULT_CACHE -- should raise RELIES_ON (DUAL); -- PLS-00999: implementation restriction (may be temporary) END; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r16</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 04:36:17</WIKITIMESTAMP></RULE><RULE><ID>4537</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Utilize the CONTINUE statement rather than a GOTO or an extra level of conditional logic within a looping construct.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY>//LOOP//GOTO</XPATH_QUERY><TIP_TEXT>Utilize the CONTINUE statement rather than a GOTO or an extra level of conditional logic within a looping construct.</TIP_TEXT><EXAMPLE_CODE> -- Using GOTO for next iteration - problem -- DECLARE x NUMBER := 0; BEGIN LOOP -- After CONTINUE statement, control resumes here <<XXXXX>> DBMS_OUTPUT.PUT_LINE ('Inside loop: x = ' || TO_CHAR (x)); x := x + 1; IF (x < 3) THEN GOTO XXXXX; -- should raise END IF; DBMS_OUTPUT.PUT_LINE ( 'Inside loop, after CONTINUE: x = ' || TO_CHAR (x)); EXIT WHEN x = 5; END LOOP; DBMS_OUTPUT.PUT_LINE (' After loop: x = ' || TO_CHAR (x)); END; -- Extra level of conditional logic - problem -- DECLARE x NUMBER := 0; BEGIN LOOP -- After CONTINUE statement, control resumes here DBMS_OUTPUT.PUT_LINE ('Inside loop: x = ' || TO_CHAR (x)); x := x + 1; IF (x < 3) THEN CONTINUE; END IF; DBMS_OUTPUT.PUT_LINE ( 'Inside loop, after CONTINUE: x = ' || TO_CHAR (x)); EXIT WHEN x = 5; END LOOP; DBMS_OUTPUT.PUT_LINE (' After loop: x = ' || TO_CHAR (x)); END; -- Correction for both example scenarios -- DECLARE x NUMBER := 0; BEGIN LOOP -- After CONTINUE statement, control resumes here DBMS_OUTPUT.PUT_LINE ('Inside loop: x = ' || TO_CHAR (x)); x := x + 1; CONTINUE WHEN x < 3; DBMS_OUTPUT.PUT_LINE ( 'Inside loop, after CONTINUE: x = ' || TO_CHAR (x)); EXIT WHEN x = 5; END LOOP; DBMS_OUTPUT.PUT_LINE (' After loop: x = ' || TO_CHAR (x)); END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r19</WIKIREV><WIKITIMESTAMP>23 Feb 2012 - 21:53:43</WIKITIMESTAMP></RULE><RULE><ID>4538</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Use LABELS on looping constructs that contain the CONTINUE statement.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY>//LOOP//CONTINUE_LOOP[not(@label)]</XPATH_QUERY><TIP_TEXT>Use LABELS on looping constructs that contain the CONTINUE statement.</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE p IS BEGIN <<lab>> LOOP x := 1; CONTINUE lab; x := 2; END LOOP lab; <<lab>> LOOP x := 1; CONTINUE; -- should raise x := 2; END LOOP lab; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r20</WIKIREV><WIKITIMESTAMP>23 Feb 2012 - 21:54:08</WIKITIMESTAMP></RULE><RULE><ID>4539</ID></RULE><RULE><ID>4540</ID></RULE><RULE><ID>4541</ID></RULE><RULE><ID>4542</ID></RULE><RULE><ID>4543</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid raising application errors.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //CALL/QNAME/IDENTIFIER[@value="RAISE_APPLICATION_ERROR"] </XPATH_QUERY><TIP_TEXT>Declare exception numbers and exceptions in pl/sql package headers rather than raising application errors.</TIP_TEXT><EXAMPLE_CODE>CREATE OR REPLACE TRIGGER no_create AFTER DDL ON SCHEMA BEGIN IF ORA_SYSEVENT = 'CREATE' THEN RAISE_APPLICATION_ERROR (-20000, -- should raise 'Cannot create the ' || ORA_DICT_OBJ_TYPE || ' named ' || ORA_DICT_OBJ_NAME || ' as requested by ' || ORA_DICT_OBJ_OWNER); END IF; END; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r17</WIKIREV><WIKITIMESTAMP>18 Jan 2012 - 09:23:38</WIKITIMESTAMP></RULE><RULE><ID>4601</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Avoid use of new-line or unprintable characters in literal strings.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>46</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>Unprintable characters can have unpredictable consequences in your code, both from the standpoint of compilation and execution. New-line characters in a literal string are often caused by wrapping as your source code is moved from one environment to another. While it is certainly possible and appropriate to have a new-line character in your string, you should visually scan the code to verify its correctness.</TIP_TEXT><EXAMPLE_CODE>BEGIN a2 := 'Literal with a newline'; a3 := 'Literal with unprintable'; -- bell 0x07 -- should raise a7 := 'hello'; a9 := ''; END; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R4601</TIP_LINK><WIKIREV>r23</WIKIREV><WIKITIMESTAMP>23 Sep 2011 - 18:32:04</WIKITIMESTAMP></RULE><RULE><ID>4602</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid use of literals in non-declarative parts of the program.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>46</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//LITERAL [not(@type = "boolean")] [not(ancestor::VAR_DECL or ancestor::CONST_DECL)]</XPATH_QUERY><TIP_TEXT>A hard-coded literal has been used outside of the declarative portion of your program. These hard-coded literals create a "weak link" in your program because it assumes that this value will never change. A better practice is to create a named constant (or function) in a package specification and reference this named element instead of the hard-coded value. You should never place a hard-coded literal, like 'Y' or 150 in your code. Instead, create a package to hold these values and publish a name to be used in place of the literals. You will probably find it best to: Define constants that are referenced throughout your application in a single, central package. Define constants that are more specific to a single area of functionality within the package that encapsulates that functionality. You might also consider storing these special values in a database table, thus keeping them out of your code entirely. Here is a portion of a general constants package: <pre>CREATE OR REPLACE PACKAGE constants IS -- Standard string representation of TRUE/FALSE tval CONSTANT CHAR(1) := 'T'; fval CONSTANT CHAR(1) := 'F'; -- Earliest valid date: 5 years past min_date CONSTANT DATE := ADD_MONTHS (SYSDATE, -5 * 12); </pre> And here is a package that contains constants specific to one area of functionality: <pre>CREATE OR REPLACE PACKAGE nightly_transform IS c_max_weeks CONSTANT INTEGER := 54; c_active CONSTANT CHAR(1) := 'A'; c_inactive CONSTANT CHAR(1) := 'I'; c_english CONSTANT INTEGER := 1; c_usa CONSTANT INTEGER := 1; c_namerica CONSTANT VARCHAR2(2) := 'N'; END nightly_transform; </pre> <b>Benefits</b> You are much less likely to hard-code literal values in your programs, thus improving the readability and maintainability of your code. You have established a place to go when a developer needs to add another constant to hide a literal. <b>Challenges </b> The entire development team needs to know about the packages and use the constants that have been defined for them. Be very careful about the values you assign to your constants; with cut-and-paste, it is very easy to end up assigning a value that is too long and will raise the "ORA-06502: PL/SQL: numeric or value error" at runtime - when the package is initialized.</TIP_TEXT><EXAMPLE_CODE>DECLARE x VARCHAR2 (12); y INT; v_hello_world CONSTANT VARCHAR2 (12) := 'Hello World'; z BOOLEAN := TRUE; TYPE t_associative_array IS TABLE OF VARCHAR2 (12) INDEX BY VARCHAR2 (12); v_associative_array t_associative_array; BEGIN ---------------------------------------------------------------- x := 'Hello World'; -- should raise -- would be better done either using an init clause on -- variable declaration or by assigning it to a constant x := v_hello_world; y := LENGTH ('Hello World'); -- should raise -- would be better written as y := LENGTH (v_hello_world); z := FALSE; ---------------------------------------------------------------- -- note that string can be used to index into arrays v_associative_array ('One') := 'One'; -- should raise v_associative_array ('Hello World') := 'Hello Wolrd'; -- should raise ---------------------------------------------------------------- END; / ---------------------------------------------------------------- -- note that this next example should be treated as OK CREATE OR REPLACE PROCEDURE proc_4602 (x VARCHAR := 'Hello World') AS BEGIN NULL; END; / ---------------------------------------------------------------- -- note that this next example should raise the error CREATE OR REPLACE FUNCTION func_4602 (x INT) RETURN VARCHAR AS BEGIN RETURN ('Hello World'); -- should raise END; /</EXAMPLE_CODE><DB_VERSION>Not applicable.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R4602</TIP_LINK><WIKIREV>r23</WIKIREV><WIKITIMESTAMP>06 Mar 2012 - 13:54:45</WIKITIMESTAMP></RULE><RULE><ID>4603</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid literal concatenations at runtime.</DEF><RULE_TYPE>2</RULE_TYPE><CAT>46</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //EXPR[@cat="concat"] [count(LITERAL[@type="text"]) = 2] / LITERAL[2], //EXPR[@cat="concat"]/LITERAL/EXPR[@cat="concat"] / * [last()][name() = 'LITERAL'] ] </XPATH_QUERY><TIP_TEXT>Two literal strings are being concatenated at runtime. While this is not uncommon, it is more efficient just to combine them in the code than for this to occur at runtime. For example, the concatenation: 'suit' || 'case' is better simply coded as: 'suitcase' Combining string concatenations will also make your code easier to read and maintain. You may also want to switch the Word wrap option on in Toads options (Editor -> Behavior).</TIP_TEXT><EXAMPLE_CODE>BEGIN a := 'aa' || 'bb'; -- should raise a := 'aa' || 'bb' || 'cc'; -- should raise a := 'aa' || v || 'bb' || w || 'cc' || 'dd' || e; -- should raise END; / DECLARE x VARCHAR2 (12); y INT; v_hello_world CONSTANT VARCHAR2 (12) := 'Hello ' || 'World'; -- should raise TYPE t_associative_array IS TABLE OF VARCHAR2 (12) INDEX BY VARCHAR2 (12); v_associative_array t_associative_array; BEGIN ---------------------------------------------------------------- x := 'Hello ' || 'World'; -- should raise -- would be better done either using an init clause on -- variable declaration or by assigning it to a constant x := v_hello_world; y := LENGTH ('Hello ' || 'World'); -- should raise -- would be better written as y := LENGTH (v_hello_world); ---------------------------------------------------------------- -- note that string can be used to index into arrays v_associative_array ('Hello ' || 'World') := 'Hello ' || 'World'; -- should raise twice ---------------------------------------------------------------- END; /</EXAMPLE_CODE><DB_VERSION>None.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R4603</TIP_LINK><WIKIREV>r32</WIKIREV><WIKITIMESTAMP>03 Mar 2012 - 21:44:53</WIKITIMESTAMP></RULE><RULE><ID>4801</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Use EXIT WHEN rather than IF THEN EXIT.</DEF><RULE_TYPE>3</RULE_TYPE><CAT>48</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//IF//EXIT_LOOP</XPATH_QUERY><TIP_TEXT>If the IF clause does not contain any code except for an EXIT statement, then use of the EXIT WHEN <expression> syntax is more intuitive and involves less coding. Whenever possible, you should consolidate all exit logic in your simple loop to a single EXIT (or EXIT WHEN) statement. In general, use the EXIT WHEN statement in place of code like this: <pre>IF <condition> THEN EXIT; END IF;</pre> because it is more intuitive and requires less typing. Here is part of a program that compares two files for equality. After reading the next line from each file, it checks for the following conditions: Did I reach the end of both files? Are the lines different? Did I reach the end of just one file? In each case, the code sets the "return value" for the function and also issues an EXIT statement. <pre>LOOP read_line (file1, line1, file1_eof); read_line (file2, line2, file2_eof); IF (file1_eof AND file2_eof) THEN retval := TRUE; EXIT; ELSIF (line1 != line2) THEN retval := FALSE; EXIT; ELSIF (file1_eof OR file2_eof) THEN retval := FALSE; EXIT; END IF; END LOOP;</pre> I would rewrite this loop body as follows: <pre>LOOP read_line (file1, line1, file1_eof); read_line (file2, line2, file2_eof); IF (file1_eof AND file2_eof) THEN retval := TRUE; exit_loop := TRUE; ELSIF (line1 != line2) THEN retval := FALSE; exit_loop := TRUE; ELSIF (file1_eof OR file2_eof) THEN retval := FALSE; exit_loop := TRUE; END IF; EXIT WHEN exit_loop; END LOOP;</pre> Sometimes it can be difficult to come up with just one EXIT statement. This usually occurs when you need to check a condition at the beginning and end of a loop. If you encounter this situation, consider changing to a WHILE loop. You should also be careful to initialize your return value and your loop terminator variable, to avoid unwanted NULL values that might disrupt your logic. Especially important in large, complex loop bodies, a single EXIT allows you to more easily trace and debug your code. However, depending on how badly the loop was written initially, you may need to perform substantial restructuring to improve the loop code.</TIP_TEXT><EXAMPLE_CODE>BEGIN LOOP IF a > 0 THEN EXIT l; -- should raise END IF; END LOOP; LOOP IF a > 0 THEN EXIT; -- should raise END IF; END LOOP; > a := 1; LOOP IF a > 0 THEN EXIT WHEN a > 1; -- should raise END IF; END LOOP; LOOP IF a > 0 THEN EXIT c WHEN a > 1; END IF; END LOOP; > a := 1; END; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R4801</TIP_LINK><WIKIREV>r19</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:44:19</WIKITIMESTAMP></RULE><RULE><ID>4802</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid unlabeled EXIT statements in a labeled loop.</DEF><RULE_TYPE>3</RULE_TYPE><CAT>48</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //LOOP[preceding-sibling::*[1]/name() = "LABEL"]//EXIT_LOOP[not(END_LABEL)] </XPATH_QUERY><TIP_TEXT>A loop label helps readers find the associated end for any given loop. This is especially true if loops are broken over screen or page boundaries. If you use a loop label, though, you should make sure to use that label in any EXIT statements inside your simple loop. That way, future changes that may introduce a nested loop are less likely to introduce an error, with the exit accidentally exiting from the wrong loop. For example, instead of the following: <pre> BEGIN &lt;&lt;abc&gt;&gt; LOOP IF complex_condition THEN IF complex_sub_condition THEN EXIT; ELSE increment_one_way; END IF; ELSE increment_another_way; END IF; END LOOP; END;</pre> you would use something like this: <pre>BEGIN &lt;&lt;abc&gt;&gt; LOOP IF complex_condition THEN IF complex_sub_condition THEN EXIT abc; ELSE increment_one_way; END IF; ELSE increment_another_way; END IF; END LOOP abc; END;</pre> While PL/SQL labels (identifiers within double angle brackets, like &lt;&lt;yearly_analysis&gt;&gt;) are most often associated with GOTOs, they can be a big help in improving the readability of code. Use a label directly in front of loops and nested anonymous blocks: To give a name to that portion of code and thereby self-document what it is doing. So that you can repeat that name with the END statement of that block or loop. This recommendation is especially important when you have multiple nestings of loops (and possibly inconsistent indentation), as in the following: <pre>LOOP &lt;body&gt; WHILE &lt;condition&gt; LOOP &lt;while body&gt; END LOOP; END LOOP;</pre> You use labels for a block and two nested loops, and then apply them in the appropriate END statements. You can now easily see which loop and block is ending, regardless of how much the code is indented. <pre>CREATE OR REPLACE PROCEDURE display_book_usage IS BEGIN &lt;&lt;best_seller_review&gt;&gt; DECLARE CURSOR yearly_analysis_cur IS SELECT ...; CURSOR monthly_analysis_cur IS SELECT ...; BEGIN &lt;&lt;yearly_analysis&gt;&gt; FOR book_rec IN yearly_analysis_cur (2000) LOOP &lt;&lt;monthly_analysis&gt;&gt; FOR month_rec IN monthly_analysis_cur ( yearly_analysis_cur%rowcount) LOOP ... lots of month-related code ... END LOOP monthly_analysis; ... lots of year-related code ... END LOOP yearly_analysis; END best_seller_review; END display_book_usage;</pre> If you use labels, it will be much easier to read your code, especially if it contains loops and nested blocks that have long bodies (i.e., the loop starts on page 2 and ends on page 7, with three other loops inside that outer loop).</TIP_TEXT><EXAMPLE_CODE>BEGIN <<the_lp>> LOOP EXIT WHEN a > 0; -- should raise END LOOP the_lp; END; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R4802</TIP_LINK><WIKIREV>r24</WIKIREV><WIKITIMESTAMP>04 Jan 2012 - 18:32:11</WIKITIMESTAMP></RULE><RULE><ID>4804</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid use of EXIT/RETURN statements in a FOR/WHILE loop.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>48</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//LOOP//EXIT_LOOP, //LOOP//RETURN</XPATH_QUERY><TIP_TEXT>Your loop contains an EXIT or RETURN statement that can cause an unstructured termination of the loop. This makes the code harder to read and maintain. A WHILE loop is designed to terminate when the Boolean expression in the WHILE statement is no longer TRUE. A FOR loop is a promise: It will execute the body of the loop for a fixed number of times, as specified in the loop range clause. If you EXIT or RETURN from within either type of loop, you create multiple exit points and make it harder to debug and trace. By following the maxim "one way in and one way out" for your loops, the resulting code is much easier to understand and debug. Here is an example of a WHILE loop that relies on EXIT to terminate the loop when the matching row is found: <pre>CREATE OR REPLACE FUNCTION matching_row ( title_list_in IN title_collection_t, title_in IN VARCHAR2 ) RETURN PLS_INTEGER IS l_row PLS_INTEGER; return_value PLS_INTEGER; BEGIN l_row := title_list_in.FIRST; WHILE (l_row IS NOT NULL) LOOP IF title_in = title_list_in (l_row) THEN return_value := l_row; EXIT; ELSE l_row := title_list_in.NEXT (l_row); END IF; END LOOP; RETURN return_value; END matching_row; </pre> To avoid the two different exit paths, we will shift the explicit EXIT into the WHILE condition: <pre>CREATE OR REPLACE FUNCTION matching_row ( title_list_in IN title_collection_t, title_in IN VARCHAR2 ) RETURN PLS_INTEGER IS l_row PLS_INTEGER; return_value PLS_INTEGER; BEGIN l_row := title_list_in.FIRST; WHILE (l_row IS NOT NULL AND return_value IS NULL) LOOP IF title_in = title_list_in (l_row) THEN return_value := l_row; ELSE l_row := title_list_in.NEXT (l_row); END IF; END LOOP; RETURN return_value; END matching_row; </pre></TIP_TEXT><EXAMPLE_CODE>DECLARE x INT; i INT; j INT; BEGIN -------------------------------------------------------- -- FOR LOOP tests -- this loop has a simple exit x := 0; FOR i IN 1 .. 100 LOOP x := x + i; IF (i = 50) THEN EXIT; -- should raise END IF; END LOOP; -- this loop has an exit when (same as exit) x := 0; FOR i IN 1 .. 100 LOOP x := x + i; EXIT WHEN (i = 50); -- should raise END LOOP; -- this loop is nested and has both types of exit x := 0; FOR i IN 1 .. 100 LOOP x := x + i; FOR j IN 1 .. 100 LOOP x := x + j; EXIT WHEN ( (i = 50) OR (j = 50)); -- should raise END LOOP; IF (x = 50) THEN EXIT; -- should raise END IF; END LOOP; -------------------------------------------------------- -- WHILE LOOP tests -- this loop has a simple exit x := 0; i := 1; WHILE (i <= 100) LOOP x := x + i; i := i + 1; IF (i = 50) THEN EXIT; -- should raise END IF; END LOOP; -- this loop has an exit when (same as exit) x := 0; i := 1; WHILE (i <= 100) LOOP x := x + i; i := i + 1; EXIT WHEN (i = 50); -- should raise END LOOP; -- this loop is nested and has both types of exit x := 0; i := 1; WHILE (i <= 100) LOOP x := x + i; i := i + 1; j := 1; WHILE (j <= 100) LOOP x := x + j; j := j + 1; EXIT WHEN ( (i = 50) OR (j = 50)); -- should raise END LOOP; IF (i = 50) THEN EXIT; -- should raise END IF; END LOOP; -------------------------------------------------------- END;</EXAMPLE_CODE><DB_VERSION>None.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R4804</TIP_LINK><WIKIREV>r24</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:44:26</WIKITIMESTAMP></RULE><RULE><ID>4806</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Avoid use of unreferenced FOR loop indexes.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>48</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>The loop index is not used for anything but traffic control inside the loop. This is one of the indicators that a numeric FOR loop is being used incorrectly. The actual body of executable statements completely ignores the loop index. When that is the case, there is a good chance that you don't need the loop at all. If the loop contains an SQL statement then consider replacing the whole loop with a single statement. You should go through your programs and remove any part of your code that is no longer used. This is a relatively straightforward process for variables and named constants. Simply execute searches for a variable's name in that variable's scope. If you find that the only place it appears is in its declaration, delete the declaration. There is never a better time to review all the steps you took, and to understand the reasons you took them, than immediately upon completion of your program. If you wait, you will find it particularly difficult to remember those parts of the program that were needed at one point, but were rendered unnecessary in the end. The following block of sample code has several dead zones that could cause a variety of problems. <pre>CREATE OR REPLACE PROCEDURE weekly_check ( isbn_in IN book.isbn%TYPE, author_in IN VARCHAR2) IS l_count PLS_INTEGER; l_counter PLS_INTEGER; l_available BOOLEAN; l_new_location PLS_INTEGER := 1056; l_published_date DATE := SYSDATE; BEGIN l_published_date := te_book.published_date (isbn_in); IF ADD_MONTHS (SYSDATE, -60) &gt; l_published_date THEN review_usage; ELSIF ADD_MONTHS (SYSDATE, -24) &gt; l_published_date THEN check_availability (isbn_in, l_available, l_count); IF l_available AND /* Turn off due to Req A12.6 */ FALSE THEN transfer_book (isbn_in, l_count - 1, l_new_location); END IF; -- Check for reserves -- reserve_pkg.analyze_requests (isbn_in); END IF; END;</pre> The sample above contains the following examples of dead code: The author_in parameter is declared but never used. It doesn't even have a default value, so you have to pass in an ignored value. l_counter is declared but not used. l_published_date is assigned a default value of SYSDATE, which is immediately overridden by the call to te_book.published_date. The call to transfer_book has been turned off with the addition of AND FALSE. The call to reserve_pkg.analyze_requests has been commented out. Note: It is much easier to maintain, debug and enhance code that does not have "dead zones". There are sometimes valid reasons for keeping "dead code" in place. You may sometimes want to "turn off" code temporarily. Also, you may need to comment out some logic, but still show that this action was done and why. In such cases, make sure that you include the necessary documentation in the code. Even better, use problem tracking or bug reporting software to keep a comprehensive history of any changes made to code.</TIP_TEXT><EXAMPLE_CODE>DECLARE a NUMBER; BEGIN FOR i IN 1 .. 2 LOOP a := i; END LOOP; END; / DECLARE a NUMBER; BEGIN FOR i IN 1 .. 2 -- should raise LOOP a := 2; END LOOP; END; / DECLARE a NUMBER; BEGIN FOR i IN 1 .. 2 LOOP DECLARE b NUMBER; BEGIN b := a + f (i); END; END LOOP; END; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R4806</TIP_LINK><WIKIREV>r19</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:44:30</WIKITIMESTAMP></RULE><RULE><ID>4807</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid hard-coded upper or lower bound values with FOR loops.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>48</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//RANGE/(RANGE_LOW, RANGE_HIGH)/LITERAL</XPATH_QUERY><TIP_TEXT>Your LOOP statement uses a hard-coded value for either its upper or lower bounds. This creates a "weak link" in your program because it assumes that this value will never change. A better practice is to create a named constant (or function) in a package specification and reference this named element instead of the hard-coded value. For example: <pre> DECLARE x INTEGER; loop_min CONSTANT INTEGER := 1; loop_max CONSTANT INTEGER := 100; BEGIN -- Example of a LOOP counting up x := 0; FOR i IN 1 .. 100 LOOP x := x + i; END LOOP; -- would be better written as x := 0; FOR i IN loop_min .. loop_max LOOP x := x + i; END LOOP; END; / </pre></TIP_TEXT><EXAMPLE_CODE>BEGIN FOR i IN 1 .. abc -- should raise LOOP NULL; END LOOP; FOR i IN abc .. 100 -- should raise LOOP NULL; END LOOP; FOR i IN abc .. def LOOP NULL; END LOOP; FOR i IN 1 .. 100 -- should raise LOOP NULL; END LOOP; END; /</EXAMPLE_CODE><DB_VERSION>None.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R4807</TIP_LINK><WIKIREV>r23</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:44:33</WIKITIMESTAMP></RULE><RULE><ID>4808</ID></RULE><RULE><ID>4809</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>A loop that contains DML statements should be refactored to use BULK COLLECT and FORALL.</DEF><RULE_TYPE>2</RULE_TYPE><CAT>22</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //INTO_LIST [../../(EXPR[@cat="query"], EXECUTE_IMMEDIATE, FETCH) ] [ancestor::LOOP[not(@cat="forall")]] [not (@bulk_collect="true")], //(INSERT, DELETE, UPDATE, MERGE)/.[ancestor::LOOP[not(@cat = "forall")]]/TOKEN[1] </XPATH_QUERY><TIP_TEXT>Whenever an insert, update, delete or merge is executed inside a loop (cursor FOR loop, simple loop, FOR loop, etc.), you should convert to using BULK COLLECT and FORALL. BULK COLLECT is needed when the loop queries data from relational tables. Even though Oracle will automatically optimize performance of the query to match BULK COLLECTION, you will need BULK COLLECT to populate collections for use in FORALL. Use of FORALL will result in dramatic increase in DML performance, driven by collections and reductions in context switches.</TIP_TEXT><EXAMPLE_CODE>CREATE OR REPLACE PROCEDURE upd_for_dept ( dept_in IN employees.department_id%TYPE, newsal_in IN employees.salary%TYPE) IS CURSOR emp_cur IS SELECT employee_id, salary, hire_date FROM employees WHERE department_id = dept_in; BEGIN FOR rec IN emp_cur LOOP BEGIN INSERT -- should raise INTO employee_history (employee_id, salary, hire_date) VALUES (rec.employee_id, rec.salary, rec.hire_date); FETCH data_ -- should raise --BULK COLLECT INTO a_id_, a_grouping_ LIMIT 5000; FETCH data_ -- no hit BULK COLLECT INTO a_id_, a_grouping_ LIMIT 5000; rec.salary := newsal_in; adjust_compensation (rec.employee_id, rec.salary); UPDATE employees -- should raise SET salary = rec.salary WHERE employee_id = rec.employee_id; EXCEPTION WHEN OTHERS THEN log_error; END; END LOOP; END upd_for_dept;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r31</WIKIREV><WIKITIMESTAMP>22 Mar 2012 - 17:42:57</WIKITIMESTAMP></RULE><RULE><ID>4810</ID><STATUS>5</STATUS><COLOR>Yellow</COLOR><DEF>Ensure that FORALL statements rely on a driving array that is not empty.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>Ensure that FORALL statements rely on a driving array that is not empty.</TIP_TEXT><EXAMPLE_CODE></EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r18</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 01:45:40</WIKITIMESTAMP></RULE><RULE><ID>4811</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>A simple or WHILE loop that fetches data from a cursor and then executes DML should be refactored to use BULK COLLECT and FORALL.</DEF><RULE_TYPE>2</RULE_TYPE><CAT>22</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //LOOP [./qp5:set-root()/descendant::FETCH [following::INSERT or following::UPDATE]]/TOKEN[1] </XPATH_QUERY><TIP_TEXT>Whenever an insert, update, delete or merge is executed inside a loop (cursor FOR loop, simple loop, FOR loop, etc.), you should convert to using BULK COLLECT and FORALL. BULK COLLECT is needed when the loop queries data from relational tables. Even though Oracle will automatically optimize performance of the query to match BULK COLLECTION, you will need BULK COLLECT to populate collections for use in FORALL. Use of FORALL will result in dramatic increase in DML performance, driven by collections and reductions in context switches.</TIP_TEXT><EXAMPLE_CODE>CREATE OR REPLACE PROCEDURE upd_for_dept ( dept_in IN employees.department_id%TYPE, newsal_in IN employees.salary%TYPE) IS CURSOR emp_cur IS SELECT employee_id, salary, hire_date FROM employees WHERE department_id = dept_in; emp_rec emp_cur%ROWTYPE; BEGIN OPEN emp_cur; LOOP -- should raise FETCH emp_cur INTO emp_rec; EXIT WHEN emp_cur%NOTFOUND; BEGIN INSERT INTO employee_history (employee_id, salary, hire_date) VALUES (rec.employee_id, rec.salary, rec.hire_date); rec.salary := newsal_in; adjust_compensation (rec.employee_id, rec.salary); UPDATE employees SET salary = rec.salary WHERE employee_id = rec.employee_id; EXCEPTION WHEN OTHERS THEN log_error; END; END LOOP; END upd_for_dept; -- should not raise BEGIN OPEN emp_cur; LOOP BEGIN INSERT INTO employee_history (employee_id, salary, hire_date) VALUES (rec.employee_id, rec.salary, rec.hire_date); rec.salary := newsal_in; adjust_compensation (rec.employee_id, rec.salary); UPDATE employees SET salary = rec.salary WHERE employee_id = rec.employee_id; EXCEPTION WHEN OTHERS THEN log_error; END; FETCH emp_cur INTO emp_rec; EXIT WHEN emp_cur%NOTFOUND; END LOOP; END upd_for_dept;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r13</WIKIREV><WIKITIMESTAMP>22 Mar 2012 - 17:41:27</WIKITIMESTAMP></RULE><RULE><ID>4812</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>SELECT BULK COLLECT INTO (implicit bulk query) could cause problems with session memory if too many rows are retrieved. Consider using FETCH BULK COLLECT with LIMIT clause.</DEF><RULE_TYPE>2</RULE_TYPE><CAT>22</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //EXPR[@cat="query"]/INTO_LIST [@bulk_collect="true"] </XPATH_QUERY><TIP_TEXT>Whenever an insert, update, delete or merge is executed inside a loop (cursor FOR loop, simple loop, FOR loop, etc.), you should convert to using BULK COLLECT and FORALL. BULK COLLECT is needed when the loop queries data from relational tables. Even though Oracle will automatically optimize performance of the query to match BULK COLLECTION, you will need BULK COLLECT to populate collections for use in FORALL. Use of FORALL will result in dramatic increase in DML performance, driven by collections and reductions in context switches.</TIP_TEXT><EXAMPLE_CODE>DECLARE TYPE employees_aat IS TABLE OF employees%ROWTYPE; l_employees employees_aat; BEGIN -- All rows at once... SELECT * BULK COLLECT INTO l_employees -- should raise FROM employees ORDER BY last_name DESC; DBMS_OUTPUT.put_line (l_employees.COUNT); FOR indx IN 1 .. l_employees.COUNT LOOP DBMS_OUTPUT.put_line (l_employees (indx).last_name); END LOOP; END; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r12</WIKIREV><WIKITIMESTAMP>11 Mar 2012 - 11:16:42</WIKITIMESTAMP></RULE><RULE><ID>5001</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid comparisons with null value, consider using IS [NOT] NULL.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>50</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//EXPR[@cat = ("eq", "ne", "gt", "lt", "ge", "le")] [LITERAL[@type="boolean" and @value="null"]]</XPATH_QUERY><TIP_TEXT>The NULL value can cause confusion both from the standpoint of code review and code execution. You should always use the IS NULL or IS NOT NULL syntax when you need to check to see if a value is or is not NULL. So code that looks like this... <pre>IF variable = NULL</pre> ...should be changed to: <pre>IF variable IS NULL</pre> Code that looks like this... <pre>IF variable != NULL</pre> ...should be changed to: <pre>IF variable IS NOT NULL</pre> Of course, in many cases the check for NULL will not be that obvious. The code might look like this... <pre>IF variable != lowest_value (SYSDATE)</pre> ...and that might look perfectly fine - on the surface. However, if variable evaluates to NULL, this conditional clause will likely not work as desired. So change it to: <pre>IF (variable != lowest_value (SYSDATE) OR variable IS NULL)</pre></TIP_TEXT><EXAMPLE_CODE>DECLARE a NUMBER; BEGIN a := 3; IF ( a = NULL -- should raise OR a <> NULL -- should raise OR a < NULL -- should raise OR a > NULL -- should raise OR a >= NULL -- should raise OR a <= NULL -- should raise ) THEN a := 1; END IF; IF (a = b OR a = c OR a <= NULL) -- should raise THEN a := 1; END IF; END; / BEGIN IF a IS NULL THEN a := 1; END IF; IF a IS NOT NULL THEN a := 1; END IF; END; / DECLARE a NUMBER; BEGIN a := 0; IF (a IS NULL OR b IS NOT NULL) THEN a := 3; END IF; END; / BEGIN IF a = NULL -- should raise THEN NULL; END IF; IF a = NULL AND (b != NULL OR c) -- should raise THEN NULL; END IF; IF a IS NULL OR d IS NOT NULL THEN NULL; END IF; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R5001</TIP_LINK><WIKIREV>r24</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:44:57</WIKITIMESTAMP></RULE><RULE><ID>5002</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid assigning strings of zero length to character-type variables.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>50</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//RHS/LITERAL[@type="text" and @value="''"], //DEFAULT/LITERAL[@type="text" and @value="''"], //PARAM/LITERAL[@type="text" and @value="''"]</XPATH_QUERY><TIP_TEXT>In Oracle 8, strings of zero length are not equivalent to a NULL. PL/SQL treats any zero-length string like a null. This includes values returned by character functions and Boolean expressions. According to the ANSI SQL 1992 Transitional standard, however, a zero-length or empty string is not the same as NULL. The Oracle database server may comply fully with this aspect of the standard in the future. Therefore, it is recommended that applications ensure that empty strings values and NULL are not treated equivalently. Therefore, code like this: <pre>IF variable = ''</pre> should be replaced with <pre>IF variable IS NULL</pre> To concatenate an expression that might be null, use the NVL function to explicitly convert the expression to a zero-length string. Instead of this: <pre>my_string := book_title || first_review</pre> write this: <pre>my_string := book_title || NVL (first_review, '');</pre></TIP_TEXT><EXAMPLE_CODE>DECLARE a VARCHAR2 (100) := ''; -- should raise BEGIN IF b = '' THEN proc (a, ''); -- should raise a := ''; -- should raise END IF; END; / BEGIN a := ''; -- should raise a := '' || ''; END; DECLARE l CHAR (1) := ''; -- should raise BEGIN l := ''; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R5002</TIP_LINK><WIKIREV>r22</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:45:01</WIKITIMESTAMP></RULE><RULE><ID>5003</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid initializing variables to NULL.</DEF><RULE_TYPE>3</RULE_TYPE><CAT>50</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//(VAR_DECL, CONST_DECL)/DEFAULT /LITERAL[@type="boolean" and @value="null"]</XPATH_QUERY><TIP_TEXT>By default, variables are initialized to NULL. So while your setting of the default value will not change the behavior of the program, it is unnecessary. You should do this explicit assignment only if you want to emphasize to the reader of your code that this NULL value is crucial to the functioning of the program. In other words, you don't want the DEFAULT value, so much as you want the NULL value.</TIP_TEXT><EXAMPLE_CODE>DECLARE a NUMBER (2) := NULL; -- should raise b VARCHAR2 (10) DEFAULT NULL; -- should raise BEGIN f (a, b); END; / DECLARE a VARCHAR2 (100) := NULL; -- should raise b CONSTANT VARCHAR2 (8) := NULL; -- should raise b2 VARCHAR2 (8) := NuL; c CONSTANT VARCHAR2 (8) := "null"; d CONSTANT VARCHAR2 (8) := "NULL"; BEGIN NULL; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R5003</TIP_LINK><WIKIREV>r22</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:45:04</WIKITIMESTAMP></RULE><RULE><ID>5004</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Use of NULL statements.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>50</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//NULL</XPATH_QUERY><TIP_TEXT>While there is nothing intrinsically wrong with the use of NULL statements, their appearance is nonetheless questionable. Often developers place them inside constructs as placeholders while coding. But those placeholders should then be removed before the code is marked as completed. A valid and useful application of the NULL; statement occurs when a developers uses NULL; to indicate that she has considered a particular path of execution and has decided that it should be NULL. For example, in the following code, we are left to wonder if the developer meant to do something in the "else" condition... <pre>IF publish_pkg.date_available (l_book_id) >= SYSDATE THEN prepare_for_publication (l_book_id); END IF; In the following implementation, however, the developer makes her intentions clear: IF publish_pkg.date_available (l_book_id) < SYSDATE THEN NULL; ELSE prepare_for_publication (l_book_id); END IF;</pre></TIP_TEXT><EXAMPLE_CODE>BEGIN NULL; -- should raise END; / BEGIN NULL; -- should raise LOOP IF a THEN NULL; -- should raise END IF; END LOOP; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R5004</TIP_LINK><WIKIREV>r21</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:45:07</WIKITIMESTAMP></RULE><RULE><ID>5201</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid RETURN statements in the initialization section of the package body.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>52</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//PACKAGE_BODY/BLOCK//RETURN</XPATH_QUERY><TIP_TEXT>The initialization section of a package body might look like the executable section of a procedure, but it is only used to initialize values within a package. It is run the first time a package is referenced within a session. It makes no sense at all to call the RETURN statement within this section of code. Note, however, that Oracle will compile such code and it will not affect the behavior of the package beyond the fact that none of the lines of code in the initialization section following the RETURN statement will be executed.</TIP_TEXT><EXAMPLE_CODE>CREATE PACKAGE BODY pa IS BEGIN f; RETURN; -- should raise END pa; / CREATE PACKAGE BODY pack2 AS FUNCTION func2 (x IN NUMBER) RETURN numset_t PIPELINED AS BEGIN FOR i IN 1 .. x LOOP PIPE ROW (i); END LOOP; RETURN; -- no 5201, RETURN used in the function END func2; END pack2; / PACKAGE BODY pkg IS FUNCTION f RETURN NUMBER IS BEGIN RETURN 0; END; BEGIN RETURN 1; -- should raise IF a THEN IF b THEN RETURN 2; -- should raise END IF; END IF; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R5201</TIP_LINK><WIKIREV>r20</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:45:11</WIKITIMESTAMP></RULE><RULE><ID>5202</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid defining global public variables in the package specification.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>52</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//PACKAGE_SPEC/VAR_DECL</XPATH_QUERY><TIP_TEXT>You should always declare package-level data inside the package body. You can then define "get and set" programs (function and procedure, respectively) in the package specification to provide controlled access to that data. With this approach, you can guarantee data integrity, change your data structure implementation, and also track access to those data structures. Data structures (scalar variables, collections, cursors) declared in the package specification (not within any specific program) can be directly referenced from any program run from a session with EXECUTE authority on the package. This is should be avoided. Instead, declare all package-level data in the package body and provide "get and set" programs - a function to GET the value and a procedure to SET the value - in the package specification. Developers then can access the data through these programs - and automatically follow whatever rules you establish for manipulating that data. For example, you create a package to calculate overdue fines. The fine is, by default, $.10 per day, but you can change it according to this rule: the fine can never be less than $.05 or more than $.25 per day. Here is one version: <pre>CREATE OR REPLACE PACKAGE overdue_pkg IS g_daily_fine NUMBER := .10; FUNCTION days_overdue (isbn_in IN book.isbn%TYPE) RETURN INTEGER; -- Relies on g_daily_fine for calculation FUNCTION fine (isbn_in IN book.isbn%TYPE) RETURN INTEGER; END overdue_pkg; </pre> You can see the problem with this package in the following block: <pre>BEGIN overdue_pkg.g_daily_fine := .50; pl ('Your overdue fine is ' || overdue_pkg.fine (' 1-56592-375-8')); END; </pre> The block was able to bypass the business rule and apply a daily fine of $.50. By "publishing" the daily fine variable, you lose control of your data structure and the ability to enforce your business rules. The following rewrite of the overdue_pkg fixes the problem; it will show only the replacement of the g_daily_fine variable with its "get and set" programs: <pre>CREATE OR REPLACE PACKAGE overdue_pkg IS PROCEDURE set_daily_fine (fine_in IN NUMBER); PROCEDURE daily_fine RETURN NUMBER; and the implementation: CREATE OR REPLACE PACKAGE BODY overdue_pkg IS g_daily_fine NUMBER := .10; PROCEDURE set_daily_fine (fine_in IN NUMBER) IS BEGIN g_daily_fine := GREATEST (LEAST (fine_in, .25), .05); END; FUNCTION daily_fine RETURN NUMBER IS BEGIN RETURN g_daily_fine; END; </pre> Now it is impossible to bypass the business rule for the daily fine. You would be even better off, of course, if you put your maximum and minimum fine information in a database table. Then you can use the package initialization section to load these limits into package data structures. This way, if (when) the data points change, you will not have to change the program itself, just some rows and columns in a table. *Benefits* The only way to change a value is through the set procedure. The values of your data structures are protected; business rules can be enforced without exception. You can track all accesses to your data structure - that is, you can put a "watch" on a variable. This is a debugging feature that is not even supported by Oracle's debugger API (as of Oracle8i). By hiding the data structure, you give yourself the freedom to change how that data is defined without affecting all accesses to the data. Package data can now be accessed by from Oracle Developer tools, such as Formsbuilder. You may not, from "client-side" PL/SQL (i.e., code written in Oracle Developer components) reference stored package elements unless they are procedures or functions. *Challenges* You will need to write get and set programs for your data structures. Review existing packages to identify data structures defined in specifications - and then fix them by moving the structures to the bodies. You will have to rewrite some existing programs that reference that data.</TIP_TEXT><EXAMPLE_CODE>CREATE PACKAGE pa IS a NUMBER (8); -- should raise END pa; / PACKAGE b IS a NUMBER; -- should raise b NUMBER := 234; -- should raise c CONSTANT NUMBER := 123; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R5202</TIP_LINK><WIKIREV>r25</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:45:14</WIKITIMESTAMP></RULE><RULE><ID>5401</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Avoid using an IN OUT parameter as IN only.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>54</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>By showing the mode of parameters, you aid the reader. If you do not specify a parameter mode, the default mode is in. Explicitly showing the mode indication of all parameters is a more assertive action than simply taking the default mode. Anyone reviewing the code later will be more confident that you intended the parameter mode to be in. Use the mode that reflects the actual use of the parameter. You should avoid the tendency to make all parameters in out mode because out mode parameters may be examined as well as updated. Here is a function that declares a parameter IN OUT "just in case". Upon examination, the parameter is never modified, only read: <pre> PROCEDURE book_ins ( isbn_inout IN OUT book.isbn%TYPE, title_inout IN OUT book.title%TYPE DEFAULT NULL, summary_inout IN OUT book.summary%TYPE DEFAULT NULL, author_inout IN OUT book.author%TYPE DEFAULT NULL, date_published_inout IN OUT book.date_published%TYPE DEFAULT NULL, page_count_inout IN OUT book.page_count%TYPE DEFAULT NULL ) IS BEGIN INSERT INTO book ( isbn, title, summary, author, date_published, page_count ) VALUES ( isbn_inout, title_inout, summary_inout, author_inout, date_published_inout, page_count_inout ); EXCEPTION WHEN OTHERS THEN err.LOG; END book_ins;</pre> Change it to: <pre>PROCEDURE book_ins ( isbn_in IN book.isbn%TYPE, title_in IN book.title%TYPE DEFAULT NULL, summary_in IN book.summary%TYPE DEFAULT NULL, author_in IN book.author%TYPE DEFAULT NULL, date_published_in IN book.date_published%TYPE DEFAULT NULL, page_count_in IN book.page_count%TYPE DEFAULT NULL ) IS BEGIN INSERT INTO book ( isbn, title, summary, author, date_published, page_count ) VALUES ( isbn_in, title_in, summary_in, author_in, date_published_in, page_count_in ); EXCEPTION WHEN OTHERS THEN err.LOG; END book_ins </pre></TIP_TEXT><EXAMPLE_CODE>CREATE OR REPLACE PROCEDURE p (a IN OUT NUMBER, -- should raise b IN OUT NUMBER, c IN OUT NUMBER, d IN OUT NUMBER, e IN OUT NUMBER, f IN OUT NUMBER) IS n NUMBER (4); BEGIN n := a; b := n; c := c + a; d := c; e := e; END p; / CREATE OR REPLACE PROCEDURE p (a IN OUT NUMBER, b IN OUT NUMBER, c IN OUT NUMBER, -- should raise d IN OUT NUMBER, -- should raise e IN OUT NUMBER, -- should raise f IN OUT NUMBER, -- should raise g IN OUT NUMBER, -- should raise h IN OUT NUMBER, -- should raise i IN OUT NUMBER, -- should raise j IN OUT NUMBER, -- should raise k IN OUT NUMBER -- should raise ) IS BEGIN proc (a); proc (func2 (b)); proc (func2 (b) + 1); proc (func2 (b + 1)); -- proc (c + d); proc (e AND f); proc (g * h); proc (i ** j); proc (-k); END p; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R5401</TIP_LINK><WIKIREV>r20</WIKIREV><WIKITIMESTAMP>25 Sep 2011 - 00:45:02</WIKITIMESTAMP></RULE><RULE><ID>5402</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Avoid using an IN OUT parameter as OUT only.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>54</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>By showing the mode of parameters, you aid the reader. If you do not specify a parameter mode, the default mode is IN. Explicitly showing the mode indication of all parameters is a more assertive action than simply taking the default mode. Anyone reviewing the code later will be more confident that you intended the parameter mode to be IN. Use the mode that reflects the actual use of the parameter. You should avoid the tendency to make all parameters IN OUT mode because OUT mode parameters may be examined as well as updated. Here is a procedure that declares its parameters IN OUT "just in case": <pre> PROCEDURE book_receive ( isbn_inout IN OUT book.isbn%TYPE, title_inout IN OUT book.title%TYPE, summary_inout IN OUT book.summary%TYPE, author_inout IN OUT book.author%TYPE, date_published_inout IN OUT book.date_published%TYPE, page_count_inout IN OUT book.page_count%TYPE, wait IN INTEGER := 0 ) IS BEGIN -- Receive next message and unpack for each column. g_status := DBMS_PIPE.receive_message (defname, wait); IF g_status = 0 THEN DBMS_PIPE.unpack_message (isbn_inout); DBMS_PIPE.unpack_message (title_inout); DBMS_PIPE.unpack_message (summary_inout); DBMS_PIPE.unpack_message (author_inout); DBMS_PIPE.unpack_message (date_published_inout); DBMS_PIPE.unpack_message (page_count_inout); END IF; g_action := 'RECEIVE_MESSAGE'; END book_receive;</pre> Change it to: <pre>PROCEDURE book_receive ( isbn_out OUT book.isbn%TYPE, title_out OUT book.title%TYPE, summary_out OUT book.summary%TYPE, author_out OUT book.author%TYPE, date_published_out OUT book.date_published%TYPE, page_count_out OUT book.page_count%TYPE, wait IN INTEGER := 0 ) IS BEGIN -- Receive next message and unpack for each column. g_status := DBMS_PIPE.receive_message (defname, wait); IF g_status = 0 THEN DBMS_PIPE.unpack_message (isbn_out); DBMS_PIPE.unpack_message (title_out); DBMS_PIPE.unpack_message (summary_out); DBMS_PIPE.unpack_message (author_out); DBMS_PIPE.unpack_message (date_published_out); DBMS_PIPE.unpack_message (page_count_out); END IF; g_action := 'RECEIVE_MESSAGE'; END book_receive; </pre></TIP_TEXT><EXAMPLE_CODE>CREATE OR REPLACE PROCEDURE p (a IN OUT NUMBER, b IN OUT NUMBER, -- should raise c IN OUT NUMBER, d IN OUT NUMBER, -- should raise e IN OUT NUMBER, f IN OUT NUMBER) IS n NUMBER (4); BEGIN n := a; b := n; c := c + a; d := c; e := e; END p; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R5402</TIP_LINK><WIKIREV>r20</WIKIREV><WIKITIMESTAMP>25 Sep 2011 - 00:45:05</WIKITIMESTAMP></RULE><RULE><ID>5405</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Ensure mode of parameter is specified with IN parameter.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>54</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//PARAM_SPEC[not((IN, OUT, INOUT)) ]</XPATH_QUERY><TIP_TEXT>It is recommended that you always specify a parameter mode with your parameters. Your intended use of the parameter is then documented explicitly in the code itself. The default mode of a parameter is IN, and that is what will be used in this case. It is far better, however, to explicitly declare the mode of the parameter. The resulting code is more readable and more easily maintained by other developers. You can write a parameter list like this: <pre>FUNCTION isfunction ( schema VARCHAR2, package VARCHAR2, program VARCHAR2, overload PLS_INTEGER := NULL ) RETURN BOOLEAN </pre> but it is much better to (a) use prefixes or suffixes on your parameter names and (b) to explicitly show the parameter mode, as in: <pre>FUNCTION isfunction ( schema_in IN VARCHAR2, package_in IN VARCHAR2, program_in IN VARCHAR2, overload_in IN PLS_INTEGER := NULL ) RETURN BOOLEAN </pre></TIP_TEXT><EXAMPLE_CODE> CREATE PROCEDURE p (a IN NUMBER, b NUMBER, -- should raise c IN OUT NUMBER, d VARCHAR2) -- should raise IS BEGIN f(a, b, c, d); c := 1; END p; / </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R5405</TIP_LINK><WIKIREV>r26</WIKIREV><WIKITIMESTAMP>28 Feb 2012 - 09:43:23</WIKITIMESTAMP></RULE><RULE><ID>5406</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Remove unused parameters or modify code to use the parameter.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>54</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>You have defined a parameter in the parameter list, but it is not used inside the program. This can occur as the result of deprecations in code over time, but you should make sure that this situation does not reflect a problem in your code. You should go through your programs and remove any part of your code that is no longer used.</TIP_TEXT><EXAMPLE_CODE>PROCEDURE p (a IN NUMBER, b IN NUMBER) -- should raise IS ia NUMBER; BEGIN ia := b; END p; / PROCEDURE p (a IN NUMBER, -- should raise b IN NUMBER, c IN NUMBER, -- should raise d IN NUMBER -- should raise ) IS ia NUMBER; BEGIN IF (b IS NOT NULL) THEN ia := 13; END IF; END p; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R5406</TIP_LINK><WIKIREV>r18</WIKIREV><WIKITIMESTAMP>23 Sep 2011 - 18:29:20</WIKITIMESTAMP></RULE><RULE><ID>5601</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid using RETURN statements in a PROCEDURE.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>56</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//RETURN[ ancestor::*[self::PROCEDURE_BODY or self::FUNCTION_BODY][1][self::PROCEDURE_BODY]]</XPATH_QUERY><TIP_TEXT>Use of the RETURN statement is legal within a procedure in PL/SQL, but it is very similar to a GOTO, which means you end up with poorly-structured code that is hard to debug and maintain. A good general rule to follow as you write your PL/SQL programs is: "one way in and one way out". In other words, there should be just one way to enter or call a program, and there should be one way out, one exit path from a program (or loop) on successful termination. By following this rule, you end up with code that is much easier to trace, debug, and maintain. For a function, this means that you should think of the executable section as a funnel: all the lines of code narrow down to the last executable statement: <pre>RETURN <return value>;</pre> Note the following: You could, and should, still have RETURN statements in your exception handlers. Not every exception should be passed un-handled from your function. It is possible (i.e., acceptable syntax) to use an "unqualified" RETURN statement in a procedure, as follows: <pre> IF all_done THEN RETURN; END IF; </pre> and the procedure will immediately terminate and return control. You should not do this, however, as it results in very unstructured code that is hard to debug and maintain. This same recommendation holds for the initialization section of a package. Here is a very simple function that relies on multiple RETURNs: <pre> CREATE OR REPLACE FUNCTION status_desc ( cd_in IN VARCHAR2 ) RETURN VARCHAR2 IS BEGIN IF cd_in = 'C' THEN RETURN 'CLOSED'; ELSIF cd_in = 'O' THEN RETURN 'OPEN'; ELSIF cd_in = 'I' THEN RETURN 'INACTIVE'; END IF; END; </pre> At first glance, this function looks fine. Yet this function has a deep flaw, due to the reliance on separate RETURNs: if you do not pass in C, O, or I for the cd_in argument, the function raises: ORA-06503: PL/SQL: Function returned without value Here is a rewrite that relies on (a) a standard types package to avoid hard-coding a VARCHAR2 variable length and also gives names to literal values, and (b) a single RETURN at the end of the function: <pre> CREATE OR REPLACE FUNCTION status_desc ( cd_in IN VARCHAR2 ) RETURN stdtypes.description_t IS retval stdtypes.description_t; BEGIN IF cd_in = stdtypes.c_closed_abbrev THEN retval := stdtypes.c_closed; ELSIF cd_in = stdtypes.c_open_abbrev THEN retval := stdtypes.c_open; ELSIF cd_in = stdtypes.c_inactive_abbrev ' THEN retval := stdtypes.c_inactive; END IF; RETURN retval; END; </pre> This program also safely and correctly returns NULL if the program does not receive a value of 'C', 'O', or 'I', unlike the first implementation. A single RETURN function is easier to trace and debug, since you don't have to worry about multiple exit pathways from the function.</TIP_TEXT><EXAMPLE_CODE>PROCEDURE p IS BEGIN IF a THEN RETURN 2; -- should raise END IF; RETURN 1; -- should raise END; PROCEDURE p IS FUNCTION f RETURN NUMBER IS BEGIN IF a THEN RETURN 2; END IF; RETURN 1; END; BEGIN IF a THEN RETURN 2; -- should raise END IF; RETURN 1; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R5601</TIP_LINK><WIKIREV>r21</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:45:31</WIKITIMESTAMP></RULE><RULE><ID>5602</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid coding procedures with no parameters.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>56</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//PROCEDURE_BODY[not(PARAM_SPEC_LIST)]/QNAME/@value</XPATH_QUERY><TIP_TEXT>While this is not necessarily a problem, a procedure without any parameters is more likely to (a) depend on global variables for data or (b) not be as reusable as it could be. Your program would have to be very simple or your requirements rigid to not require the flexibility offered by parameters. Take a look at the body of this program. Is it referencing external variables? Pass them as parameters. Is the program making assumptions about what the user will want to have happen? Move those assumptions to the parameter so that the user can choose. Use package globals judiciously and only in package bodies. A global variable is a data structure that can be referenced outside of the scope or block in which it is declared. In the following block, for example, the l_publish_date is global to the local display_book_info procedure: <pre> DECLARE l_publish_date DATE; ... PROCEDURE display_book_info IS BEGIN DBMS_OUTPUT.PUT_LINE (l_publish_date); END; </pre> Globals are dangerous and to be avoided as they create hidden "dependencies" or side effects. A global does not have to be passed through the parameter list, so it is hard to even know that a global is referenced in a program without looking at the implementation. Globals are most often defined in packages. If a variable is declared at the package level (not within any specific program), that variable exists and retains its value for the duration of the session. The general solution to this problem is to pass the global as a parameter in the procedure and function; do not reference it directly within the program. Another general technique to keep in mind is to declare variables, cursors, functions, and other objects as deep as possible (i.e., in the block nearest to where, or within which, that object will be used), in order to reduce the chance of unintended use by other sections of the code. Reliance on global data structures can be a particularly acute problem in Oracle Developer's Formsbuilder (previously known as Oracle Forms). Developers have historically relied on :GLOBAL data structures to pass information between forms. In the latest versions of Oracle Developer, avoid :GLOBAL variables. Instead, build and share packages (and variables declared within those packages) among forms. Here is an example of a function with a hidden dependency on a global variable: <pre>CREATE OR REPLACE FUNCTION overdue_fine ( isbn_in IN book.isbn%TYPE) RETURN NUMBER IS l_days_overdue NUMBER; BEGIN l_days_overdue := overdue_pkg.days_overdue (isbn_in, SYSDATE); RETURN (l_days_overdue * overdue_pkg.g_daily_fine); END; </pre> The global is the amount of the daily fine. It is buried inside the function. By writing the function this way, two things happen: (a) there is no flexibility to pass in a different daily fine amount, as may be required, and (b) if the daily fine has not been set within the overdue package, the function doesn't work properly. The dependency can be eliminated by adding a parameter: <pre> CREATE OR REPLACE FUNCTION overdue_fine ( isbn_in IN book.isbn%TYPE, daily_fine_in IN NUMBER) RETURN NUMBER IS l_days_overdue NUMBER; BEGIN l_days_overdue := overdue_pkg.days_overdue (isbn_in, SYSDATE); RETURN (l_days_overdue * daily_fine_in); END; </pre> By reducing the inter-dependencies between programs, it is easier and safer make a change to one without worrying about all the others being affected. Of course, existing programs may need to be revamped to pull out global references and replace them with either parameters or calls to "get and set" programs that encapsulate the global data</TIP_TEXT><EXAMPLE_CODE>PACKAGE BODY pkg IS PROCEDURE p -- should raise IS BEGIN NULL; END; PROCEDURE p (a NUMBER) IS PROCEDURE p -- should raise IS BEGIN NULL; END; BEGIN NULL; END; END; CREATE OR REPLACE PROCEDURE z -- should raise IS BEGIN a := 3; END z; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R5602</TIP_LINK><WIKIREV>r22</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:45:35</WIKITIMESTAMP></RULE><RULE><ID>5603</ID></RULE><RULE><ID>5801</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid storing ROWIDs or UROWIDs in a table.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>58</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY>//EXPR[@cat="query"] [(ancestor::INSERT, ancestor::UPDATE, ancestor::MERGE_USING)] //QNAME [not(ancestor::WHERE)]/IDENTIFIER [@value="ROWID" or @value="UROWID"], //INSERT//QNAME/IDENTIFIER[@value="ROWID" or @value="UROWID"], //UPDATE/ASSIGN_LIST//RHS/QNAME/IDENTIFIER[@value="ROWID" or @value="UROWID"]</XPATH_QUERY><TIP_TEXT>It is an extremely dangerous practice to store ROWIDs in a table, except for some very limited scenarios of runtime duration. Any manually explicit or system generated implicit table reorganization will reassign the row's ROWID and break the data consistency.</TIP_TEXT><EXAMPLE_CODE>BEGIN INSERT INTO tab VALUES (1, ROWID, -- should raise urowiD); -- should raise UPDATE tab SET a = ROWID, -- should raise b = UROWID; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R5801</TIP_LINK><WIKIREV>r24</WIKIREV><WIKITIMESTAMP>23 Feb 2012 - 22:04:15</WIKITIMESTAMP></RULE><RULE><ID>5803</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Use FETCH into a record rather than a list of variables.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>58</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//FETCH/INTO_LIST[not(@bulk_collect = "true")][count(*)>1]</XPATH_QUERY><TIP_TEXT>You should always FETCH into a record declared on the corresponding cursor using the %ROWTYPE attribute. Then if the structure of the SELECT statement in the cursor changes, (another expression is added to the list, for example), your code will adapt automatically after a recompile. Whenever you fetch data from a cursor, whether it's an explicit cursor or a cursor variable, you should fetch into a record defined from that cursor with %ROWTYPE. For example, suppose that a cursor is declared in a package as follows: <pre> PACKAGE book_pkg IS CURSOR books_by_category ( category_in IN book.category%TYPE) IS SELECT title, author FROM book WHERE category = category_in; END book_pkg; </pre> Now you need to fetch information from this cursor. If you fetch into individual variables like this: <pre> DECLARE l_title book.title%TYPE; l_author book.author%TYPE; BEGIN OPEN book_pkg.books_by_category ('SCIFI'); FETCH book_pkg.books_by_category INTO l_title, l_author; </pre> then you are hard-coding the number of values returned by a cursor, as well as the datatypes of the individual variables. This can be a very dangerous assumption to make. What if the owner of the book_pkg package decides to add another column to the SELECT list? Then the code will fail to compile. If, on the other hand, you write the code like this: <pre> DECLARE scifi_rec book_pkg.books_by_category%ROWTYPE; BEGIN OPEN book_pkg.books_by_category ('SCIFI'); FETCH book_pkg.books_by_category INTO scifi_rec; </pre> then if the cursor ever changes, the code will/can be recompiled and it will automatically adapt to the new cursor structure. You also write less code, since you don't have to declare individual variables.</TIP_TEXT><EXAMPLE_CODE>BEGIN FETCH t INTO rec; FETCH t INTO a, b, c; -- should raise FETCH t INTO :a, :b, :c; -- should raise END; DECLARE CURSOR cur IS SELECT * FROM mytab; BEGIN FETCH emp_cv BULK COLLECT INTO empnos, enames; FETCH cur INTO v1; FETCH cur INTO v1, v2; -- should raise FETCH cur INTO v1, v2, v3, v4; -- should raise FETCH cur INTO v1; FETCH cur INTO v1, v2; -- should raise FETCH cur INTO v1, v2, v3, v4; -- should raise END; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R5803</TIP_LINK><WIKIREV>r23</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:45:45</WIKITIMESTAMP></RULE><RULE><ID>5804</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Ensure SELECT COUNT(*) is being used to check the number of rows and not merely the existence of rows.</DEF><RULE_TYPE>2</RULE_TYPE><CAT>58</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>A common error among developers is to answer the question "Do I have at least one of ___ ?" by executing a SELECT COUNT(*) against the table/view. This is a very inefficient way to answer that question. SELECT COUNT answers a different question: "How many do I have?" If you really only need to find out if you have N rows, use an explicit cursor and FETCH once. If you need to find out if you have more than one row, use an explicit cursor and FETCH N times. Use COUNT only when the actual number of occurrences is needed. Do not use the COUNT function to answer either of the following questions: Is there at least one row matching certain criteria? Is there more than one row matching certain criteria? Instead, use an explicit cursor inside a function. You should only use COUNT when you need to answer the question: "How many rows match a certain criteria?" Suppose that you have been asked to write a program that returns TRUE if there is at least one book in a given category. You could write it like this: <pre> CREATE OR REPLACE FUNCTION atleastone ( category_in IN book.category%TYPE) RETURN BOOLEAN IS numbooks INTEGER; BEGIN SELECT COUNT(*) INTO numbooks FROM book WHERE category = category_in; RETURN (numbooks > 0); END;</pre> But you are asking the RDBMS to do lots of unnecessary work. It might find, for instance, that there are 12 million books in the NON-FICTION category. A better approach is: <pre>CREATE OR REPLACE FUNCTION atleastone ( category_in IN book.category%TYPE) RETURN BOOLEAN IS retval BOOLEAN; CURSOR category_cur SELECT 1 FROM book WHERE category = category_in; BEGIN OPEN category_cur; FETCH category_cur INTO category_rec; retval := category_cur%FOUND; CLOSE category_cur; RETURN retval; END;</pre> In other words: all you have to do is see if there is a single row and you are done. With this practice, you get optimal performance out of your query and the readability of your code also improves, since it is a more accurate translation of the requirement.</TIP_TEXT><EXAMPLE_CODE>DECLARE CURSOR duplicatecur IS SELECT COUNT (*) -- should raise FROM ( SELECT rate_mun_no FROM tax_rate_import WHERE rate_mun_no IS NOT NULL HAVING COUNT (*) > 1 GROUP BY rate_mun_no); CURSOR duplicatecur IS SELECT COUNT (*), other_col FROM ( SELECT rate_mun_no FROM tax_rate_import WHERE rate_mun_no IS NOT NULL HAVING COUNT (*) > 1 GROUP BY rate_mun_no); CURSOR unmatchedcur IS SELECT COUNT (*) -- should raise FROM tax_rate_import WHERE rate_mun_no IS NULL; CURSOR unmatchedcur IS SELECT COUNT (*), other_col FROM tax_rate_import WHERE rate_mun_no IS NULL; BEGIN a := 22; END; / SELECT COUNT (z) -- should raise FROM mytab; SELECT a, b, c, COUNT (z) FROM mytab; SELECT COUNT FROM tab; -- no 5804, column "count" is used</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R5804</TIP_LINK><WIKIREV>r18</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:45:49</WIKITIMESTAMP></RULE><RULE><ID>5805</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid using DELETE or UPDATE without a WHERE clause.</DEF><RULE_TYPE>2</RULE_TYPE><CAT>58</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //(DELETE, UPDATE)/.[not(WHERE)] [not(ancestor::MERGE)]/TOKEN[1] </XPATH_QUERY><TIP_TEXT>It is perfectly valid to write and execute DELETE and UPDATE statements that do not have a WHERE clause. Such statements, however, have "global" impact on the referenced tables: all rows will be affected. You should examine such statements closely and confirm that they are written as required. Here's a perfectly "valid" UPDATE statement that could have unintended consequences: <pre>UPDATE items SET total_sale = 0;</pre> Perhaps the developer really did mean to set the total sales amount for every order to 0, but probably not.</TIP_TEXT><EXAMPLE_CODE>BEGIN DELETE -- should raise FROM DUAL; DELETE -- should raise FROM (SELECT * FROM Y); DELETE FROM DUAL WHERE dummy = 'Y'; DELETE FROM (SELECT * FROM Y) WHERE dummy = 'Y'; UPDATE tab -- should raise SET col2 = 3; UPDATE tab SET col2 = 3 WHERE col2 = 1; END; BEGIN DELETE FROM tab; -- should raise DELETE FROM tab WHERE a = b; UPDATE tab SET a = b; -- should raise UPDATE tab SET a = b WHERE c = d; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R5805</TIP_LINK><WIKIREV>r26</WIKIREV><WIKITIMESTAMP>27 Feb 2012 - 21:25:36</WIKITIMESTAMP></RULE><RULE><ID>5806</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Avoid using exclusive table locks.</DEF><RULE_TYPE>2</RULE_TYPE><CAT>58</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>While there is nothing intrinsically wrong with the use of LOCK TABLE statements, it's nonetheless still unusual to have to resort to locking an entire table with all of Oracle's advanced locking mechanisms.</TIP_TEXT><EXAMPLE_CODE>BEGIN LOCK TABLE tab IN EXCLUSIVE MODE; -- should raise END; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R5806</TIP_LINK><WIKIREV>r19</WIKIREV><WIKITIMESTAMP>19 Dec 2011 - 20:08:06</WIKITIMESTAMP></RULE><RULE><ID>5807</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid Cartesian queries; consider adding WHERE clause.</DEF><RULE_TYPE>2</RULE_TYPE><CAT>58</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//EXPR[@cat="query"][not(WHERE)]/TABLE_REF_LIST[count(TABLE_REF)>1]</XPATH_QUERY><TIP_TEXT>The query is a carthesian product of the data sources, which means that the number of retrieved rows will be as high as the product of the number of rows in each source. This is most often caused by either a WHERE missing clause entirely or not having enough WHERE clause join conditions for the given number of data sources in the FROM clause.</TIP_TEXT><EXAMPLE_CODE>BEGIN SELECT tab1.a, tab2.b INTO u, v FROM tab1, tab2; -- should raise SELECT z INTO u FROM (SELECT tab1.a, tab2.b FROM tab1, tab2) -- should raise WHERE a > b; SELECT tab1.z INTO u FROM (SELECT tab1.a, tab2.b -- should raise FROM tab1, tab2), -- should raise second_tab; END; / BEGIN SELECT x INTO y FROM a; SELECT x INTO y FROM a, b; -- should raise SELECT x INTO y FROM a, b, c; -- should raise SELECT x INTO y FROM a, b WHERE z; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R5807</TIP_LINK><WIKIREV>r23</WIKIREV><WIKITIMESTAMP>03 Oct 2011 - 02:18:24</WIKITIMESTAMP></RULE><RULE><ID>5808</ID></RULE><RULE><ID>5809</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Ensure elements in the SELECT list (either columns or expressions) are qualified by a table/view name.</DEF><RULE_TYPE>3</RULE_TYPE><CAT>58</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>You will improve readability and potentially query performance by qualifying each reference to a column with the object that owns that column. In addition, you will avoid undesired capture which may occur as a result of database schema evolution or migration.</TIP_TEXT><EXAMPLE_CODE>BEGIN SELECT a, -- should raise b, -- should raise c, -- should raise d -- should raise INTO u, v, w, x FROM tab1, tab2 WHERE a > 1; SELECT a, b, c, d INTO u, v, w, x FROM tab1 WHERE a > 1; SELECT t1.a, t2.b, t3.c, t3.d INTO u, v, w, x FROM t1, t2, t3 WHERE a > 1; SELECT a -- should raise INTO u FROM tab1, tab2 WHERE a > 1; SELECT a INTO u FROM y WHERE a > 1; SELECT a, f (), f (a + b) INTO u FROM y WHERE EXISTS (SELECT dummy FROM DUAL); END; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R5809</TIP_LINK><WIKIREV>r17</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:46:05</WIKITIMESTAMP></RULE><RULE><ID>5810</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Use synonyms in SQL table clauses rather than hard coded owner.table references.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>58</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //TABLE_REF[not(parent::SYNONYM)]/QNAME[count(IDENTIFIER) > 1] </XPATH_QUERY><TIP_TEXT>You should avoid explicitly qualifying references to database objects (including tables, views and type) with the name of the schema that owns the objects. Such a reference means that the code will fail to work if the schema name ever changes - a likely development. Instead, create synonyms to hide the schema name. This permits the table ownership to change without breaking the code. Note that there are performance reasons for choosing public synonyms over private synonyms when permissible in order to promote SGA SQL statement cache reuse and thus decreased parsing. Example: <pre>select * from syn_dept</pre> versus <pre>select * from scott.dept</pre></TIP_TEXT><EXAMPLE_CODE>-- assuming test is run connected to some user other than scott/tiger -- and that Oracle's demo account scott/tiger has already been created CONNECT scott/tiger DROP SEQUENCE dept_seq; CREATE SEQUENCE dept_seq START WITH 50 INCREMENT BY 1; DROP TABLE dept_summary; CREATE TABLE dept_summary ( deptno NUMBER (2) NOT NULL PRIMARY KEY, headcount NUMBER (4) NOT NULL CHECK (headcount > 0), totsalary NUMBER (9, 2) NOT NULL CHECK (totsalary > 0) ); CONNECT bert/bert DROP SYNONYM my_emp; DROP SYNONYM my_dept; DROP SYNONYM next_dept; DROP SYNONYM my_dsum; CREATE SYNONYM my_emp FOR scott.emp; CREATE SYNONYM my_dept FOR scott.dept; CREATE SYNONYM next_dept FOR scott.dept_seq; CREATE SYNONYM my_dsum FOR scott.dept_summary; -- everywhere -- scott.empemp owner.object_name -- scott.dept owner.object_name -- scott.dep_seq.NEXTVAL owner.sequence.NEXTVAL -- scott.dep_seq.CURRVAL owner.sequence.CURRVAL -- appears below is -- case for rule #5810 DECLARE x INT; CURSOR c1 IS SELECT COUNT (scott.emp.empno) FROM scott.emp, scott.dept -- should raise twice WHERE scott.dept.deptno = scott.emp.deptno -- should raise twice AND scott.dept.loc = 'Dallas'; -- should raise BEGIN LOCK TABLE scott.dept IN SHARE MODE; -- better written as LOCK TABLE my_dept IN SHARE MODE; SELECT COUNT (scott.emp.empno) -- should raise INTO x FROM scott.emp, scott.dept -- should raise twice WHERE scott.dept.deptno = scott.emp.deptno -- should raise twice AND scott.dept.loc = 'Dallas'; -- should raise -- better written as SELECT COUNT (e.empno) INTO x FROM my_emp e, my_dept d WHERE d.deptno = e.deptno AND d.loc = 'Dallas'; DELETE FROM scott.emp -- should raise WHERE scott.emp.empno = 99; -- should raise -- better written as DELETE FROM my_emp e WHERE e.empno = 99; UPDATE scott.dept -- should raise SET scott.dept.loc = 'Dallas' -- should raise WHERE scott.dept.deptno = 20; -- should raise -- better written as UPDATE my_dept d SET d.loc = 'Dallas' WHERE d.deptno = 20; INSERT INTO scott.dept VALUES (scott.dept_seq.NEXTVAL, 'xxx', 'yyy'); -- should raise twice -- better written as INSERT INTO my_dept VALUES (next_dept.NEXTVAL, 'xxx', 'yyy'); MERGE INTO scott.dept_summary D -- should raise USING ( SELECT deptno, COUNT (*) hc, SUM (sal) ts FROM scott.emp GROUP BY deptno) S -- should raise ON (D.deptno = S.deptno) WHEN MATCHED THEN UPDATE SET D.headcount = D.headcount + S.hc, D.totsalary = D.totsalary + S.ts WHEN NOT MATCHED THEN INSERT (D.deptno, D.headcount, D.totsalary) VALUES (S.deptno, S.hc, S.ts); -- better written as MERGE INTO my_dsum D USING ( SELECT deptno, COUNT (*) hc, SUM (sal) ts FROM my_emp GROUP BY deptno) S ON (D.deptno = S.deptno) WHEN MATCHED THEN UPDATE SET D.headcount = D.headcount + S.hc, D.totsalary = D.totsalary + S.ts WHEN NOT MATCHED THEN INSERT (D.deptno, D.headcount, D.totsalary) VALUES (S.deptno, S.hc, S.ts); END; / -- this is just a check to see if merge worked OK SELECT * FROM my_dsum; ROLLBACK;</EXAMPLE_CODE><DB_VERSION>None.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R5810</TIP_LINK><WIKIREV>r23</WIKIREV><WIKITIMESTAMP>31 Oct 2011 - 06:18:46</WIKITIMESTAMP></RULE><RULE><ID>5813</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid coding triggers with COMMIT or ROLLBACK statements unless used as autonomous transactions.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>58</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY>//(COMMIT, SAVEPOINT, ROLLBACK)/.[ancestor::TRIGGER_BODY[not(descendant::PRAGMA/@name="autonomous_transaction")]]</XPATH_QUERY><TIP_TEXT>Triggers cannot contain COMMIT or ROLLBACK unless they are defined as autonomous transactions. You will get a runtime error of ORA-04092: cannot COMMIT in a trigger. To define a trigger as an autonomous transaction, you will need to create a declaration section and insert the associated PRAGMA. You will then be required to perform a COMMIT or ROLLBACK on any exit from the trigger. For example: <pre>CREATE TRIGGER parts_trigger BEFORE INSERT ON parts FOR EACH ROW DECLARE PRAGMA AUTONOMOUS_TRANSACTION; BEGIN INSERT INTO parts_log VALUES(:new.pnum, :new.pname); COMMIT; -- allowed only in autonomous triggers EXCEPTION WHEN OTHERS THEN ROLLBACK; END; </pre></TIP_TEXT><EXAMPLE_CODE>CONNECT scott/tiger DROP TABLE log_emp_deletes; CREATE TABLE log_emp_deletes ( when_delete DATE NOT NULL, who_delete VARCHAR2 (32) NOT NULL, empno_delete NUMBER (4) NOT NULL, ename_delete VARCHAR2 (10) NOT NULL ); CREATE OR REPLACE TRIGGER trig_5813 BEFORE DELETE ON emp FOR EACH ROW DECLARE v_username VARCHAR2 (32); v_empno NUMBER (4); v_ename VARCHAR2 (10); -- if the following line is commented out, the trigger will -- compile just fine, but will yield the run-time error of: -- ORA-04092: cannot COMMIT in a trigger -- PRAGMA AUTONOMOUS_TRANSACTION; BEGIN SELECT USER INTO v_username FROM DUAL; v_empno := :old.empno; v_ename := :old.ename; INSERT INTO log_emp_deletes VALUES (SYSDATE, v_username, v_empno, v_ename); COMMIT; -- should raise END; / SHOW ERROR CREATE OR REPLACE TRIGGER trig_5813 BEFORE DELETE ON emp FOR EACH ROW DECLARE v_username VARCHAR2 (32); v_empno NUMBER (4); v_ename VARCHAR2 (10); -- if thhe following line is commented out, the trigger will -- compile just fine, but will yield the run-time error of: -- ORA-04092: cannot COMMIT in a trigger -- PRAGMA AUTONOMOUS_TRANSACTION; BEGIN SELECT USER INTO v_username FROM DUAL; v_empno := :old.empno; v_ename := :old.ename; INSERT INTO log_emp_deletes VALUES (SYSDATE, v_username, v_empno, v_ename); ROLLBACK; -- should raise END; / SHOW ERROR CREATE OR REPLACE TRIGGER trig_5813 BEFORE DELETE ON emp FOR EACH ROW DECLARE v_username VARCHAR2 (32); v_empno NUMBER (4); v_ename VARCHAR2 (10); PRAGMA AUTONOMOUS_TRANSACTION; BEGIN SELECT USER INTO v_username FROM DUAL; v_empno := :old.empno; v_ename := :old.ename; INSERT INTO log_emp_deletes VALUES (SYSDATE, v_username, v_empno, v_ename); COMMIT; END; / SHOW ERROR DECLARE BEGIN DELETE FROM emp; END; / -- verify that trigger fired SELECT * FROM log_emp_deletes; ROLLBACK;</EXAMPLE_CODE><DB_VERSION>None.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R5813</TIP_LINK><WIKIREV>r25</WIKIREV><WIKITIMESTAMP>27 Feb 2012 - 21:29:30</WIKITIMESTAMP></RULE><RULE><ID>5814</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Use output bind arguments in the RETURNING INTO clause of dynamic INSERT, UPDATE, or DELETE statements rather than the USING clause.</DEF><RULE_TYPE>3</RULE_TYPE><CAT>58</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//EXECUTE_IMMEDIATE/PARAM_LIST/PARAM[@out]</XPATH_QUERY><TIP_TEXT>When a dynamic INSERT, UPDATE, or DELETE statement has a RETURNING clause, output bind arguments can go in the RETURNING INTO clause or in the USING clause. You should use the RETURNING INTO clause for values returned from a DML operation. Reserve OUT and IN OUT bind variables for dynamic PL/SQL blocks that return values in PL/SQL variables. For example: <pre>DECLARE sql_stmt VARCHAR2(200); my_empno NUMBER(4) := 7902; my_ename VARCHAR2(10); my_job VARCHAR2(9); my_sal NUMBER(7,2) := 3250.00; BEGIN sql_stmt := 'UPDATE emp SET sal = :1 WHERE empno = :2 RETURNING ename, job INTO :3, :4'; /* OLD WAY: Bind returned values through USING clause. */ EXECUTE IMMEDIATE sql_stmt USING my_sal, my_empno, OUT my_ename, OUT my_job; /* NEW WAY: Bind returned values through RETURNING INTO clause. */ EXECUTE IMMEDIATE sql_stmt USING my_sal, my_empno RETURNING INTO my_ename, my_job; END; </pre></TIP_TEXT><EXAMPLE_CODE>CONNECT scott/tiger DECLARE sql_stmt VARCHAR2 (200); my_empno NUMBER (4) := 7902; my_ename VARCHAR2 (10); my_job VARCHAR2 (9); my_sal NUMBER (7, 2) := 3250.00; BEGIN sql_stmt := 'UPDATE emp SET sal = :1 WHERE empno = :2 RETURNING ename, job INTO :3, :4'; /* Bind returned values through USING clause. */ -- this should fire the rule EXECUTE IMMEDIATE sql_stmt USING my_sal, my_empno, OUT my_ename, -- should raise OUT my_job; -- should raise /* Bind returned values through RETURNING INTO clause. */ -- Oracle recommended syntax EXECUTE IMMEDIATE sql_stmt USING my_sal, my_empno RETURNING INTO my_ename, my_job; END; /</EXAMPLE_CODE><DB_VERSION>8i and greater.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R5814</TIP_LINK><WIKIREV>r22</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:46:16</WIKITIMESTAMP></RULE><RULE><ID>5816</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Use CASE construct rather than DECODE.</DEF><RULE_TYPE>3</RULE_TYPE><CAT>58</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY>//QNAME/IDENTIFIER [@value="decode"]</XPATH_QUERY><TIP_TEXT>Prior to Oracle8i, the only option for a CASE-like structure in PL/SQL was the DECODE function, and it was only accessible via a SQL statement within your PL/SQL code. Oracle8i allowed developers to use the more intuitive CASE statement inside SQL. Oracle9i allowed developers to use CASE within PL/SQL programs. Use CASE construct instead of DECODE (especially for nested conditional tests). It is more standard and easier to understand. Example: <pre> select decode (dummy, 'A', 1, 'B', 2, 'C', 3, 'D', 4, 'E', 5, 'F', 6, 7) into result from dual; </pre> is better written as: <pre> BEGIN RESULT := CASE dummy WHEN 'A' THEN 1 WHEN 'B' THEN 2 WHEN 'C' THEN 3 WHEN 'D' THEN 4 WHEN 'E' THEN 5 WHEN 'F' THEN 6 ELSE 7 END; END; </pre></TIP_TEXT><EXAMPLE_CODE>VARIABLE x NUMBER VARIABLE y NUMBER VARIABLE z NUMBER DECLARE xxx INT := 0; yyy INT := 0; zzz INT := 0; BEGIN -- simple SQL case SELECT DECODE (dummy, 'X', 1, 'Y', 0, -1) -- should raise INTO xxx FROM DUAL; -- embedded decodes SELECT DECODE ( -- should raise LENGTH (dummy), 1, DECODE (SUBSTR (dummy, 1, 1), 'X', 10, -- should raise 'Y', 0, -10), 2, DECODE (SUBSTR (dummy, 1, 2), 'XX', 20, -- should raise 'YY', 0, -20), 3, DECODE (SUBSTR (dummy, 1, 3), 'XXX', 30, -- should raise 'YYY', 0, -30), 4, DECODE (SUBSTR (dummy, 1, 4), 'XXXX', 40, -- should raise 'YYYY', 0, -40), -1) INTO yyy FROM DUAL; -- return values out to be printed :x := xxx; :y := yyy; :z := zzz; END; / PRINT x y z</EXAMPLE_CODE><DB_VERSION>CASE Statement introduced in 8i. None.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R5816</TIP_LINK><WIKIREV>r23</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:46:19</WIKITIMESTAMP></RULE><RULE><ID>5822</ID></RULE><RULE><ID>5823</ID></RULE><RULE><ID>5824</ID></RULE><RULE><ID>5825</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Consider replacing INSERT/UPDATE or UPDATE/INSERT pairs working on the same table by a MERGE.</DEF><RULE_TYPE>2</RULE_TYPE><CAT>58</CAT><SEVERITY>2</SEVERITY><XPATH_QUERY> //INSERT[descendant::TABLE_REF_LIST/TABLE_REF/QNAME/@value = //UPDATE/descendant::TABLE_REF_LIST/TABLE_REF/QNAME/@value]/TOKEN[1], //UPDATE[descendant::TABLE_REF_LIST/TABLE_REF/QNAME/@value = //INSERT/descendant::TABLE_REF_LIST/TABLE_REF/QNAME/@value]/TOKEN[1] </XPATH_QUERY><TIP_TEXT>Consider replacing INSERT/UPDATE or UPDATE/INSERT pairs working on the same table with a MERGE.</TIP_TEXT><EXAMPLE_CODE> --Before #1: -- BEGIN INSERT INTO t (mykey, mystuff) -- should raise VALUES ('X', 123); EXCEPTION WHEN DUP_VAL_ON_INDEX THEN UPDATE t SET mystuff = 123 WHERE mykey = 'X'; END; --Before #2: -- BEGIN UPDATE t -- should raise SET mystuff = 123 WHERE mykey = 'X'; EXCEPTION WHEN NO_DATA_FOUND THEN INSERT INTO t (mykey, mystuff) VALUES ('X', 123); END; --Before #3: -- BEGIN INSERT INTO t (mykey, mystuff) -- should raise VALUES ('X', 123); IF (SQL%ROWCOUNT = 0) THEN UPDATE t SET mystuff = 123 WHERE mykey = 'X'; END IF; END; --Before #4: -- BEGIN UPDATE t -- should raise SET mystuff = 123 WHERE mykey = 'X'; IF (SQL%ROWCOUNT = 0) THEN INSERT INTO t (mykey, mystuff) VALUES ('X', 123); END IF; END; --Before #5: -- BEGIN IF (NOT EXISTS (SELECT 1 FROM t WHERE mykey = 'X')) THEN INSERT INTO t (mykey, mystuff) -- should raise VALUES ('X', 123); ELSE UPDATE t SET mystuff = 123 WHERE mykey = 'X'; END IF; END; --Before #6: -- BEGIN IF (EXISTS (SELECT 1 FROM t WHERE mykey = 'X')) THEN UPDATE t -- should raise SET mystuff = 123 WHERE mykey = 'X'; ELSE INSERT INTO t (mykey, mystuff) VALUES ('X', 123); END IF; END; --Before #7: -- BEGIN SELECT COUNT (*) INTO cnt FROM t WHERE mykey = 'X'; IF (cnt = 0) THEN INSERT INTO t (mykey, mystuff) -- should raise VALUES ('X', 123); ELSE UPDATE t SET mystuff = 123 WHERE mykey = 'X'; END IF; END; --Before #8: -- BEGIN SELECT COUNT (*) INTO cnt FROM t WHERE mykey = 'X'; IF (cnt != 0) THEN UPDATE t -- should raise SET mystuff = 123 WHERE mykey = 'X'; ELSE INSERT INTO t (mykey, mystuff) VALUES ('X', 123); END IF; END; -- False BEGIN SELECT COUNT (*) INTO cnt FROM t WHERE mykey = 'X'; IF (cnt != 0) THEN UPDATE t SET mystuff = 123 WHERE mykey = 'X'; ELSE INSERT INTO other_t (mykey, mystuff) VALUES ('X', 123); END IF; END; -- True BEGIN BEGIN UPDATE t -- should raise SET mystuff = 123 WHERE mykey = 'X'; END; BEGIN INSERT INTO other_t (mykey, mystuff) VALUES ('X', 123); END; END; </EXAMPLE_CODE><DB_VERSION>Oracle >= 9i</DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r18</WIKIREV><WIKITIMESTAMP>23 Feb 2012 - 22:08:50</WIKITIMESTAMP></RULE><RULE><ID>5826</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid DISTINCT in a SELECT statement.</DEF><RULE_TYPE>2</RULE_TYPE><CAT>58</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //EXPR[@cat="query"]/SELECT_LIST[@mode="distinct"] </XPATH_QUERY><TIP_TEXT>Avoid DISTINCT in a SELECT statement.</TIP_TEXT><EXAMPLE_CODE>SELECT DISTINCT a, b, c FROM mytab; -- should raise SELECT a, b, c FROM mytab;</EXAMPLE_CODE><DB_VERSION>Oracle >= 9i</DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r11</WIKIREV><WIKITIMESTAMP>03 Mar 2012 - 21:47:24</WIKITIMESTAMP></RULE><RULE><ID>5827</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Consider using UNION ALL to eliminate a sort operation.</DEF><RULE_TYPE>2</RULE_TYPE><CAT>58</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //EXPR[@cat="union"]/TOKEN[@value="union"] </XPATH_QUERY><TIP_TEXT>Consider using UNION ALL to eliminate a sort operation.</TIP_TEXT><EXAMPLE_CODE> --Before #1: -- SELECT * FROM table_A WHERE col_1 = val_1 UNION -- should raise SELECT * FROM table_A WHERE col_1 = val_2; --After: -- SELECT * FROM table_A WHERE col_1 = val_1 UNION ALL SELECT * FROM table_A WHERE col_1 = val_2;</EXAMPLE_CODE><DB_VERSION>Oracle >= 9i</DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r14</WIKIREV><WIKITIMESTAMP>23 Jan 2012 - 22:03:46</WIKITIMESTAMP></RULE><RULE><ID>6001</ID></RULE><RULE><ID>6002</ID></RULE><RULE><ID>6003</ID></RULE><RULE><ID>6202</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Ensure row-level database trigger has a WHEN clause.</DEF><RULE_TYPE>2</RULE_TYPE><CAT>62</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //TRIGGER_BODY[not(TRIGGER_WHEN)][TRIGGER_TYPE[@value="row"]]/TOKEN[1] </XPATH_QUERY><TIP_TEXT>It is generally recommended that you use a WHEN clause on a database trigger whenever appropriate to avoid unnecessary executions of the trigger (that is, the value of a column was changed, but the new and old values are actually the same). Here is an example of a trigger that includes such logic: <pre> CREATE OR REPLACE TRIGGER check_raise AFTER UPDATE OF salary, commission ON employee FOR EACH ROW WHEN (NVL (OLD.salary, -1) != NVL (NEW.salary, -1) OR NVL (OLD.commission, -1) != NVL (NEW.commission, -1)) BEGIN ... </pre></TIP_TEXT><EXAMPLE_CODE>CREATE OR REPLACE TRIGGER salary_trigger1 BEFORE INSERT OR UPDATE OF salary ON payroll_emps FOR EACH ROW FOLLOWS salary_trigger ENABLE WHEN (new.salary < 150000) CALL check_sal (:new.salary) / CREATE OR REPLACE TRIGGER salary_trigger1 BEFORE INSERT OR UPDATE OF salary ON payroll_emps FOLLOWS salary_trigger ENABLE WHEN (new.salary < 150000) CALL check_sal (:new.salary) / CREATE OR REPLACE TRIGGER salary_trigger1 -- should raise BEFORE INSERT OR UPDATE OF salary ON payroll_emps FOR EACH ROW FOLLOWS salary_trigger ENABLE CALL check_sal (:new.salary) / CREATE TRIGGER scott.salary_check -- should raise BEFORE INSERT OR UPDATE OF sal, job ON scott.emp REFERENCING OLD AS OLD NEW AS NEW FOR EACH ROW -- WHEN (new.job <> 'PRESIDENT') BEGIN f; END; / CREATE TRIGGER scott.salary_check BEFORE INSERT OR UPDATE OF sal, job ON scott.emp REFERENCING OLD AS OLD NEW AS NEW FOR EACH ROW WHEN (new.job <> 'PRESIDENT') BEGIN f; END; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R6202</TIP_LINK><WIKIREV>r23</WIKIREV><WIKITIMESTAMP>23 Feb 2012 - 22:16:29</WIKITIMESTAMP></RULE><RULE><ID>6401</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Avoid multiple declarations of labels and variables.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>64</CAT><SEVERITY>2</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>The item has been declared multiple times, possibly one time as a label. Labels share the name space of variables. Labels and variables with the same name will cause conflicts. When you compile this code, you will very likely receive the PLS-00371 which states that at most, one declaration for 'string' is permitted in the declaration section. Cause: A reference to an identifier is ambiguous because there are conflicting declarations for it in the declarative part of a block, procedure, or function. At most, one declaration of the identifier is permitted in a declarative part. Action: Check the spelling of the identifier. If necessary, remove all but one declaration of the identifier.</TIP_TEXT><EXAMPLE_CODE>DECLARE a NUMBER (10, 2); a NUMBER (11, 2); -- should raise PROCEDURE proc (a IN NUMBER); PROCEDURE proc (a IN NUMBER, b IN NUMBER); -- overloaded PROCEDURE proc (a IN NUMBER); -- should raise BEGIN a := 1; proc (1, 1); proc (1); END; / > DECLARE a NUMBER (10, 2); BEGIN > BEGIN > BEGIN a := 1; -- should raise END b; END a; END lab; / DECLARE a NUMBER (10, 2); BEGIN > BEGIN a := 1; -- should raise END; END; / DECLARE a NUMBER; BEGIN BEGIN a := 1; BEGIN > -- no 6401 BEGIN a := 1; -- should raise END a; END; END; END; / > DECLARE na NUMBER (10, 2); FUNCTION lb (a IN NUMBER) RETURN NUMBER IS b NUMBER; BEGIN b := a; RETURN b; END lb; BEGIN > BEGIN > BEGIN na := 0; END b; END lb; na := lb (1); -- should raise END lab; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R6401</TIP_LINK><WIKIREV>r18</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:47:09</WIKITIMESTAMP></RULE><RULE><ID>6402</ID></RULE><RULE><ID>6404</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Use parameters or pull in definitions rather than referencing external variables in a local program unit.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>64</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>Local procedures and functions offer an excellent way to avoid code redundancy and make your code more readable (and thus more maintainable). Your local program makes a reference, however, to an external data structure, i.e., a variable that is declared outside of the local program. Thus, it is acting as a global variable inside the program. This external dependency is hidden, and may cause problems in the future. You should instead add a parameter to the parameter list of this program and pass the value through the list. This technique makes your program more reusable and avoids scoping problems, i.e. the program unit is less tied to particular variables in the program. In addition, unit encapsulation makes maintenance a lot easier and cheaper. You will find more information about the dangers of global variables below. Use package globals judiciously and only in package bodies. A global variable is a data structure that can be referenced outside of the scope or block in which it is declared. In the following block, for example, the l_publish_date is global to the local display_book_info procedure: <pre> DECLARE l_publish_date DATE; ... PROCEDURE display_book_info IS BEGIN DBMS_OUTPUT.PUT_LINE (l_publish_date); END; </pre> Globals are dangerous and to be avoided as they create hidden "dependencies" or side effects. A global does not have to be passed through the parameter list, so it is hard to even know that a global is referenced in a program without looking at the implementation. Globals are most often defined in packages. If a variable is declared at the package level (not within any specific program), that variable exists and retains its value for the duration of the session. The general solution to this problem is to pass the global as a parameter in the procedure and function; do not reference it directly within the program. Another general technique to keep in mind is to declare variables, cursors, functions, and other objects as deep as possible (i.e., in the block nearest to where, or within which, that object will be used), in order to reduce the chance of unintended use by other sections of the code. Reliance on global data structures can be a particularly acute problem in Oracle Developer's Formsbuilder (previously known as Oracle Forms). Developers have historically relied on :GLOBAL data structures to pass information between forms. In the latest versions of Oracle Developer, avoid :GLOBAL variables. Instead, build and share packages (and variables declared within those packages) among forms. Here is an example of a function with a hidden dependency on a global variable: <pre> CREATE OR REPLACE FUNCTION overdue_fine ( isbn_in IN book.isbn%TYPE) RETURN NUMBER IS l_days_overdue NUMBER; BEGIN l_days_overdue := overdue_pkg.days_overdue (isbn_in, SYSDATE); RETURN (l_days_overdue * overdue_pkg.g_daily_fine); END;</pre> The global is the amount of the daily fine. It is buried inside the function. By writing the function this way, two things happen: (a) there is no flexibility to pass in a different daily fine amount, as may be required, and (b) if the daily fine has not been set within the overdue package, the function doesn't work properly. The dependency can be eliminated by adding a parameter: <pre> CREATE OR REPLACE FUNCTION overdue_fine ( isbn_in IN book.isbn%TYPE, daily_fine_in IN NUMBER) RETURN NUMBER IS l_days_overdue NUMBER; BEGIN l_days_overdue := overdue_pkg.days_overdue (isbn_in, SYSDATE); RETURN (l_days_overdue * daily_fine_in); END;</pre> By reducing the inter-dependencies between programs, it is easier and safer make a change to one without worrying about all the others being affected. Of course, existing programs may need to be revamped to pull out global references and replace them with either parameters or calls to "get and set" programs that encapsulate the global data.</TIP_TEXT><EXAMPLE_CODE>DECLARE a VARCHAR2 (1) := 'X'; con_a CONSTANT VARCHAR2 (1) := 'X'; PROCEDURE p (n IN NUMBER) IS b VARCHAR2 (10); BEGIN a := n; -- should raise b := a; -- should raise b := con_a; -- constants are not flagged END p; BEGIN DBMS_OUTPUT.put_line (a); END; / CREATE OR REPLACE PROCEDURE p (a IN NUMBER) IS PROCEDURE p2 (n2 IN NUMBER) IS BEGIN a := n2; -- should raise END p2; BEGIN DBMS_OUTPUT.put_line (a); p2 (100); END p; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R6404</TIP_LINK><WIKIREV>r16</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:47:16</WIKITIMESTAMP></RULE><RULE><ID>6405</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Avoid defining variables that are not referenced inside the program.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>64</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>You have defined a variable, but it is not used inside the program. This can occur as the result of deprecations in code over time, but you should make sure that this situation does not reflect a problem. You should also remove the declaration to avoid maintenance errors in the future. You should go through your programs and remove any part of your code that is no longer used. This is a relatively straightforward process for variables and named constants. Simply execute searches for a variable's name in that variable's scope. If you find that the only place it appears is in its declaration, delete the declaration. There is never a better time to review all the steps you took, and to understand the reasons you took them, than immediately upon completion of your program. If you wait, you will find it particularly difficult to remember those parts of the program that were needed at one point, but were rendered unnecessary in the end. The following block of sample code has several dead zones that could cause a variety of problems. <pre> CREATE OR REPLACE PROCEDURE weekly_check ( isbn_in IN book.isbn%TYPE, author_in IN VARCHAR2) IS l_count PLS_INTEGER; l_counter PLS_INTEGER; l_available BOOLEAN; l_new_location PLS_INTEGER := 1056; l_published_date DATE := SYSDATE; BEGIN l_published_date := te_book.published_date (isbn_in); IF ADD_MONTHS (SYSDATE, -60) > l_published_date THEN review_usage; ELSIF ADD_MONTHS (SYSDATE, -24) > l_published_date THEN check_availability (isbn_in, l_available, l_count); IF l_available AND /* Turn off due to Req A12.6 */ FALSE THEN transfer_book (isbn_in, l_count - 1, l_new_location); END IF; -- Check for reserves -- reserve_pkg.analyze_requests (isbn_in); END IF; END;</pre> The sample above contains the following examples of dead code: The author_in parameter is declared but never used. It doesn't even have a default value, so you have to pass in an ignored value. l_counter is declared but not used. l_published_date is assigned a default value of SYSDATE, which is immediately overridden by the call to te_book.published_date. The call to transfer_book has been turned off with the addition of AND FALSE. The call to reserve_pkg.analyze_requests has been commented out. Note: It is much easier to maintain, debug and enhance code that does not have "dead zones". There are sometimes valid reasons for keeping "dead code" in place. You may sometimes want to "turn off" code temporarily. Also, you may need to comment out some logic, but still show that this action was done and why. In such cases, make sure that you include the necessary documentation in the code. Even better, use problem tracking or bug reporting software to keep a comprehensive history of any changes made to code.</TIP_TEXT><EXAMPLE_CODE>DECLARE a NUMBER (6); -- should raise b NUMBER (4); BEGIN b := 1; END; / DECLARE a NUMBER (6); -- should raise b NUMBER (4); BEGIN b := 1; DECLARE a VARCHAR2 (6); BEGIN a := 'abc'; END; END; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R6405</TIP_LINK><WIKIREV>r23</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:47:19</WIKITIMESTAMP></RULE><RULE><ID>6406</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Avoid multiple definitions of the same element in different scopes.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>64</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>PL/SQL allows you to declare variables with the same names in nested blocks of code. While the PL/SQL compiler will be able to tell these structures apart, a person reading and maintaining the code could be easily confused. You should use distinct names throughout the scope of a block of code (including blocks nested within that outer-most block). This section often arises when you declare a variable for the index of a FOR loop (an integer or record, depending on the type of loop). You should avoid such an action, for reasons described below. Never declare the FOR loop index. PL/SQL offers two kinds of FOR loops: numeric and cursor. Both have this general format: <pre>FOR <loop index> IN <loop range> LOOP &lt;loop body&gt; END LOOP;</pre> The &lt;loop index&gt; is either an integer or a record; in either case, it is implicitly declared by the PL/SQL runtime engine. The scope of the loop index variable is restricted to the body of the loop (between the LOOP and END LOOP statements). You should never declare a variable for the loop. If you do declare the loop index variable, you are actually declaring a completely separate (record type or numeric) variable that will (best case) never be used or (worst case) used outside the loop in a way that will be confusing and likely to introduce errors. In the following example, the procedure is designed to delete books from the collection by title: <pre> CREATE OR REPLACE PROCEDURE remove_titles ( title_in IN book.title%TYPE, ) IS CURSOR book_cur IS SELECT isbn, author FROM book WHERE title LIKE title_in; book_rec book_cur%ROWTYPE; BEGIN FOR book_rec IN book_cur LOOP te_book.rem (book_rec.isbn); END LOOP; END; The code works fine. Now assume that the following code is added after the FOR loop to display the last book removed: END LOOP; pl (book_rec.isbn || ' - ' || book_rec.author); END;</pre> The code compiles, but the last book information keeps coming up NULL. This is because the the original code was faulty. The declaration of book_rec was unnecessary and made the error possible. By avoiding unnecessary code, you make it less likely for programmers to introduce errors into the code at some later point.</TIP_TEXT><EXAMPLE_CODE>> BEGIN > DECLARE c NUMBER (3); CURSOR cur (c NUMBER) -- should raise IS SELECT * FROM DUAL; BEGIN c := 2; END; END a; /</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R6406</TIP_LINK><WIKIREV>r18</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:47:23</WIKITIMESTAMP></RULE><RULE><ID>6407</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Ensure item is defined in as deep a scope as possible.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>64</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>You have declared one or more data structures in the declaration section of one block, but you only use that variable inside a nested block of that outer block. It is a good practice to declare variables, cursors, functions and other objects as deep as possible, in order to reduce the chance of unintended use (and, usually, the introduction of errors) by other sections of the code.</TIP_TEXT><EXAMPLE_CODE>DECLARE z NUMBER; -- should raise BEGIN BEGIN z := 1; END; END; PROCEDURE p IS z NUMBER; -- should raise PROCEDURE p1 IS z NUMBER; -- should raise BEGIN BEGIN z := 1; END; END; PROCEDURE p2 IS z NUMBER; BEGIN BEGIN z := 1; END; END; BEGIN BEGIN z := 1; END; END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R6407</TIP_LINK><WIKIREV>r16</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:47:26</WIKITIMESTAMP></RULE><RULE><ID>6411</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid using NOT NULL variable constraints.</DEF><RULE_TYPE>2</RULE_TYPE><CAT>64</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY>//CONSTRAINT [(ancestor::VAR_DECL, ancestor::TYPE_SPEC)] [@type="not_null"]</XPATH_QUERY><TIP_TEXT>When variables are constrained by NOT NULL, the value of the expressions assigned are first assigned to temporary variables, which are then tested for nullity. If the variables are not null, the value is assigned successfully. Otherwise, an exception is raised. You can avoid the overhead (small as it may be) of the temporary variables by performing the check for nullity yourself. Example: <pre>PROCEDURE calc_m IS m NUMBER NOT NULL := 0; a NUMBER; b NUMBER; BEGIN ... m := a + b; ... END;</pre> is better written as: <pre>PROCEDURE calc_m IS m NUMBER; -- no constraint a NUMBER; b NUMBER; BEGIN ... m := a + b; IF m IS NULL THEN -- enforce constraint programmatically ... END IF; END;</pre></TIP_TEXT><EXAMPLE_CODE>DECLARE a NUMBER NOT NULL := 3; -- should raise b NUMBER; BEGIN NULL; END; -- should not flag: CREATE TABLE a (b NUMBER (1) NOT NULL); CREATE OR REPLACE PACKAGE pack_6411 AS SUBTYPE intnotnull IS INT NOT NULL; -- should raise FUNCTION func_6411 (aaa IN intnotnull := 1, bbb IN intnotnull := 2) RETURN INT; PROCEDURE proc_6411 (aaa IN intnotnull := 1, bbb IN intnotnull := 2, ccc IN OUT INT); END; / SHOW ERROR DECLARE -- subtypes SUBTYPE aaa IS INT NOT NULL; -- should raise bbb aaa := 100; -- types TYPE ccc IS VARRAY (2) OF INT NOT NULL; -- should raise ddd ccc; -- record TYPE eee IS RECORD ( e1 INT NOT NULL:= 100, -- should raise e2 INT NOT NULL:= 200, -- should raise e3 INT NOT NULL:= 300 -- should raise ); fff eee; -- Explicit variable declarations xxx INT NOT NULL := 100; -- should raise yyy NATURALN := 100; zzz POSITIVEN := 100; -- explicit cursor BEGIN ddd := ccc (100, 200); yyy := pack_6411.func_6411 (xxx, yyy); pack_6411.proc_6411 (xxx, yyy, zzz); zzz := 100; END; /</EXAMPLE_CODE><DB_VERSION>None.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R6411</TIP_LINK><WIKIREV>r23</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:47:30</WIKITIMESTAMP></RULE><RULE><ID>6413</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid using quoted identifiers for variable names.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>64</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //QNAME/IDENTIFIER[starts-with(@value, '"')] </XPATH_QUERY><TIP_TEXT>Your code is using a quoted identifier. Although Oracle permits declaring non-standard variable names by enclosing them in double quotes, such as: <pre>DECLARE "x+y" INTEGER; </pre> quoted identifiers should be avoided because they make your code harder to read and maintain. You should strive to use standard identifier names that clearly define their purpose and usage.</TIP_TEXT><EXAMPLE_CODE>CREATE OR REPLACE FUNCTION func_6413 ("x+y" IN INT := 1) RETURN INT AS "a+b" INT; -- should raise BEGIN "a+b" := -- should raise "x+y" * 2; -- should raise RETURN "a+b"; END; / SHOW ERROR CREATE OR REPLACE PROCEDURE proc_6413 ("x+y" IN OUT INT) AS "a+b" INT := 100; -- should raise BEGIN "x+y" := -- should raise "x+y" + -- should raise "a+b"; -- should raise END; / SHOW ERROR DECLARE x INT := 100; y INT := 100; "x+y" INT := 100; -- should raise BEGIN x := func_6413 (x); proc_6413 (y); "x+y" := 100; -- should raise END; /</EXAMPLE_CODE><DB_VERSION>None.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R6413</TIP_LINK><WIKIREV>r25</WIKIREV><WIKITIMESTAMP>18 Jan 2012 - 10:30:17</WIKITIMESTAMP></RULE><RULE><ID>6414</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid using overly short names for declared or implicitly declared identifiers.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>64</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //QNAME/IDENTIFIER [last()] [../../name()=("LOOP_INDEX_DECL", "VAR_DECL", "CONST_DECL", "CURSOR", "CURSOR_SPEC", "PARAM_SPEC", "ATTRIBUTE_SPEC", "TYPE_SPEC", "EXCEPTION_DECL")] [string-length(@value) < 4] </XPATH_QUERY><TIP_TEXT>The variable that you've declared has an overly short name (less than 4 characters). You should ensure that the name you've chosen well defines its purpose and usage. While you can save a few keystrokes typing very short names, the resulting code is obscure and hard for anyone besides the author to understand. For example, instead of just using 'i' ... <pre>DECLARE i PLS_INTEGER; BEGIN</pre> Consider using something like this instead: <pre>DECLARE l_index PLS_INTEGER; BEGIN</pre> The same rule applies to implicitly declared variables. Instead of this: <pre>FOR r IN c LOOP ... END LOOP;</pre> Consider the following which is clearer to the reader: <pre>FOR emp_rec IN all_employees_cur LOOP ... END LOOP;</pre></TIP_TEXT><EXAMPLE_CODE>CREATE OR REPLACE FUNCTION func_6414 (aaa IN INT := 1, bbb IN INT := 2) RETURN INT AS xxx INT; yyy INT := 100; BEGIN xxx := aaa + bbb + yyy; RETURN xxx; END; / SHOW ERROR CREATE OR REPLACE PROCEDURE proc_6414 (aaa IN INT := 1, bbb IN INT := 2, ccc OUT INT) AS xxx INT; yyy INT := 100; BEGIN ccc := aaa + bbb + yyy; END; / SHOW ERROR DECLARE -- subtypes SUBTYPE aaa IS INT; bbb aaa; -- types TYPE ccc IS VARRAY (100) OF INT; ddd ccc; -- record -- note: record name is short and so are record sub-component names -- thus this example has 4 conditions which should fire rule! TYPE eee IS RECORD ( e1 INT, e2 INT, e3 INT ); fff eee; TYPE ggg IS RECORD ( g1 INT, g2 INT, g3 INT ); hhh ggg; -- Explicit variable declarations xxx INT := 100; yyy INT := 100; zzz INT := 100; -- explicit cursor CURSOR c1 IS SELECT * FROM DUAL; BEGIN yyy := func_6414 (xxx, yyy); proc_6414 (xxx, yyy, zzz); zzz := 100; -- Implicit variable declarations FOR i IN 1 .. 100 LOOP FOR j IN 1 .. 100 LOOP NULL; END LOOP; END LOOP; FOR rec IN (SELECT * FROM DUAL) LOOP NULL; END LOOP; END; /</EXAMPLE_CODE><DB_VERSION>None.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R6414</TIP_LINK><WIKIREV>r30</WIKIREV><WIKITIMESTAMP>10 Mar 2012 - 22:03:38</WIKITIMESTAMP></RULE><RULE><ID>6415</ID><STATUS>5</STATUS><COLOR>Yellow</COLOR><DEF>Consider moving assignments of default values for variables to an initialization section.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>Consider moving assignments of default values for variables to an initialization section.</TIP_TEXT><EXAMPLE_CODE>DECLARE name VARCHAR2 (100); BEGIN name := 'Paul'; -- should raise proc_call (name); END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r19</WIKIREV><WIKITIMESTAMP>19 Dec 2011 - 19:58:23</WIKITIMESTAMP></RULE><RULE><ID>6416</ID></RULE><RULE><ID>6801</ID><STATUS>5</STATUS><COLOR>Green</COLOR><DEF>Ensure all statements can be reached (dead code).</DEF><RULE_TYPE>0</RULE_TYPE><CAT>68</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>This branch will not be taken because the prior condition will never evaluate to TRUE, or because this code follows a GOTO. This is one example of a "dead zone", an area of code that is never run - and is detectable independent of the conditions under which the code is run. You should remove all dead zones from your code. In a database trigger, INSERTING, UPDATING or DELETING may have been used while not compatible with the type of the trigger. You should go through your programs and remove any part of your code that is no longer used. This is a relatively straightforward process for variables and named constants. Simply execute searches for a variable's name in that variable's scope. If you find that the only place it appears is in its declaration, delete the declaration. There is never a better time to review all the steps you took, and to understand the reasons you took them, than immediately upon completion of your program. If you wait, you will find it particularly difficult to remember those parts of the program that were needed at one point, but were rendered unnecessary in the end. The following block of sample code has several dead zones that could cause a variety of problems. <pre> CREATE OR REPLACE PROCEDURE weekly_check ( isbn_in IN book.isbn%TYPE, author_in IN VARCHAR2) IS l_count PLS_INTEGER; l_counter PLS_INTEGER; l_available BOOLEAN; l_new_location PLS_INTEGER := 1056; l_published_date DATE := SYSDATE; BEGIN l_published_date := te_book.published_date (isbn_in); IF ADD_MONTHS (SYSDATE, -60) > l_published_date THEN review_usage; ELSIF ADD_MONTHS (SYSDATE, -24) > l_published_date THEN check_availability (isbn_in, l_available, l_count); IF l_available AND /* Turn off due to Req A12.6 */ FALSE THEN transfer_book (isbn_in, l_count - 1, l_new_location); END IF; -- Check for reserves -- reserve_pkg.analyze_requests (isbn_in); END IF; END; </pre> The sample above contains the following examples of dead code: The author_in parameter is declared but never used. It doesn't even have a default value, so you have to pass in an ignored value. l_counter is declared but not used. l_published_date is assigned a default value of SYSDATE, which is immediately overridden by the call to te_book.published_date. The call to transfer_book has been turned off with the addition of AND FALSE. The call to reserve_pkg.analyze_requests has been commented out. Note: It is much easier to maintain, debug and enhance code that does not have "dead zones". There are sometimes valid reasons for keeping "dead code" in place. You may sometimes want to "turn off" code temporarily. Also, you may need to comment out some logic, but still show that this action was done and why. In such cases, make sure that you include the necessary documentation in the code. Even better, use problem tracking or bug reporting software to keep a comprehensive history of any changes made to code.</TIP_TEXT><EXAMPLE_CODE>BEGIN y := CASE 2 WHEN 1 THEN 3 -- should raise WHEN 2 THEN a2 + f (a4) - 1 WHEN 3 THEN a3 -- should raise ELSE b - c -- should raise END; END; BEGIN a; GOTO z; b; -- should raise c; -- should raise > e; RETURN; f; -- should raise g; -- should raise LOOP -- should raise EXIT; -- should raise loop1; -- should raise loop2; -- should raise END LOOP; h; -- should raise i; -- should raise END;</EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R6801</TIP_LINK><WIKIREV>r23</WIKIREV><WIKITIMESTAMP>17 Sep 2011 - 01:47:48</WIKITIMESTAMP></RULE><RULE><ID>6802</ID><STATUS>5</STATUS><COLOR>Yellow</COLOR><DEF>Avoid coding program units too long.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>The executable section for this program exceeds the recommended limit of 60 lines. You should keep your executable sections small, so that they can be easily read, understood and maintained. You are generally better creating larger numbers of small programs that interact with each other through clearly defined interfaces (parameter lists) than creating a few very large programs. This is often referred to as increasing cohesion (code does one thing) and decreasing coupling (too many dependencies of various types within the same code). Examine the program unit to determine if it can be divided into smaller, more manageable units.</TIP_TEXT><EXAMPLE_CODE></EXAMPLE_CODE><DB_VERSION>None.</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R6802</TIP_LINK><WIKIREV>r19</WIKIREV><WIKITIMESTAMP>19 Dec 2011 - 19:40:50</WIKITIMESTAMP></RULE><RULE><ID>6806</ID><STATUS>5</STATUS><COLOR>Yellow</COLOR><DEF>Use native execution for PL/SQL that is compute intensive and has little SQL.</DEF><RULE_TYPE>2</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>Use native execution for PL/SQL that is compute intensive and has little SQL.</TIP_TEXT><EXAMPLE_CODE>-- the procedure and function have no DML CREATE OR REPLACE FUNCTION func_6414 (aaa IN INT := 1, bbb IN INT := 2) RETURN INT AS xxx INT; yyy INT := 100; BEGIN xxx := aaa + bbb + yyy; RETURN xxx; END; / SHOW ERROR CREATE OR REPLACE PROCEDURE proc_6414 (aaa IN INT := 1, bbb IN INT := 2, ccc OUT INT) AS xxx INT; yyy INT := 100; BEGIN ccc := aaa + bbb + yyy; END; / SHOW ERROR DECLARE xxx INT := 100; yyy INT := 100; BEGIN yyy := func_6806 (xxx, yyy); proc_6806 (xxx, yyy, zzz); END; /</EXAMPLE_CODE><DB_VERSION>None.</DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r22</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 04:38:04</WIKITIMESTAMP></RULE><RULE><ID>6807</ID></RULE><RULE><ID>6808</ID><STATUS>5</STATUS><COLOR>Yellow</COLOR><DEF>Avoid evaluating an expression in an EXECUTE IMMEDIATE statement.</DEF><RULE_TYPE>2</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY></XPATH_QUERY><TIP_TEXT>Avoid evaluating an expression in an EXECUTE IMMEDIATE statement; instead pass a pre-set variable.</TIP_TEXT><EXAMPLE_CODE></EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r19</WIKIREV><WIKITIMESTAMP>19 Dec 2011 - 19:58:32</WIKITIMESTAMP></RULE><RULE><ID>6809</ID></RULE><RULE><ID>6004</ID></RULE><RULE><ID>6005</ID></RULE><RULE><ID>6006</ID></RULE><RULE><ID>6007</ID></RULE><RULE><ID>6008</ID></RULE><RULE><ID>6009</ID></RULE><RULE><ID>6010</ID></RULE><RULE><ID>6011</ID></RULE><RULE><ID>6012</ID></RULE><RULE><ID>6013</ID></RULE><RULE><ID>6014</ID></RULE><RULE><ID>6015</ID></RULE><RULE><ID>6016</ID></RULE><RULE><ID>6017</ID></RULE><RULE><ID>6018</ID></RULE><RULE><ID>6019</ID></RULE><RULE><ID>6020</ID></RULE><RULE><ID>6021</ID></RULE><RULE><ID>6022</ID></RULE><RULE><ID>6023</ID></RULE><RULE><ID>6024</ID></RULE><RULE><ID>6025</ID></RULE><RULE><ID>6026</ID></RULE><RULE><ID>6027</ID></RULE><RULE><ID>6028</ID></RULE><RULE><ID>6029</ID></RULE><RULE><ID>6030</ID></RULE><RULE><ID>6031</ID></RULE><RULE><ID>6032</ID></RULE><RULE><ID>6033</ID></RULE><RULE><ID>6034</ID></RULE><RULE><ID>6035</ID></RULE><RULE><ID>6036</ID></RULE><RULE><ID>6037</ID></RULE><RULE><ID>6038</ID></RULE><RULE><ID>6039</ID></RULE><RULE><ID>6040</ID></RULE><RULE><ID>6041</ID></RULE><RULE><ID>6042</ID></RULE><RULE><ID>6043</ID></RULE><RULE><ID>6044</ID></RULE><RULE><ID>6045</ID></RULE><RULE><ID>6046</ID></RULE><RULE><ID>6047</ID></RULE><RULE><ID>6048</ID></RULE><RULE><ID>6049</ID></RULE><RULE><ID>6050</ID></RULE><RULE><ID>6051</ID></RULE><RULE><ID>6052</ID></RULE><RULE><ID>6053</ID></RULE><RULE><ID>5901</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>FND_GLOBAL.apps_initialize string found within code. Please use xxccs_fea_utl_session.apps_initialize() instead.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //CALL/QNAME[IDENTIFIER[1]/@value="fnd_global"][IDENTIFIER[2]/@value="apps_initialize"] </XPATH_QUERY><TIP_TEXT>FND_GLOBAL.apps_initialize string found within code. Please use xxccs_fea_utl_session.apps_initialize() instead.</TIP_TEXT><EXAMPLE_CODE> begin FND_GLOBAL.apps_initialize; -- should raise end; </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r14</WIKIREV><WIKITIMESTAMP>24 Jan 2012 - 19:47:35</WIKITIMESTAMP></RULE><RULE><ID>5902</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Any use of UTL_MAIL should be replaced with FEA Mail Utility service.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //CALL/QNAME[IDENTIFIER[last()]/preceding-sibling::*[1]/@value="utl_mail"][IDENTIFIER[last()]] </XPATH_QUERY><TIP_TEXT>Any use of UTL_MAIL should be replaced with FEA Mail Utility service.</TIP_TEXT><EXAMPLE_CODE> begin UTL_MAIL.some_function; -- should raise end; </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r16</WIKIREV><WIKITIMESTAMP>24 Jan 2012 - 20:35:58</WIKITIMESTAMP></RULE><RULE><ID>5903</ID></RULE><RULE><ID>5904</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Catch all Exception block (OTHERS) has a code block that consists of RAISE only.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //EXCEPTION_BLOCK/EXCEPTION [not(QNAME)] [count(*) = 1] [RAISE] </XPATH_QUERY><TIP_TEXT>Catch all Exception block (OTHERS) has a code block that consists of RAISE only. Resulting in loss of actual location of exception/error. Creates unusable errors within production code.</TIP_TEXT><EXAMPLE_CODE> BEGIN my_func; EXCEPTION WHEN NO_DATA_FOUND THEN RAISE x; WHEN OTHERS -- should raise THEN RAISE y; END; </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r13</WIKIREV><WIKITIMESTAMP>23 Jan 2012 - 21:00:34</WIKITIMESTAMP></RULE><RULE><ID>5905</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Any code reference to set_log_context API of Session Utility is NOT allowed in production code.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //CALL/QNAME[IDENTIFIER[1]/@value="DBMS_SESSION"][IDENTIFIER[2]/@value="SET_LOG_CONTEXT"] </XPATH_QUERY><TIP_TEXT>Any code reference to set_log_context API of Session Utility is NOT allowed in production code as it will result in hard coding of log levels which can result in all applications in full debug mode thereby causing performance and system resource issues.</TIP_TEXT><EXAMPLE_CODE> begin DBMS_SESSION.SET_LOG_CONTEXT; -- should raise end; </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r14</WIKIREV><WIKITIMESTAMP>24 Jan 2012 - 20:01:41</WIKITIMESTAMP></RULE><RULE><ID>5906</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Found string containing "@cisco.com". All email addresses should be stored in a table, profile options, or some other configurable data store and retrieved.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>46</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //LITERAL[contains(@value, "@cisco.com")] </XPATH_QUERY><TIP_TEXT>Found string containing "@cisco.com". All email addresses should be stored elsewhere.</TIP_TEXT><EXAMPLE_CODE> DECLARE my_email VARCHAR2 (100) := 'me@cisco.com'; -- should raise std_email CONSTANT VARCHAR2 (100) := 'info@cisco.com'; -- should raise BEGIN var1 := 'he@cisco.com'; -- should raise SELECT 'they@cisco.com' INTO var2 FROM DUAL; -- should raise END; </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r13</WIKIREV><WIKITIMESTAMP>23 Jan 2012 - 21:03:11</WIKITIMESTAMP></RULE><RULE><ID>5907</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Database link found. All database link references should be done via synonyms.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>58</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //DB_LINK </XPATH_QUERY><TIP_TEXT>Any code element containing an "@" symbol implies database link. All database link references should be done via synonyms instead of directly within all code.</TIP_TEXT><EXAMPLE_CODE> SELECT col, col2 FROM customers@main.cisco.com; </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r13</WIKIREV><WIKITIMESTAMP>23 Jan 2012 - 21:04:14</WIKITIMESTAMP></RULE><RULE><ID>5908</ID></RULE><RULE><ID>5909</ID></RULE><RULE><ID>5910</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Parameters should start with P_, end with _i or _o or _io, and if _o then NOCOPY hint is required.</DEF><RULE_TYPE>3</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //PARAM_SPEC[not(starts-with(QNAME/@value, "p_"))], //PARAM_SPEC[(@in and not(@out) and not(ends-with(QNAME/@value, "_i"))) or (@out and not(@in) and not(ends-with(QNAME/@value, "_o"))) or (@out and @in and not(ends-with(QNAME/@value, "_io")))], //PARAM_SPEC[@out and not(@in) and not(@nocopy)] </XPATH_QUERY><TIP_TEXT>Parameters should start with P_, end with _i or _o or _io, and if _o then NOCOPY hint is required.</TIP_TEXT><EXAMPLE_CODE> PROCEDURE proc1 (a_bad NUMBER, -- should raise b_bad IN NUMBER, -- should raise c_bad OUT NUMBER, -- should raise d_bad IN OUT NUMBER, -- should raise p_a_bad NUMBER, -- should raise p_a_i NUMBER, p_b_i IN NUMBER, p_c_bad_o OUT NUMBER, -- should raise p_c_o OUT NOCOPY NUMBER, p_d_io IN OUT NUMBER) IS BEGIN NULL; END; </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r16</WIKIREV><WIKITIMESTAMP>24 Jan 2012 - 09:13:41</WIKITIMESTAMP></RULE><RULE><ID>5911</ID></RULE><RULE><ID>5912</ID></RULE><RULE><ID>5913</ID></RULE><RULE><ID>5914</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Use untransformed column values in the WHERE clause.</DEF><RULE_TYPE>2</RULE_TYPE><CAT>58</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //WHERE[descendant::PARAM_LIST] </XPATH_QUERY><TIP_TEXT>Use untransformed column values in the WHERE clause. For example, use <pre>WHERE ooh.order_no = ool.order_no</pre> rather than <pre>WHERE TO_NUMBER (substr(ooh.order_no, 1,5)) = TO_NUMBER (substr(ool.order_no,1,5))</pre></TIP_TEXT><EXAMPLE_CODE> SELECT * FROM ooh WHERE ooh.order_no = ool.order_no UNION SELECT * FROM ooh WHERE ooh.order_no = ool.order_no / SELECT * FROM ooh WHERE ooh.order_no = ool.order_no UNION SELECT * FROM ooh WHERE TO_NUMBER (SUBSTR (ooh.order_no, 1, 5)) = TO_NUMBER (SUBSTR (ool.order_no, 1, 5)) -- should raise / </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r14</WIKIREV><WIKITIMESTAMP>04 Mar 2012 - 19:52:26</WIKITIMESTAMP></RULE><RULE><ID>5915</ID></RULE><RULE><ID>5916</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Beware of and avoid implicit data type conversions.</DEF><RULE_TYPE>3</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //QNAME [PARAM_LIST[count(PARAM) < 2]] [IDENTIFIER[@value="TO_DATE"]] </XPATH_QUERY><TIP_TEXT>Beware of and avoid implicit data type conversions. PLSQL will allow you to write code like <br />my_birth_date DATE := '30-OCT-71'; In this case runtime engine automatically converts the string to a date using default format mask. However you should avoid this implicit conversion. There are at least 2 BIG problems in relying on PLSQL convert. See SEF http://toadworld.com/Blogs/tabid/67/EntryId/840/The-Date-Literal.aspx for a discussion around DATE.</TIP_TEXT><EXAMPLE_CODE> DECLARE l_date DATE; BEGIN l_date := '15-FEB-2011'; -- should flag l_date := TO_DATE ('15-FEB-2011'); -- should flag l_date := TO_DATE ('FEB-15-2011', 'MON-DD-YYYY'); l_date := DATE '2011-02-15'; l_date := TO_DATE ('FEB-15-2011 13:14:15' , 'MON-DD-YYYY HH24:MI:SS'); END; </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r11</WIKIREV><WIKITIMESTAMP>23 Mar 2012 - 08:19:20</WIKITIMESTAMP></RULE><RULE><ID>5917</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Do not use 'Insert into ... select * ...' statements.</DEF><RULE_TYPE>4</RULE_TYPE><CAT>58</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //INSERT/descendant::EXPR[@cat="query"] </XPATH_QUERY><TIP_TEXT>Do not use 'Insert into ... select * ...' statements.</TIP_TEXT><EXAMPLE_CODE> INSERT INTO customers SELECT name, address, age FROM old_customers; </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r13</WIKIREV><WIKITIMESTAMP>23 Feb 2012 - 22:15:12</WIKITIMESTAMP></RULE><RULE><ID>5918</ID></RULE><RULE><ID>5919</ID></RULE><RULE><ID>5920</ID></RULE><RULE><ID>5921</ID></RULE><RULE><ID>5922</ID></RULE><RULE><ID>2101</ID></RULE><RULE><ID>2102</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid comments that are not explanatory within the body of the code.</DEF><RULE_TYPE>3</RULE_TYPE><CAT>68</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //COMMENT[matches(., ":=")] </XPATH_QUERY><TIP_TEXT>Avoid comments that are not explanatory within the body of the code. Base this on the number of lines within the comment and look for things such as commented out code.</TIP_TEXT><EXAMPLE_CODE>CREATE PROCEDURE proc1 /* This procedure contains comments that are not relevant to what it is actually doing. */ IS BEGIN a := 1; b := 2; /* -- should hit here c := 3; d := 4; */ e := 5; -- f := 6; -- should hit here END; </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r18</WIKIREV><WIKITIMESTAMP>29 Mar 2012 - 19:00:19</WIKITIMESTAMP></RULE><RULE><ID>2103</ID></RULE><RULE><ID>2104</ID></RULE><RULE><ID>2105</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid "SELECT ... FROM DUAL" when PL/SQL equivalent exists.</DEF><RULE_TYPE>2</RULE_TYPE><CAT>58</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //EXPR[@cat="query"][TABLE_REF_LIST/TABLE_REF/QNAME/@value="dual"] /SELECT_LIST/SELECT_ITEM/descendant::QNAME[not(parent::ALIAS)]/IDENTIFIER[last()][@value=("NEXTVAL", "CURRVAL")] </XPATH_QUERY><TIP_TEXT>The pseudocolumns CURRVAL and NEXTVAL make writing PL/SQL source code easier for you and improve run-time performance and scalability. You can use sequence_name.CURRVAL and sequence_name.NEXTVAL wherever you can use a NUMBER expression. <pre> -- Using CURRVAL and NEXTVAL CREATE TABLE employees_temp AS SELECT employee_id, first_name, last_name FROM employees; CREATE TABLE employees_temp2 AS SELECT employee_id, first_name, last_name FROM employees; DECLARE seq_value NUMBER; BEGIN -- Generate initial sequence number seq_value := employees_seq.NEXTVAL; -- Print initial sequence number: DBMS_OUTPUT.PUT_LINE ('Initial sequence value: ' || TO_CHAR(seq_value)); -- Use NEXTVAL to create unique number when inserting data: INSERT INTO employees_temp VALUES (employees_seq.NEXTVAL, 'Lynette', 'Smith'); -- Use CURRVAL to store same value somewhere else: INSERT INTO employees_temp2 VALUES (employees_seq.CURRVAL, 'Morgan', 'Smith'); -- Because NEXTVAL values might be referenced -- by different users and applications, -- and some NEXTVAL values might not be stored in the database, -- there might be gaps in the sequence. -- Use CURRVAL to specify the record to delete: seq_value := employees_seq.CURRVAL; DELETE FROM employees_temp2 WHERE employee_id = seq_value; -- Udpate employee_id with NEXTVAL for specified record: UPDATE employees_temp SET employee_id = employees_seq.NEXTVAL WHERE first_name = 'Lynette' AND last_name = 'Smith'; -- Display final value of CURRVAL: seq_value := employees_seq.CURRVAL; DBMS_OUTPUT.PUT_LINE ('Ending sequence value: ' || TO_CHAR(seq_value)); END; / </pre> Usage Notes: You can use sequence_name.CURRVAL and sequence_name.NEXTVAL wherever you can use a NUMBER expression. Using sequence_name.CURRVAL or sequence_name.NEXTVAL to provide a default value for an object type method parameter causes a compilation error. PL/SQL evaluates every occurrence of sequence_name.CURRVAL and sequence_name.NEXTVAL (unlike SQL, which evaluates a sequence expression only once for every row in which it appears).</TIP_TEXT><EXAMPLE_CODE> SELECT employees_seq.NEXTVAL -- should flag FROM dual; SELECT employees_seq.NEXTVAL -- should NOT flag FROM employees; SELECT employees_seq.NEXTVAL, -- should flag CURRVAL -- should flag FROM dual; SELECT seq.NEXTVAL nextval, -- should flag 1 + (2 * seq.CURRVAL) currval -- should flag FROM dual; </EXAMPLE_CODE><DB_VERSION>>= 10g</DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r19</WIKIREV><WIKITIMESTAMP>13 Oct 2011 - 21:00:42</WIKITIMESTAMP></RULE><RULE><ID>2106</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid unnecessary references to schema names.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>58</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //TABLE_REF/QNAME[count(IDENTIFIER) > 1] </XPATH_QUERY><TIP_TEXT>Rule 5810 ("Use synonyms in SQL table clauses rather than hard coded owner.table references") may not work for you if you don't use synonyms. A reference to an object in the same schema is made directly without using a synonym; a reference to an object in another schema is made using a wrapper object (view or package). You could consider using synonyms to avoid schema name references in wrapper objects. But in any case, you still might want to insist on minimizing references to external objects, which is achieved not by enforcing the use of synonyms but rather by enforcing the use of wrappers. Furthermore, rule 5810 as it currently reads does not preclude a reference of the form SCHEMA.SYNONYM, which obviously you may not want. Therefore, you may like to flag references of the form SCHEMA.OBJECT, where the referenced object is in the same schema as the referencing object.</TIP_TEXT><EXAMPLE_CODE> SELECT col1, col2, col3 FROM mytab; SELECT col1, col2, col3 FROM myschema.mytab; -- should raise </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r13</WIKIREV><WIKITIMESTAMP>23 Feb 2012 - 20:49:56</WIKITIMESTAMP></RULE><RULE><ID>2107</ID></RULE><RULE><ID>2108</ID></RULE><RULE><ID>2109</ID></RULE><RULE><ID>2110</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid deprecated data types such as RAW and LONG.</DEF><RULE_TYPE>2</RULE_TYPE><CAT>68</CAT><SEVERITY>2</SEVERITY><XPATH_QUERY> //TYPE/QNAME[@value = ("RAW", "LONG", "LONG RAW", "LONG VARCHAR")] </XPATH_QUERY><TIP_TEXT>Consider using BLOBS and CLOBS which have way less usage restrictions than LONG and LONG RAWS. For instance, they can be used in PL/SQL, INSERT INTO ... SELECT, CREATE TABLE AS SELECT ..., in WHERE clauses etc.</TIP_TEXT><EXAMPLE_CODE> DECLARE a RAW; -- migrate to BLOB b LONG RAW; -- migrate to BLOB c LONG VARCHAR; -- migrate to LOB d LONG; -- migrate to LOB x VARCHAR; CURSOR mycur RETURN LONG; -- should flag FUNCTION myfunc RETURN LONG RAW -- should flag IS z LONG RAW; -- should flag BEGIN RETURN z; END; BEGIN SELECT mycol, CAST(col2 AS RAW) -- should flag INTO a FROM mytab WHERE (b IS OF TYPE(LONG RAW, RAW, VARCHAR)); -- should flag END; CREATE TABLE tab ( a RAW, -- should flag b LONG RAW, -- should flag c LONG VARCHAR, -- should flag d LONG, -- should flag x VARCHAR ); </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r16</WIKIREV><WIKITIMESTAMP>14 Oct 2011 - 03:27:57</WIKITIMESTAMP></RULE><RULE><ID>2111</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Use ANSI standard syntax wherever possible.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>58</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //WHERE[../TABLE_REF_LIST[count(TABLE_REF) > 1]]/TOKEN[1] </XPATH_QUERY><TIP_TEXT>We only need to worry about ANSI JOIN syntax. The reason is that prior to 9i Oracle only offered its own proprietary syntax (+) that would not work with any other database. And Oracle people are now in the habit of only using the Oracle syntax - even though the ANSI syntax is easier to read/write - and even though the Oracle syntax does not support full outer joins without doing two outer joins (left and right) with a union - which is terribly inefficient and hard to read.</TIP_TEXT><EXAMPLE_CODE> SELECT e.name FROM employees e, departments d -- should flag WHERE e.deptname = d.deptname; SELECT e.name FROM employees e WHERE e.deptname = 'Kitchen'; </EXAMPLE_CODE><DB_VERSION>>= 9i</DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r25</WIKIREV><WIKITIMESTAMP>28 Mar 2012 - 17:16:18</WIKITIMESTAMP></RULE><RULE><ID>2112</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Specify column names instead of numbers in ORDER BY clauses.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>58</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //EXPR[@cat="query"]/ORDER_LIST/ORDER_ITEM/LITERAL[@type="numeric"] </XPATH_QUERY><TIP_TEXT>Specify column names instead of numbers in ORDER BY clauses.</TIP_TEXT><EXAMPLE_CODE> SELECT col1, col2, col3 FROM mytable ORDER BY 2, -- should flag col3, 1, -- should flag 2 + col3; </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r19</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 04:40:04</WIKITIMESTAMP></RULE><RULE><ID>2113</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Explicitly specify ASC/DESC in ORDER BY clauses.</DEF><RULE_TYPE>3</RULE_TYPE><CAT>58</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //ORDER_ITEM[not(@order)] </XPATH_QUERY><TIP_TEXT>Explicitly specify ASC/DESC in ORDER BY clauses.</TIP_TEXT><EXAMPLE_CODE> SELECT col1, col2, col3 FROM mytable ORDER BY col1 ASC, col2, -- should flag col3 DESC; </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r16</WIKIREV><WIKITIMESTAMP>30 Oct 2011 - 01:49:10</WIKITIMESTAMP></RULE><RULE><ID>2114</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Introduce column aliases using the AS keyword.</DEF><RULE_TYPE>3</RULE_TYPE><CAT>58</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //SELECT_ITEM/ALIAS[not(TOKEN/@value="as")] </XPATH_QUERY><TIP_TEXT>Introduce column aliases using the AS keyword.</TIP_TEXT><EXAMPLE_CODE> SELECT e.name AS ename, e.deptname dname -- deptname should raise FROM employees e, departments d WHERE e.deptname = d.deptname; </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r26</WIKIREV><WIKITIMESTAMP>26 Mar 2012 - 19:07:44</WIKITIMESTAMP></RULE><RULE><ID>2115</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Force uppercase reserved words.</DEF><RULE_TYPE>3</RULE_TYPE><CAT>58</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //TOKEN[@cat="keyword"][not(qp5:is-upper-case(@value))] </XPATH_QUERY><TIP_TEXT>NOTE - This is basically a check that formatter has been run.</TIP_TEXT> <EXAMPLE_CODE>DECLARE a NUMBER; Begin -- should hit null; -- should hit END; </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r21</WIKIREV><WIKITIMESTAMP>29 Mar 2012 - 19:00:38</WIKITIMESTAMP></RULE><RULE><ID>2116</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid DBMS_OUTPUT.PUT_LINE unless encased within a conditional compilation directive.</DEF><RULE_TYPE>3</RULE_TYPE><CAT>58</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //CALL[QNAME/@value="dbms_output.put_line"][not(parent::COND_COMP_ITEM)] </XPATH_QUERY><TIP_TEXT>NOTE &#8211; This is a pre-production deployment check.</TIP_TEXT><EXAMPLE_CODE> BEGIN $IF a >= b $THEN DBMS_OUTPUT.put_line; $END END; BEGIN DBMS_OUTPUT.put_line; -- should flag END; BEGIN $IF a >= b $THEN IF some_condition THEN DBMS_OUTPUT.put_line; -- should flag END IF; $END END; BEGIN IF some_condition THEN DBMS_OUTPUT.put_line; -- should flag END IF; END; </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r20</WIKIREV><WIKITIMESTAMP>13 Oct 2011 - 21:01:22</WIKITIMESTAMP></RULE><RULE><ID>2118</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid DDLs other than Truncate, Exchange/Split partition.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //CHUNK[starts-with(@type, "Alter") or starts-with(@type, "Create") or starts-with(@type, "Drop") or ends-with(@type, "Statistics") or starts-with(@type, "Purge") or starts-with(@type, "Flashback") or starts-with(@type, "Set") or starts-with(@type, "Truncate") or @type=("Audit", "Noaudit", "Rename")] [not((descendant::SPLIT_PARTITION, descendant::EXCHANGE_PARTITION, descendant::TRUNCATE_PARTITION))] /descendant::QNAME[1] </XPATH_QUERY><TIP_TEXT>Avoid DDLs other than Truncate, Exchange/Split partition.</TIP_TEXT><EXAMPLE_CODE> --2118 /* GOOD */ ALTER TABLE pt_subpart SPLIT PARTITION p3 AT (25) INTO (PARTITION p3_1 SUBPARTITIONS 5 STORE IN ( users, c_data), PARTITION p3_2 SUBPARTITIONS 5 TABLESPACE dc_02 COMPRESS VARRAY VA SUBSTITUTABLE AT ALL LEVELS STORE AS LOB lnt (ENABLE STORAGE IN ROW) ); ALTER TABLE atp12 TRUNCATE PARTITION part12 UPDATE GLOBAL INDEXES; ALTER TABLE sales EXCHANGE PARTITION feb97 WITH TABLE sales_feb97 WITHOUT VALIDATION INVALIDATE GLOBAL INDEXES; /* BAD */ ALTER TABLE sales MODIFY SUBPARTITION jan97 -- should raise LOB l_0001 RETENTION PCTVERSION 20; ALTER TABLE tt1 MODIFY DEFAULT ATTRIBUTES FOR PARTITION part1 -- should raise COMPRESS OVERFLOW ALLOCATE EXTENT (SIZE 2M); ALTER TABLE atp15 MERGE PARTITIONS partition_1 , partition_2 -- should raise INTO PARTITION p1 UPDATE GLOBAL INDEXES PARALLEL 39; ALTER TABLE departments -- should raise DROP PRIMARY KEY CASCADE DROP INDEX; CREATE INDEX ind11 ON a (a DESC) INVISIBLE STORAGE (MAXSIZE 10 M); -- should raise create or replace view VW as -- should raise WITH rep AS (SELECT * FROM dual) SELECT * from rep; </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r29</WIKIREV><WIKITIMESTAMP>03 Mar 2012 - 21:50:32</WIKITIMESTAMP></RULE><RULE><ID>2119</ID></RULE><RULE><ID>2120</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid SELECT statements with more than two UNION operations.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>58</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //EXPR[@cat="query"]/EXPR[@cat=("union", "union_all", "intersect", "minus")] [ EXPR[@cat=("union", "union_all", "intersect", "minus")] /EXPR[@cat=("union", "union_all", "intersect", "minus")] ] /EXPR[@cat="query"] </XPATH_QUERY><TIP_TEXT>We want such occurrences flagged simply because we've seen too many abuses with respect to UNION operations. The flag will serve as a cue to review the code carefully. The most common mistake is where the two halves of a UNION can be easily merged into a single SELECT statement with a few strategically-placed CASE expressions.</TIP_TEXT><EXAMPLE_CODE> begin select col1 into v from tab1; select col1 into v from tab1 union select col2 into v from tab2; select col1 into v from tab1 union select col2 into v from tab2 union select col3 into v from tab3; select col1 into v from tab1 union select col2 into v from tab2 union select col3 into v from tab3 union select col4 into v from tab4; -- should flag select col1 into v from tab1 union select col2 into v from tab2 union select col3 into v from tab3 union select col4 into v from tab4 union select col5 into v from tab5; -- should flag end; </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r21</WIKIREV><WIKITIMESTAMP>07 Nov 2011 - 20:58:46</WIKITIMESTAMP></RULE><RULE><ID>2121</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid use of DISTINCT in queries where it is already implied, such as IN (SELECT DISTINCT ...).</DEF><RULE_TYPE>0</RULE_TYPE><CAT>58</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //SELECT_LIST[@mode="distinct"][ancestor::WHERE or ancestor::EXPR[@cat="union"]] </XPATH_QUERY><TIP_TEXT>The DISTINCT keyword is at the very least redundant and may incur additional logical IO in the following examples: Example 1) <pre> -- IN operator implies DISTINCT SELECT a_column FROM a_table WHERE a_column IN (SELECT DISTINCT b_column FROM b_table) </pre> Example 2) <pre> -- UNION operator implies DISTINCT SELECT a_column FROM a_table UNION SELECT DISTINCT b_column FROM b_table </pre></TIP_TEXT><EXAMPLE_CODE> SELECT a_column FROM a_table WHERE a_column IN (SELECT DISTINCT b_column FROM b_table); -- should raise SELECT DISTINCT a_column FROM a_table -- should raise UNION SELECT DISTINCT b_column FROM b_table; -- should raise </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r23</WIKIREV><WIKITIMESTAMP>23 Feb 2012 - 22:26:03</WIKITIMESTAMP></RULE><RULE><ID>2123</ID></RULE><RULE><ID>2124</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Use SAVE EXCEPTIONS in FORALL statements.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>30</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //LOOP[@cat="forall"][not(@save_exceptions="true")] </XPATH_QUERY><TIP_TEXT>You may want to use rule 3015 to enforce an error handler.</TIP_TEXT><EXAMPLE_CODE> BEGIN FORALL i IN l_ary.first .. l_ary.last -- should flag INSERT INTO vew_trade (tra_trade_id, tra_trade_date) SELECT seq_object_id.nextval, trade_date FROM TABLE(ary_trade(l_ary(i))) t; FORALL i IN l_ary.first .. l_ary.last SAVE EXCEPTIONS INSERT INTO vew_trade (tra_trade_id, tra_trade_date) SELECT seq_object_id.nextval, trade_date FROM TABLE(ary_trade(l_ary(i))) t; END; / </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r19</WIKIREV><WIKITIMESTAMP>23 Feb 2012 - 21:25:07</WIKITIMESTAMP></RULE><RULE><ID>2125</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Limit use of FULL OUTER JOIN.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>58</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //JOIN[@type="full_outer"] </XPATH_QUERY><TIP_TEXT>NOTE: Some shops have concerns that developers don't understand full outer joins so they want to simply flag any such usage for code review. This rule is similar to Rules 2120 (too many UNION operations) and 2129 (too many OR operations) in that we just want unusual code to be flagged for review. The impetus for this particular rule is a series of snags we ran into following our 10g to 11g upgrade. We encountered at least two bugs related to FULL OUTER JOIN: Bug 9882805 Wrong results with NATIVE FULL OUTER JOIN and NVL Bug 6686885 WRONG RESULTS (missing rows) USING NATIVE FULL OUTER JOIN While it's true that the existence of bugs related to a given language feature doesn't constitute sufficient justification for a rule, we feel that FULL OUTER JOIN is unusual enough to warrant flagging. We've seen cases where an equivalent statement using UNION was better. For example, sometimes <pre> SELECT * FROM A LEFT OUTER JOIN B ON ... INNER JOIN C ON ... UNION ALL SELECT * FROM B LEFT OUTER JOIN A ON ... INNER JOIN C ON ... </pre> is semantically more precise than <pre> SELECT * FROM (SELECT * FROM A FULL OUTER JOIN B ON ...) INNER JOIN C ON ... </pre> despite being syntactically less compact. The former may even perform better, particularly if each inner join to C can be executed early to reduce the size of the intermediate result set prior to each left outer join.</TIP_TEXT><EXAMPLE_CODE> SELECT * FROM A LEFT OUTER JOIN B ON x INNER JOIN C ON y UNION ALL SELECT * FROM B LEFT OUTER JOIN A ON x INNER JOIN C ON y; SELECT * FROM (SELECT * FROM A FULL OUTER JOIN B ON x) INNER JOIN C ON y; -- should flag </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r17</WIKIREV><WIKITIMESTAMP>28 Oct 2011 - 04:43:26</WIKITIMESTAMP></RULE><RULE><ID>2126</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Encapsulate all procedures and functions in packages.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //CHUNK/(PROCEDURE_BODY, FUNCTION_BODY)/QNAME, //CHUNK/CREATE/(PROCEDURE_BODY, FUNCTION_BODY)/QNAME </XPATH_QUERY><TIP_TEXT>This rule finds any standalone PROCEDUREs and FUNCTIONs (with optional CREATE), as such program units should not exist outside a package.</TIP_TEXT><EXAMPLE_CODE> CREATE PROCEDURE p -- should raise IS PROCEDURE q IS BEGIN NULL; END; BEGIN NULL; END; / CREATE FUNCTION f -- should raise RETURN NUMBER IS PROCEDURE q IS BEGIN NULL; END; BEGIN NULL; END; / CREATE PACKAGE pkg IS PROCEDURE p; FUNCTION f RETURN NUMBER; END; / CREATE PACKAGE BODY pkgb IS PROCEDURE p IS PROCEDURE q IS BEGIN NULL; END; BEGIN NULL; END; FUNCTION f RETURN NUMBER IS PROCEDURE q IS BEGIN NULL; END; BEGIN NULL; END; END; / </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r22</WIKIREV><WIKITIMESTAMP>23 Feb 2012 - 21:26:09</WIKITIMESTAMP></RULE><RULE><ID>2127</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Limit use of sub queries in SELECT clauses.</DEF><RULE_TYPE>1</RULE_TYPE><CAT>30</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //SELECT_ITEM//EXPR[@cat="query"] </XPATH_QUERY><TIP_TEXT>An example of what we're looking to have flagged is: <pre> SELECT t.amount, (SELECT c.name FROM currency c WHERE c.currency_id = t.currency_id) AS currency_name FROM trade t WHERE t.trade_date = TRUNC(SYSDATE) </pre> This is semantically equivalent to <pre> SELECT t.amount, c.name AS currency_name FROM trade t LEFT OUTER JOIN currency c ON c.currency_id = t.currency_id WHERE t.trade_date = TRUNC(SYSDATE) </pre> except that the former comes with this additional side effect: an exception is raised if the parenthetical subquery returns more than one row. As a result, it's not surprising to find that such constructs have an effect on the query plan. By experience, we've found that hash joins are no longer considered where they otherwise might be. The subquery is typically evaluated in a nested loop and this evaluation must necessarily take place after the containing SELECT statement is resolved. In most cases, we've found that: (1) the programmer was unaware of these subtle differences and just happened to prefer the former syntax for reasons of readability; AND (2) an enabled foreign key constraint exists between the tables in question, thereby justifying a straightforward join (either INNER or OUTER, depending on the foreign key's nullability). In the above example, if txn.currency_id is a non-NULL foreign key reference to currency.currency_id, then the following syntax is preferred: <pre> SELECT t.amount, c.name AS currency_name FROM trade t INNER JOIN currency c ON c.currency_id = t.currency_id WHERE t.trade_date = TRUNC(SYSDATE) </pre></TIP_TEXT><EXAMPLE_CODE> SELECT t.amount, (SELECT c.name -- should flag FROM currency c WHERE c.currency_id = t.currency_id) AS currency_name FROM trade t WHERE t.trade_date = TRUNC(SYSDATE); SELECT t.amount, c.name AS currency_name FROM trade t LEFT OUTER JOIN currency c ON c.currency_id = t.currency_id WHERE t.trade_date = TRUNC(SYSDATE); SELECT t.amount, c.name AS currency_name FROM trade t INNER JOIN currency c ON c.currency_id = t.currency_id WHERE t.trade_date = TRUNC(SYSDATE); </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r16</WIKIREV><WIKITIMESTAMP>14 Oct 2011 - 14:38:51</WIKITIMESTAMP></RULE><RULE><ID>2128</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid large item lists for IN clauses.</DEF><RULE_TYPE>2</RULE_TYPE><CAT>58</CAT><SEVERITY>2</SEVERITY><XPATH_QUERY> //EXPR[@cat=("in", "not_in", "eq_any", "eq_some", "eq_all", "ne_any", "ne_some", "ne_all", "lt_any", "lt_some", "lt_all", "le_any", "le_some", "le_all", "gt_any", "gt_some", "gt_all", "ge_any", "ge_some", "ge_all")] /EXPR[@cat="list"]/child::*[1001] </XPATH_QUERY><TIP_TEXT>We have no specific objection to large item lists in IN clauses in PL/SQL code but DML statements are another story. We see a large item list as a possible indication that perhaps a new table should be created. Note that item lists in IN conditions are limited in size to 1000 members. Thus, IN conditions are not as scalable as joins.</TIP_TEXT><EXAMPLE_CODE> SELECT col1 FROM tab WHERE col IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 , 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 , 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60 , 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80 , 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100 , 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120 , 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140 , 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160 , 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180 , 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200 , 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220 , 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240 , 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260 , 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280 , 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300 , 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320 , 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340 , 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360 , 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380 , 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400 , 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420 , 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440 , 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460 , 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480 , 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500 , 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520 , 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540 , 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560 , 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580 , 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600 , 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620 , 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640 , 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660 , 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680 , 681, 682, 683, 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700 , 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720 , 721, 722, 723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739, 740 , 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760 , 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780 , 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 800 , 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820 , 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840 , 841, 842, 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, 855, 856, 857, 858, 859, 860 , 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, 874, 875, 876, 877, 878, 879, 880 , 881, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 900 , 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920 , 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940 , 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960 , 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980 , 981, 982, 983, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000, 1001, -- should flag 1002, 1003) / </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r19</WIKIREV><WIKITIMESTAMP>14 Oct 2011 - 14:34:37</WIKITIMESTAMP></RULE><RULE><ID>2129</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>For a given parenthetical level, avoid more than six OR operations in WHERE clauses.</DEF><RULE_TYPE>2</RULE_TYPE><CAT>58</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //EXPR[@cat="or"][1]/ EXPR[@cat="or"][1]/ EXPR[@cat="or"][1]/ EXPR[@cat="or"][1]/ EXPR[@cat="or"][1]/ EXPR[@cat="or"][1]/ EXPR[@cat="or"][1] </XPATH_QUERY><TIP_TEXT>For a given parenthetical level, avoid more than six OR operations in WHERE clauses.</TIP_TEXT><EXAMPLE_CODE> SELECT col1 FROM tab WHERE ( cond1 -- should raise OR cond2 -- should raise OR cond3 -- should raise OR cond4 OR cond5 OR cond6 OR cond7 OR cond8 OR cond9); </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r26</WIKIREV><WIKITIMESTAMP>23 Feb 2012 - 21:29:21</WIKITIMESTAMP></RULE><RULE><ID>2131</ID></RULE><RULE><ID>2132</ID></RULE><RULE><ID>2133</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Limit use of DEFAULT clause in parameter declarations.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>68</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //PARAM_SPEC/DEFAULT </XPATH_QUERY><TIP_TEXT>Assume the following package exists in a production application: <pre> CREATE OR REPLACE PACKAGE date_utils AS FUNCTION get_next_weekday RETURN DATE; END date_utils; / CREATE OR REPLACE PACKAGE BODY date_utils AS FUNCTION get_next_weekday RETURN DATE AS v_date CONSTANT DATE := TRUNC(SYSDATE); BEGIN RETURN v_date + CASE TO_CHAR(v_date,'D') WHEN '6' THEN 3 WHEN '7' THEN 2 ELSE 1 END; END get_next_weekday; END date_utils; / </pre> Assume there are several references to this package throughout the production code base such as the following: <pre> CREATE OR REPLACE PACKAGE trade_utils AS FUNCTION get_settle_date RETURN DATE; END trade_utils; / CREATE OR REPLACE PACKAGE BODY trade_utils AS FUNCTION get_settle_date RETURN DATE AS BEGIN RETURN date_utils.get_next_weekday(); END get_settle_date; END trade_utils; / </pre> Suppose a new customer likes the function but wants the ability to pass in a reference date. One way to handle this is to add a parameter with a DEFAULT clause as follows: <pre> CREATE OR REPLACE PACKAGE date_utils AS FUNCTION get_next_weekday(p_date IN DATE DEFAULT TRUNC(SYSDATE)) RETURN DATE; END date_utils; / CREATE OR REPLACE PACKAGE BODY date_utils AS FUNCTION get_next_weekday(p_date IN DATE DEFAULT TRUNC(SYSDATE)) RETURN DATE AS BEGIN RETURN p_date + CASE TO_CHAR(p_date,'D') WHEN '6' THEN 3 WHEN '7' THEN 2 ELSE 1 END; END get_next_weekday; END date_utils; / </pre> Although this solution requires no code changes for existing callers, it will nevertheless temporarily invalidate all objects that contain those existing calls and therefore may cause "ORA-04061: existing state of package body has been invalidated" exceptions. Three-tier Weblogic or JBoss applications that maintain always-on database connection pools are especially prone to such exceptions. As such, this solution has "run-time side effects". What's more, although the new parameter with its DEFAULT clause may be added without causing compile-time side effects, the default value itself cannot be subsequently modified with the same impunity. This is because it's impossible to distinguish between callers that always want the default behavior, whatever that happens to be, versus callers that really mean to say date_utils.get_next_weekday(TRUNC(SYSDATE)) and are just being lazy. This is the essential problem with defaults in all computer languages. Explicitness is inconvenient for programmers but safer for the application. A better way is to create a new function for the new customer: <pre> CREATE OR REPLACE PACKAGE date_utils AS FUNCTION get_next_weekday_for_date(p_date IN DATE DEFAULT SYSDATE) RETURN DATE; FUNCTION get_next_weekday RETURN DATE; END date_utils; / CREATE OR REPLACE PACKAGE BODY date_utils AS FUNCTION get_next_weekday_for_date(p_date IN DATE DEFAULT SYSDATE) RETURN DATE AS BEGIN RETURN p_date + CASE TO_CHAR(p_date,'D') WHEN '6' THEN 3 WHEN '7' THEN 2 ELSE 1 END; END get_next_weekday_for_date; FUNCTION get_next_weekday RETURN DATE AS BEGIN RETURN get_next_weekday_for_date(TRUNC(SYSDATE)); END get_next_weekday; END date_utils; / </pre></TIP_TEXT><EXAMPLE_CODE> CREATE OR REPLACE PACKAGE date_utils AS FUNCTION get_next_weekday(p_date IN DATE DEFAULT TRUNC(SYSDATE)) RETURN DATE; -- should flag END date_utils; / CREATE OR REPLACE PACKAGE BODY date_utils AS FUNCTION get_next_weekday(p_date IN DATE DEFAULT TRUNC(SYSDATE)) RETURN DATE AS -- should flag BEGIN RETURN p_date + CASE TO_CHAR(p_date,'D') WHEN '6' THEN 3 WHEN '7' THEN 2 ELSE 1 END; END get_next_weekday; END date_utils; / </EXAMPLE_CODE><DB_VERSION></DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r16</WIKIREV><WIKITIMESTAMP>13 Oct 2011 - 21:02:12</WIKITIMESTAMP></RULE><RULE><ID>2134</ID></RULE><RULE><ID>2001</ID></RULE><RULE><ID>2002</ID></RULE><RULE><ID>2003</ID></RULE><RULE><ID>2004</ID></RULE><RULE><ID>2005</ID></RULE><RULE><ID>2201</ID></RULE><RULE><ID>2202</ID></RULE><RULE><ID>2203</ID></RULE><RULE><ID>2204</ID></RULE><RULE><ID>2205</ID></RULE><RULE><ID>2206</ID></RULE><RULE><ID>2207</ID></RULE><RULE><ID>2208</ID></RULE><RULE><ID>2209</ID></RULE><RULE><ID>2210</ID></RULE><RULE><ID>2211</ID></RULE><RULE><ID>2212</ID></RULE><RULE><ID>2213</ID></RULE><RULE><ID>2602</ID></RULE><RULE><ID>2604</ID></RULE><RULE><ID>2605</ID></RULE><RULE><ID>2611</ID></RULE><RULE><ID>2614</ID></RULE><RULE><ID>2615</ID></RULE><RULE><ID>2616</ID></RULE><RULE><ID>2618</ID></RULE><RULE><ID>2810</ID></RULE><RULE><ID>2823</ID></RULE><RULE><ID>2828</ID></RULE><RULE><ID>3008</ID></RULE><RULE><ID>3009</ID></RULE><RULE><ID>3011</ID></RULE><RULE><ID>4004</ID></RULE><RULE><ID>5203</ID></RULE><RULE><ID>5204</ID></RULE><RULE><ID>5206</ID></RULE><RULE><ID>5403</ID></RULE><RULE><ID>5404</ID></RULE><RULE><ID>5802</ID></RULE><RULE><ID>5811</ID></RULE><RULE><ID>5812</ID></RULE><RULE><ID>5815</ID></RULE><RULE><ID>5817</ID></RULE><RULE><ID>5818</ID></RULE><RULE><ID>5819</ID></RULE><RULE><ID>5820</ID></RULE><RULE><ID>5821</ID></RULE><RULE><ID>6403</ID></RULE><RULE><ID>6408</ID></RULE><RULE><ID>6410</ID></RULE><RULE><ID>6804</ID></RULE><RULE><ID>6201</ID></RULE><RULE><ID>6803</ID></RULE><RULE><ID>3201</ID></RULE><RULE><ID>3012</ID></RULE><RULE><ID>6412</ID></RULE><RULE><ID>4202</ID></RULE><RULE><ID>5933</ID></RULE><RULE><ID>2612</ID></RULE><RULE><ID>6601</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Test naming conventions for NUMBER variable types</DEF><RULE_TYPE>5</RULE_TYPE><CAT>64</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //VAR_DECL/QNAME [../TYPE/QNAME/@value="NUMBER"][@value[not(starts-with(.,'n_'))]] </XPATH_QUERY><TIP_TEXT>Naming conventions standardize identifier names, which improves the consistency, readability, and maintainability of source code. For example, you can create naming conventions to standardize: * The number of characters used in a name * Prefixes and suffixes, such as appending _PK to a primary key column * Plurality, such as making all table names plural * Upper or lower case</TIP_TEXT><EXAMPLE_CODE> CREATE OR REPLACE PROCEDURE test_naming_conventions ( p_cur_out OUT SYS_REFCURSOR) IS AAAvVariable VARCHAR2 (2); AAAnvVariable NVARCHAR2 (100); AAAnVariable NUMBER; AAAbVariable BOOLEAN; AAAxVariable XMLTYPE; AAAdtVariable DATE; AAAcVariable CLOB; AAAtsVariable TIMESTAMP; BEGIN AAAvVariable := 'XX'; AAAnvVariable := 'XXXYYYZZZ'; AAAnVariable := 12345; AAAbVariable := TRUE; AAAxVariable := XMLTYPE (''); AAAdtVariable := SYSDATE; AAAcVariable := 'CLOB STUFF'; AAAtsVariable := SYSTIMESTAMP; OPEN p_cur_out FOR SELECT FIELD1, FIELD2, FIELD3, FIELD4, FIELD5 FROM TEST_TABLE; END;</EXAMPLE_CODE><DB_VERSION>8i and greater</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R2006</TIP_LINK><WIKIREV>r11</WIKIREV><WIKITIMESTAMP>22 Mar 2012 - 18:36:08</WIKITIMESTAMP></RULE><RULE><ID>6602</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Test naming conventions for VARCHAR2 variable types</DEF><RULE_TYPE>5</RULE_TYPE><CAT>64</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //VAR_DECL/QNAME [../TYPE/QNAME/@value="VARCHAR2"][@value[not(starts-with(.,'vc_'))]] </XPATH_QUERY><TIP_TEXT>Naming conventions standardize identifier names, which improves the consistency, readability, and maintainability of source code. For example, you can create naming conventions to standardize: * The number of characters used in a name * Prefixes and suffixes, such as appending _PK to a primary key column * Plurality, such as making all table names plural * Upper or lower case</TIP_TEXT><EXAMPLE_CODE> CREATE OR REPLACE PROCEDURE test_naming_conventions ( p_cur_out OUT SYS_REFCURSOR) IS AAAvVariable VARCHAR2 (2); AAAnvVariable NVARCHAR2 (100); AAAnVariable NUMBER; AAAbVariable BOOLEAN; AAAxVariable XMLTYPE; AAAdtVariable DATE; AAAcVariable CLOB; AAAtsVariable TIMESTAMP; BEGIN AAAvVariable := 'XX'; AAAnvVariable := 'XXXYYYZZZ'; AAAnVariable := 12345; AAAbVariable := TRUE; AAAxVariable := XMLTYPE (''); AAAdtVariable := SYSDATE; AAAcVariable := 'CLOB STUFF'; AAAtsVariable := SYSTIMESTAMP; OPEN p_cur_out FOR SELECT FIELD1, FIELD2, FIELD3, FIELD4, FIELD5 FROM TEST_TABLE; END;</EXAMPLE_CODE><DB_VERSION>8i and greater</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R2006</TIP_LINK><WIKIREV>r10</WIKIREV><WIKITIMESTAMP>22 Mar 2012 - 18:36:24</WIKITIMESTAMP></RULE><RULE><ID>6603</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Test naming conventions for DATE variable types</DEF><RULE_TYPE>5</RULE_TYPE><CAT>64</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //VAR_DECL/QNAME [../TYPE/QNAME/@value="DATE"][@value[not(starts-with(.,'dt_'))]] </XPATH_QUERY><TIP_TEXT>Naming conventions standardize identifier names, which improves the consistency, readability, and maintainability of source code. For example, you can create naming conventions to standardize: * The number of characters used in a name * Prefixes and suffixes, such as appending _PK to a primary key column * Plurality, such as making all table names plural * Upper or lower case</TIP_TEXT><EXAMPLE_CODE>CREATE OR REPLACE PROCEDURE test_naming_conventions ( p_cur_out OUT SYS_REFCURSOR) IS AAAvVariable VARCHAR2 (2); AAAnvVariable NVARCHAR2 (100); AAAnVariable NUMBER; AAAbVariable BOOLEAN; AAAxVariable XMLTYPE; AAAdtVariable DATE; AAAcVariable CLOB; AAAtsVariable TIMESTAMP; BEGIN AAAvVariable := 'XX'; AAAnvVariable := 'XXXYYYZZZ'; AAAnVariable := 12345; AAAbVariable := TRUE; AAAxVariable := XMLTYPE (''); AAAdtVariable := SYSDATE; AAAcVariable := 'CLOB STUFF'; AAAtsVariable := SYSTIMESTAMP; OPEN p_cur_out FOR SELECT FIELD1, FIELD2, FIELD3, FIELD4, FIELD5 FROM TEST_TABLE; END;</EXAMPLE_CODE><DB_VERSION>8i and greater</DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r9</WIKIREV><WIKITIMESTAMP>22 Mar 2012 - 18:36:43</WIKITIMESTAMP></RULE><RULE><ID>6604</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Test naming conventions for BOOLEAN variable types</DEF><RULE_TYPE>5</RULE_TYPE><CAT>64</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //VAR_DECL/QNAME [../TYPE/QNAME/@value="BOOLEAN"][@value[not(starts-with(.,'b_'))]] </XPATH_QUERY><TIP_TEXT>Naming conventions standardize identifier names, which improves the consistency, readability, and maintainability of source code. For example, you can create naming conventions to standardize: * The number of characters used in a name * Prefixes and suffixes, such as appending _PK to a primary key column * Plurality, such as making all table names plural * Upper or lower case</TIP_TEXT><EXAMPLE_CODE>CREATE OR REPLACE PROCEDURE test_naming_conventions ( p_cur_out OUT SYS_REFCURSOR) IS AAAvVariable VARCHAR2 (2); AAAnvVariable NVARCHAR2 (100); AAAnVariable NUMBER; AAAbVariable BOOLEAN; AAAxVariable XMLTYPE; AAAdtVariable DATE; AAAcVariable CLOB; AAAtsVariable TIMESTAMP; BEGIN AAAvVariable := 'XX'; AAAnvVariable := 'XXXYYYZZZ'; AAAnVariable := 12345; AAAbVariable := TRUE; AAAxVariable := XMLTYPE (''); AAAdtVariable := SYSDATE; AAAcVariable := 'CLOB STUFF'; AAAtsVariable := SYSTIMESTAMP; OPEN p_cur_out FOR SELECT FIELD1, FIELD2, FIELD3, FIELD4, FIELD5 FROM TEST_TABLE; END;</EXAMPLE_CODE><DB_VERSION>8i and greater</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R2006</TIP_LINK><WIKIREV>r10</WIKIREV><WIKITIMESTAMP>22 Mar 2012 - 18:37:01</WIKITIMESTAMP></RULE><RULE><ID>6605</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Test naming conventions for CLOB variable types</DEF><RULE_TYPE>5</RULE_TYPE><CAT>64</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //VAR_DECL/QNAME [../TYPE/QNAME/@value="CLOB"][@value[not(starts-with(.,'c_'))]] </XPATH_QUERY><TIP_TEXT>Naming conventions standardize identifier names, which improves the consistency, readability, and maintainability of source code. For example, you can create naming conventions to standardize: * The number of characters used in a name * Prefixes and suffixes, such as appending _PK to a primary key column * Plurality, such as making all table names plural * Upper or lower case</TIP_TEXT><EXAMPLE_CODE>CREATE OR REPLACE PROCEDURE test_naming_conventions ( p_cur_out OUT SYS_REFCURSOR) IS AAAvVariable VARCHAR2 (2); AAAnvVariable NVARCHAR2 (100); AAAnVariable NUMBER; AAAbVariable BOOLEAN; AAAxVariable XMLTYPE; AAAdtVariable DATE; AAAcVariable CLOB; AAAtsVariable TIMESTAMP; BEGIN AAAvVariable := 'XX'; AAAnvVariable := 'XXXYYYZZZ'; AAAnVariable := 12345; AAAbVariable := TRUE; AAAxVariable := XMLTYPE (''); AAAdtVariable := SYSDATE; AAAcVariable := 'CLOB STUFF'; AAAtsVariable := SYSTIMESTAMP; OPEN p_cur_out FOR SELECT FIELD1, FIELD2, FIELD3, FIELD4, FIELD5 FROM TEST_TABLE; END;</EXAMPLE_CODE><DB_VERSION>8i and greater</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R2006</TIP_LINK><WIKIREV>r9</WIKIREV><WIKITIMESTAMP>22 Mar 2012 - 18:37:16</WIKITIMESTAMP></RULE><RULE><ID>6606</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Test naming conventions for NVARCHAR2 variable types</DEF><RULE_TYPE>5</RULE_TYPE><CAT>64</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //VAR_DECL/QNAME [../TYPE/QNAME/@value="NVARCHAR2"][@value[not(starts-with(.,'nvc_'))]] </XPATH_QUERY><TIP_TEXT>Naming conventions standardize identifier names, which improves the consistency, readability, and maintainability of source code. For example, you can create naming conventions to standardize: * The number of characters used in a name * Prefixes and suffixes, such as appending _PK to a primary key column * Plurality, such as making all table names plural * Upper or lower case</TIP_TEXT><EXAMPLE_CODE>CREATE OR REPLACE PROCEDURE test_naming_conventions ( p_cur_out OUT SYS_REFCURSOR) IS AAAvVariable VARCHAR2 (2); AAAnvVariable NVARCHAR2 (100); AAAnVariable NUMBER; AAAbVariable BOOLEAN; AAAxVariable XMLTYPE; AAAdtVariable DATE; AAAcVariable CLOB; AAAtsVariable TIMESTAMP; BEGIN AAAvVariable := 'XX'; AAAnvVariable := 'XXXYYYZZZ'; AAAnVariable := 12345; AAAbVariable := TRUE; AAAxVariable := XMLTYPE (''); AAAdtVariable := SYSDATE; AAAcVariable := 'CLOB STUFF'; AAAtsVariable := SYSTIMESTAMP; OPEN p_cur_out FOR SELECT FIELD1, FIELD2, FIELD3, FIELD4, FIELD5 FROM TEST_TABLE; END;</EXAMPLE_CODE><DB_VERSION>8i and greater</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R2006</TIP_LINK><WIKIREV>r9</WIKIREV><WIKITIMESTAMP>22 Mar 2012 - 18:37:29</WIKITIMESTAMP></RULE><RULE><ID>6607</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Test naming conventions for XMLTYPE variable types</DEF><RULE_TYPE>5</RULE_TYPE><CAT>64</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //VAR_DECL/QNAME [../TYPE/QNAME/@value="XMLTYPE"][@value[not(starts-with(.,'x_'))]] </XPATH_QUERY><TIP_TEXT>Naming conventions standardize identifier names, which improves the consistency, readability, and maintainability of source code. For example, you can create naming conventions to standardize: * The number of characters used in a name * Prefixes and suffixes, such as appending _PK to a primary key column * Plurality, such as making all table names plural * Upper or lower case</TIP_TEXT><EXAMPLE_CODE>CREATE OR REPLACE PROCEDURE test_naming_conventions ( p_cur_out OUT SYS_REFCURSOR) IS AAAvVariable VARCHAR2 (2); AAAnvVariable NVARCHAR2 (100); AAAnVariable NUMBER; AAAbVariable BOOLEAN; AAAxVariable XMLTYPE; AAAdtVariable DATE; AAAcVariable CLOB; AAAtsVariable TIMESTAMP; BEGIN AAAvVariable := 'XX'; AAAnvVariable := 'XXXYYYZZZ'; AAAnVariable := 12345; AAAbVariable := TRUE; AAAxVariable := XMLTYPE (''); AAAdtVariable := SYSDATE; AAAcVariable := 'CLOB STUFF'; AAAtsVariable := SYSTIMESTAMP; OPEN p_cur_out FOR SELECT FIELD1, FIELD2, FIELD3, FIELD4, FIELD5 FROM TEST_TABLE; END;</EXAMPLE_CODE><DB_VERSION>8i and greater</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R2006</TIP_LINK><WIKIREV>r10</WIKIREV><WIKITIMESTAMP>22 Mar 2012 - 18:37:43</WIKITIMESTAMP></RULE><RULE><ID>6608</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Test naming conventions for CONSTANTS</DEF><RULE_TYPE>5</RULE_TYPE><CAT>64</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //CONST_DECL/QNAME [@value[not(starts-with(.,'c_'))]] </XPATH_QUERY><TIP_TEXT>Naming conventions standardize identifier names, which improves the consistency, readability, and maintainability of source code. For example, you can create naming conventions to standardize: * The number of characters used in a name * Prefixes and suffixes, such as appending _PK to a primary key column * Plurality, such as making all table names plural * Upper or lower case</TIP_TEXT><EXAMPLE_CODE>CREATE OR REPLACE PROCEDURE test_for_constants AS XXXc_ImAConstant CONSTANT NUMBER := 5; -- should hit nVariable NUMBER; BEGIN nVariable := XXXc_ImAConstant; -- should hit DBMS_OUTPUT.print_line (XXXc_ImAConstant); -- should hit END;</EXAMPLE_CODE><DB_VERSION>8i and greater</DB_VERSION><TIP_LINK>qrev:chc.rev?topid=R2006</TIP_LINK><WIKIREV>r8</WIKIREV><WIKITIMESTAMP>22 Mar 2012 - 18:37:56</WIKITIMESTAMP></RULE><RULE><ID>6609</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Test naming conventions for TABLE</DEF><RULE_TYPE>5</RULE_TYPE><CAT>64</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //(TABLE, ALTER_TABLE)/QNAME/IDENTIFIER[last()] [@value[not(starts-with(., 'TBL_'))]] </XPATH_QUERY><TIP_TEXT>Naming conventions standardize identifier names, which improves the consistency, readability, and maintainability of source code. For example, you can create naming conventions to standardize: * The number of characters used in a name * Prefixes and suffixes, such as appending _PK to a primary key column * Plurality, such as making all table names plural * Upper or lower case</TIP_TEXT><EXAMPLE_CODE>CREATE TABLE SCOTT.EMP ( EMPNO NUMBER (4), ENAME VARCHAR2 (10 BYTE), JOB VARCHAR2 (9 BYTE), MGR NUMBER (4), HIREDATE DATE, SAL NUMBER (7, 2), COMM NUMBER (7, 2), DEPTNO NUMBER (2) ) TABLESPACE USERS PCTUSED 0 PCTFREE 10 INITRANS 1 MAXTRANS 255 STORAGE (INITIAL 64 K MINEXTENTS 1 MAXEXTENTS UNLIMITED PCTINCREASE 0 BUFFER_POOL DEFAULT) LOGGING NOCOMPRESS NOCACHE NOPARALLEL MONITORING / CREATE UNIQUE INDEX SCOTT.NOTNULL1 ON SCOTT.EMP (EMPNO) LOGGING TABLESPACE USERS PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE (INITIAL 64 K MINEXTENTS 1 MAXEXTENTS UNLIMITED PCTINCREASE 0 BUFFER_POOL DEFAULT) NOPARALLEL / </EXAMPLE_CODE><DB_VERSION>8i and greater</DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r6</WIKIREV><WIKITIMESTAMP>27 Feb 2012 - 21:26:32</WIKITIMESTAMP></RULE><RULE><ID>6610</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Test naming conventions global variables</DEF><RULE_TYPE>5</RULE_TYPE><CAT>64</CAT><SEVERITY>0</SEVERITY><XPATH_QUERY> //VAR_DECL/QNAME [../../../PACKAGE_SPEC][@value[not(starts-with(.,'g_'))]] </XPATH_QUERY><TIP_TEXT>Naming conventions standardize identifier names, which improves the consistency, readability, and maintainability of source code. For example, you can create naming conventions to standardize: * The number of characters used in a name * Prefixes and suffixes, such as appending _PK to a primary key column * Plurality, such as making all table names plural * Upper or lower case</TIP_TEXT><EXAMPLE_CODE>CREATE OR REPLACE PACKAGE emp_package AS /****************************************************************************** NAME: emp_package PURPOSE: REVISIONS: Ver Date Author Description --------- ---------- --------------- ------------------------------------ 1.0 9/29/2009 1. Created this package. ******************************************************************************/ AAAg_Variable NUMBER; AAAc_Conatant CONSTANT NUMBER := 5; FUNCTION MyFunction (Param1 IN NUMBER) RETURN NUMBER; PROCEDURE MyProcedure (Param1 IN NUMBER); PROCEDURE update_comm; END emp_package; CREATE OR REPLACE PACKAGE BODY emp_package AS /****************************************************************************** NAME: emp_package PURPOSE: REVISIONS: Ver Date Author Description --------- ---------- --------------- ------------------------------------ 1.0 9/29/2009 1. Created this package body. ******************************************************************************/ FUNCTION MyFunction (Param1 IN NUMBER) RETURN NUMBER IS BEGIN AAAg_Variable := 5; RETURN Param1; END; PROCEDURE MyProcedure (Param1 IN NUMBER) IS TmpVar NUMBER; BEGIN TmpVar := Param1; END; PROCEDURE update_comm IS TmpVar NUMBER; BEGIN UPDATE emp SET comm = comm + 1; INSERT INTO SCOTT.EMP (EMPNO) VALUES (1); COMMIT; SELECT 1 INTO tmpVar FROM DUAL; EXCEPTION WHEN NO_DATA_FOUND THEN NULL; WHEN OTHERS THEN -- Consider logging the error and then re-raise RAISE; END; END emp_package;</EXAMPLE_CODE><DB_VERSION>8i and greater</DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r6</WIKIREV><WIKITIMESTAMP>07 Mar 2012 - 15:52:04</WIKITIMESTAMP></RULE><RULE><ID>6811</ID><STATUS>4</STATUS><COLOR>Pink</COLOR><DEF>Avoid parameters or variables with embedded spaces.</DEF><RULE_TYPE>0</RULE_TYPE><CAT>64</CAT><SEVERITY>1</SEVERITY><XPATH_QUERY> //QNAME/IDENTIFIER[@value[contains(.,' ')]] </XPATH_QUERY><TIP_TEXT>Although technically allowed, variables and parameters with spaces is considered bad programming practice and should be avoided. Consider using the underscore character instead.</TIP_TEXT><EXAMPLE_CODE>CREATE OR REPLACE PROCEDURE sp_Test5 ( "Param With Spaces" IN NUMBER DEFAULT 0, O_Result_Set OUT pk_test.CURSOR_TYPE) AS "Var With Spaces" VARCHAR2 (255); BEGIN IF ("Param With Spaces" IS NULL) THEN raise_application_error (-20000, '"Param With Spaces" must be entered'); END IF; "Var With Spaces" := 'some value'; END sp_Test5; /</EXAMPLE_CODE><DB_VERSION>8i and greater</DB_VERSION><TIP_LINK></TIP_LINK><WIKIREV>r3</WIKIREV><WIKITIMESTAMP>16 Jan 2012 - 19:33:48</WIKITIMESTAMP></RULE><RULE><ID>2301</ID></RULE></CODE_ANALYSIS_RULES>
Copyright ©2021 || Defacer Indonesia