Professional Documents
Culture Documents
28/02/2005
TABLE OF CONTENTS
28/02/2005
Purpose:
PL/SQL code must be maintainable and professional. This means that it must be consistent and therefore must abide by certain standards. The standards will ensure that our product will be useful long after the current people building and maintaining it are around.
Introduction:
This documents contains two parts: Part A: PL/SQL Coding Standards Part B: PL/SQL Structure
28/02/2005
7. Code should be proper aligned. Left alignment for BEGIN EXCEPTION and END
block. Right alignment for SQL statement. Ex: BEGIN SELECT plan_id,eff_start_date,eff_end_date INTO l_plan_id,l_eff_start_date,l_eff_end_date FROM plan_master; EXCEPTION WHEN NO_DATA_FOUND THEN RAISE_APPLICATION_ERROR(-errcode,errormessage); END;
8. There should be proper comments for each block of code. 9. Exception should handle for each sql statement.
Ex: BEGIN SELECT plan_id,eff_start_date,eff_end_date INTO l_plan_id,l_eff_start_date,l_eff_end_date FROM plan_master; EXCEPTION WHEN NO_DATA_FOUND THEN RAISE_APPLICATION_ERROR(-errcode,errormessage); END;
28/02/2005
14. Use %TYPE and %ROWTYPE where ever possible. 15. Use standard error handling process to log all oracle errors. 16. User exception should be declared as e_exceptioname. 17. Always put WHEN OTHERS exception at end of each code block. 18. Use DBMS_OUTPUT.PUT_LINE ('sqltext- '||sqltext); for debug the code.
1. Standard indentation is four spaces. Our PL/SQL code is not only viewable in the
SQL files but also through our SQL and PL/SQL browsers. This means that we should try to make it as consistent as possible to all source code readers.
2. Lowercase everything (table name, field name, local variable name, parameters
name etc.) with the exception of %TYPE, %ROWTYPE and all KEY WORDS.
28/02/2005
We can quickly identify and fix any errors We can reliably modify or drop constraints
Why do we need a naming convention? Oracle limits names, in general, to 30 characters, which is hardly enough for a human-readable constraint name.
A.3.1. Abbreviations
We propose the following naming convention for all constraints, with the following abbreviations taken from the Oracle documentation. Note that we shortened all of the constraint abbreviations to two characters to save room. Constraint type references (foreign key) unique primary key check Not null Abbreviation Fk Un Pk ck nn
28/02/2005
keys
Naming primary keys might not have any obvious advantages. However, in Example B-2, the primary key helps make the SQL query clearer. SQL> set autotrace traceonly explain; SQL> select * from constraint_naming_example, example_topics where constraint_naming_example.topic_id = example_topics.topic_id; Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Optimizer=CHOOSE 1 0 NESTED LOOPS 2 1 TABLE ACCESS (FULL) OF 'CONSTRAINT_NAMING_EXAMPLE' 3 1 INDEX (UNIQUE SCAN) OF 'EXAMPLE_TOPICS_TOPIC_ID_PK' (UNIQUE)
28/02/2005
The SYS_C00140971 by itself provides no information as to which index is being used in this query, and more importantly, the name of this constraint will vary from database to database. Mark Lindsey (<lindsey@acm.org>) provided another good reason to name primary keys and unique constraints. Oracle creates an index for every primary key and unique constraint with the same name as the constraint. It is an unfortunate DBA who has to wrestle with storage management of tens of mysteriously-named indexes.
null
Constraints is Optional.
Red Hat Applications developers are undecided on whether or not to name not null constraints. If you want to name them, please do so and follow the above naming standard. Currently, naming not null constraints is not a requirement of WAF.
Note
Naming the not null constraints does not help immediately in error debugging (for example the error will say something like Cannot insert null value into column). We do recommend naming not null constraints to be consistent in our naming of all constraints.
Tip
An upgrade script should be written anytime that a change is made to a component's sql creation script that results in a different schema and/or data set. The new upgrade script should be written so that, when applied to the previous creation script, it results in the same schema and data set. Every component should have exactly zero or one upgrade script per release per supported database. However, an upgrade script may source other files. Upgrade scripts go in an upgrade/ directory below the directory containing the corresponding creation script. The name of the file should be < component-name>-<oldversion-name>-<new-version-name>.sql. Example:
Cms/sql/oracle-se/upgrade/cms-4.6.4-4.6.5.sql
Extending this process farther, there should be a single upgrade script for all of WAF per version. This upgrade script lives in kernel/sql/oracle-se/upgrade and is called coreplatform-<old-version-name> -<new-version-name>.sql.
28/02/2005
-- In order to use workflow list function TYPE c_RefCursors IS REF CURSOR; c_udp_engine c_RefCursors; c_null_calendars c_RefCursors;
--- Local variable declaration part -l_workflowlist VARCHAR2(1000) := ''; l_sqltext VARCHAR2(300); l_enginename varchar2(40); l_mp_engineid varchar2(40); l_release_calname l_planname VARCHAR2(200);
DATE;
l_standardflow INTEGER := 1;
-- Exceptions
28/02/2005
10
e_workflow_id_error Exception; -- Error Statements And Codes e_workflow_id VARCHAR2(1000) := 'Erroneous Workflow Identifier , Please Check the Input Id : '; e_update_error VARCHAR2(1000) := 'No Update Value Clause , Invalid query .. exiting'; e_lookup_error VARCHAR2(1000) := 'No Destination Look Up Field Specified , Please check Input Parameters'; --- cursor declaration part -CURSOR c_mst_location IS SELECT DISTINCT plan_id, location_id, ud_release_time_monday, ud_release_time_tuesday, ud_release_time_wednesday, ud_release_time_Thursday, ud_release_time_friday FROM out_location_release@MDM WHERE location_id IS NOT NULL
AND sys_ent_state='ACTIVE'; BEGIN -- Check the workflows For which the update needs to run - Applicable only in case l_workflowlist := NVL(F_workflowlist(i_workflow_id),'Error'); -- If the Workflow Id was Incorrect Raise An error IF l_workflowlist = 'Error' THEN
RAISE e_workflow_id_Error; END IF; -- Set All engines valid for Workflow to Null as part of deletion -- OPEN dynamic Cursor c_null_calendars OPEN c_null_calendars FOR 'SELECT DISTINCT enginename FROM UDT_ENGINEMASTER WHERE workflowid in (' || l_workflowlist || ')'; LOOP FETCH c_null_calendars INTO l_enginename; BEGIN L_sqltext:='UPDATE ' || i_odsschema || '.v_sitemaster SET UDF_RELEASE_CALENDAR_'|| l_enginename ||'= NULL'; EXECUTE IMMEDIATE l_sqltext;
28/02/2005
11
END; l_enginename:=NULL; EXIT WHEN c_null_calendars%NOTFOUND; END LOOP; COMMIT; l_enginename:=NULL; l_planname:=NULL; FOR c_mst_location_rec IN c_mst_location LOOP -- OPEN dynamic Cursor c_udp_engine OPEN c_udp_engine FOR 'SELECT DISTINCT enginename,mp_engineid,mp_planid FROM udt_enginemaster WHERE mp_planid='||''''|| c_mst_location_rec.plan_id ||''''||' and workflowid in (' || l_workflowlist || ')'; LOOP FETCH c_udp_engine INTO l_enginename,l_mp_engineid,l_planname; BEGIN EXECUTE IMMEDIATE 'SELECT a.plan_id,b.currentdate,b.effenddate FROM out_planmaster@MDM a,'|| i_odsschema ||'.planmaster b where a.plan_id=''' || l_planname ||''' AND a.plan_id=b.planid ' INTO l_planname,l_effstartdate,l_effenddate; EXCEPTION WHEN OTHERS THEN LOG_STANDARD_ERROR('OUT_PLANMASTER AND PLANMASTER','Plan Name retrival failed'|| sqlerrm, c_mst_location_rec.plan_id, l_jobname, 1, 'S3', l_startdate); END; l_release_calname:='RWC'||'.'||c_mst_location_rec.location_id||'.'|| l_planname; BEGIN l_sqltext:= 'UPDATE '||i_odsschema||'.V_SITEMASTER SET UDF_RELEASE_CALENDAR_'||l_enginename||' ='''||l_release_calname||''' WHERE SITEID='''|| c_mst_location_rec.location_id||'''';
28/02/2005
12
DBMS_OUTPUT.PUT_LINE ('l_sqltext- '||l_sqltext); EXECUTE IMMEDIATE sqltext; EXCEPTION WHEN OTHERS THEN LOG_STANDARD_ERROR('SITEMASTER','SITEMASTER RWC updation failed'|| sqlerrm,l_release_calname,l_jobname,1,'S3',l_startdate); END; COMMIT; EXIT WHEN c_udp_engine%NOTFOUND; END LOOP; l_enginename:=NULL; COMMIT; END LOOP; EXCEPTION WHEN e_workflow_id_Error THEN RAISE_APPLICATION_ERROR(-20001,'Unknown Error Occurred : ' || sqlerrm); ROLLBACK; WHEN OTHERS THEN ROLLBACK; RAISE_APPLICATION_ERROR(-20001,'Unknown Error Occurred : ' || sqlerrm); END; /
B.PL/SQL Structure
This contains details of PL/SQL structures and definitions.
B.1 Headers
B.1.1 Create Command in PL/SQL
The first part of any stored procedure, package, package body, or function in PL/SQL is the CREATE command. The CREATE command should use the OR REPLACE clause to ensure that new updates write over old code. You should place the variable declarations in the CREATE command on separate lines for readability, as well as the AS keyword. All IN only variables should come first, followed by the IN OUT or OUT variables. For anonymous blocks, the CREATE command is replaced with the DECLARE keyword
28/02/2005
13
28/02/2005
14
Procedure lengths should be set at a predetermined standard based on ensuring that developers modularize their code. Procedures much longer than 100 lines become hard to read. This is accomplished through compartmentalization of code into subprocedures and functions and through the use of cursors for multiple SELECT statements. A line of code is considered to be a single command, so even though a large SELECT, INSERT, or UPDATE type command spans several physical lines, it is only one line of code. For example, if the procedure in Listing 7 has been built in your schema or if you have execute privilege on it, the procedure can be called from other procedures to provide insert processing into i_temp: By using subprocedures you can reduce the space requirements in your procedures. There should be comments added into the processing body to show functions and explain special processing (if needed). See Listing 8 on the OReview Web site (www.oreview.com) for a code fragment using these techniques.
Listing 1. Example format for a PL/SQL header or footer. -- - Title: (name of script) - - Used by Application: (application the - - procedure/function/package belongs to) - - Purpose: (general purpose of script - keep it short) - - Limitations: (any prerequisites, privileges, grants, - - etc.) -- - Inputs: (list required inputs to procedure, - - function, etc.) - - Outputs: (list any output variables and their type) -- - History: - - Who: What: Date: - - (list changes to file) - - Notes: - - (List any special notes applicable to procedure, - - function, etc.)
28/02/2005
15
PROCEDURE subtract( name IN VARCHAR2, list IN VARCHAR2, lax IN BOOLEAN DEFAULT FALSE ); PROCEDURE subtract( name IN VARCHAR2, tab IN dbms_utility.uncl_array, lax IN BOOLEAN DEFAULT FALSE ); -- SUBTRACT some refreshable objects from a refresh group. PROCEDURE user_export_child( myowner IN VARCHAR2, myname IN VARCHAR2, mytype IN VARCHAR2, mycall IN OUT VARCHAR2);
28/02/2005
16
Listing 4. Other possible declarations with CREATE PACKAGE. CREATE PACKAGE admin.employee_package AS -- employee package contains objects for the HR application PROCEDURE new_emp( ename IN VARCHAR2, position IN VARCHAR2, supervisor IN NUMBER, category IN NUMBER, hiredate IN DATE, emp_no OUT NUMBER); -- The procedure new_emp adds a new employee and returns their emp_no value. PROCEDURE fire_them( emp_no IN NUMBER, reason IN VARCHAR2, term_date IN OUT DATE); -- The procedure fire_them removes an employee and returns their termination date PROCEDURE new_dept( emp_no IN NUMBER, dept IN VARCHAR2, new_dept IN VARCHAR2, date_of_change IN OUT DATE); -- The procedure new_dept changes an employees department and returns the date of change. FUNCTION get_status ( emp_no IN NUMBER) RETURN VARCHAR2; -- The function get_status takes in the employee number and returns their status. CURSOR c_get_emp ( emp_num NUMBER) IS SELECT ename, dept, status, hiredate, supervisor FROM emp WHERE emp_no=emp_num; -- The CURSOR c_get_emp is a general purpose cursor that returns all columns for a -- specified employee number entry. bad_category EXCEPTION; bad_date EXCEPTION; END employee_package;
*Header* *Create or Declare* CURSOR get_one IS(cursor definition); CURSOR get_two IS(cursor definition); The cursor definition should follow this general structure: CURSOR (cursor name) (parameter list) IS SELECT (list of columns) FROM (list of tables WHERE (list of clauses)
28/02/2005
17
ORDER BY (list of columns) FOR UPDATE OF (Other clauses) (list of tables, columns, etc.) ; Other clauses should be indented in a similar manner. Remember, the objective is to make the code easily readable. For example: CURSOR c_cons_cursor IS SELECT owner, constraint_name, decode(constraint_type,'C','CK_','V','DF_'), table_name, search_condition, r_owner, r_constraint_name, delete_rule FROM user_constraints WHERE owner NOT IN ('SYS','SYSTEM') AND constraint_type IN ('C','V') ORDER BY owner, constraint_type ;
*Header* *Create or Declare* *Cursors* - - Table Definitions -TYPE numtab IS TABLE OF NUMBER INDEX BY BINARY_INTEGER; tab_counts numtab; user_counts numtab; -- - Record Definitions -TYPE UserRecType IS RECORD (userno NUMBER(2), dname CHAR(14), loc CHAR(23)); user_rec UserRecType; -- - Rowtype Definitions -table_rec dba_tables%ROWTYPE -- - Type Definitions -tab_nam user_constraints.table_name%TYPE; cons_owner user_constraints.owner%TYPE;
28/02/2005
18
cons_name user_constraints.constraint_name%TYPE; -- - Local Variable Definitions -cons_type varchar2(11); all_columns varchar2(2000); counter integer:=0; cons_nbr integer;
Listing 7. Example of a sub-procedure. CREATE OR REPLACE PROCEDURE write_ind( p_line INTEGER, p_owner varchar2, p_name VARCHAR2, p_string VARCHAR2) IS BEGIN INSERT INTO i_temp ( lineno, id_owner, id_name, text) VALUES ( p_line, p_owner, p_name, p_string); END; Listing 8. Example code fragment using indentation, commenting and capitalization rules.
*Header* *Create or Declare* *Cursors* *Declarations* -- - Create a command set to rebuild a CREATE DATABASE command -BEGIN db_lineno:=db_lineno+1; SELECT 'CREATE DATABASE ' | | value into db_string FROM v$parameter WHERE name='db_name'; write_ind(db_lineno,db_string); <--- Note use of sub-program here db_lineno:=db_lineno+1; -- - Select the following command from dual since it isn't stored -SELECT 'CONTROLFILE REUSE' into db_string
28/02/2005
19
FROM dual; write_ind(db_lineno,db_string); db_lineno:=db_lineno+1; -- - Select the following command from dual since it isn't stored -SELECT 'LOGFILE (' into db_string FROM dual; write_ind(db_lineno,db_string); COMMIT; -- - Get redo log thread information -IF thread_cursor%ISOPEN THEN CLOSE thread_cursor; OPEN thread_cursor; ELSE OPEN thread_cursor; END IF; LOOP FETCH thread_cursor INTO thrd,grp; EXIT WHEN thread_cursor%NOTFOUND; db_lineno:=db_lineno+1; db_string:= 'THREAD '||thrd||' GROUP '||grp||' ('; write_ind(db_lineno,db_string); -- - Get Thread, group, member information -IF mem_cursor%ISOPEN THEN CLOSE mem_cursor; OPEN mem_cursor(grp); ELSE OPEN mem_cursor(grp); END IF; db_lineno:=db_lineno+1; begin_count:=db_lineno; LOOP FETCH mem_cursor INTO grp_member; EXIT WHEN mem_cursor%NOTFOUND; IF begin_count=db_lineno THEN db_string:=''''| | grp_member | |''''; write_ind(db_lineno,db_string); db_lineno:=db_lineno+1; ELSE db_string:=','| |'''| | grp_member| |'''; write_ind(db_lineno,db_string); db_lineno:=db_lineno+1;
28/02/2005
20
END IF; END LOOP; db_lineno:=db_lineno+1; db_string:=' )'; write_ind(db_lineno,db_string); END LOOP; db_lineno:=db_lineno+1; SELECT ')' INTO db_string FROM dual ; write_ind(db_lineno,db_string); COMMIT; -- - Get datafile information for the SYSTEM tablespace -IF dbf_cursor%ISOPEN THEN CLOSE dbf_cursor; OPEN dbf_cursor; ELSE OPEN dbf_cursor; END IF; begin_count:=db_lineno; LOOP FETCH dbf_cursor INTO filename, sz; EXIT WHEN dbf_cursor%NOTFOUND; IF begin_count=db_lineno THEN db_string:='DATAFILE '| |''''| | filename| |''''| |' SIZE '| |sz| |' REUSE'; ELSE db_string:=','| |''''| |filename| |''''| |' SIZE '| |sz| |' REUSE'; END IF; db_lineno:=db_lineno+1; write_ind(db_lineno,db_string); END LOOP; COMMIT; -- - Archive log data is stored as either a true or false setting, so we must decode -SELECT decode(value,'TRUE','ARCHIVELOG','FALSE','NOARCHIVELOG') INTO db_string FROM v$parameter WHERE name='log_archive_start'; db_lineno:=db_lineno+1; write_ind(db_lineno,db_string); SELECT
28/02/2005
21
';' INTO db_string FROM dual; db_lineno:=db_lineno+1; write_ind(db_lineno,db_string); CLOSE dbf_cursor; CLOSE mem_cursor; CLOSE thread_cursor; COMMIT; END;
Listing 9. Example of the format for an EXCEPTION statement. *Header* *Create or Declare* *Cursors* *Declarations* *Body* EXCEPTION WHEN NO_DATA_FOUND THEN INSERT INTO dba_temp VALUES ( stat_name,0); COMMIT; WHEN ZERO_DIVIDE THEN INSERT INTO dba_temp VALUES ( stat_name,0); COMMIT; Listing 10. Full-length code example.
Title: AddRmaNote Used by Application: RMA GUI Purpose: Add RMA notes Limitations: None Inputs: eventid, actseqnbr, user, notes Outputs: status: 1 No note added, 0 note added History:
28/02/2005
22
- - Who: What: Date: - - Mike Ault Added Header 12/16/96 - - Notes: - - None -CREATE OR REPLACE PROCEDURE AddRmaNote ( hv_eventid IN event.evntid%TYPE, hv_actseqnbr IN act.actseqnbr%TYPE, hv_user IN indiv.cuidnbr%TYPE, hv_notes IN VARCHAR2, hv_status IN OUT NUMBER ) AS -- - Local Variables --
-- - Body --
leid date1
NUMBER(10); DATE;
BEGIN hv_status := 0; leid := 0; BEGIN -- - Get the foreign key value of the leid from the - - indiv table for this user -SELECT fk_leleid INTO leid FROM indiv WHERE cuidnbr = hv_user ; EXCEPTION WHEN OTHERS THEN - If no entry then user doesn't have the leid value - required and no note will be added to RMA return a 1 - status hv_status := 1; END; - Get SYSDATE, note, an error of 1427 may indicate - multiple rows in dual SELECT sysdate INTO date1 FROM dual; -
28/02/2005
23
- - A leid number greater than zero indicates we need to - - add to the activity remark table (add an RMA note) -IF ( leid > 0 ) THEN INSERT INTO actvy_rmk ( actvyrmkdt, actvyrmktxt, fk_actfk_eventevnt, fk_actactseqnbr, fk_indivfk_leleid ) VALUES ( date1, hv_notes, hv_eventid, hv_actseqnbr, leid ); END IF; COMMIT; END AddRmaNote;
28/02/2005