PL/SQL Best Practices
As the discipline of software development evolves, software products grow in size and the code grows in complexity, the need for a set of guidelines and standards becomes increasingly evident. In this article I will enumerate some of the industry best practices that the experts have proposed and emphasized, in the context of PL/SQL programming.
These best practices improve our data marts, data warehouses and programming environments by introducing the features like manageability, maintainability and performance, regardless of the size of organization or the development effort. The list I have provided is neither exhaustive nor complete and I believe that producing such a list is very subjective given the fact that each software development effort and development organization is unique and not all the best practices are applicable in all situations. Here are some of the more common suggestions that you will find useful:
Use Anchored Declarations:
As Oracle 10G gains more ground, a term that we will all hear repeatedly is 'self-managing'. Oracle has incorporated features in this version that would reduce DBA intervention to manage it. It would be nice to introduce some 'self-management' features into the our PL/SQL code as well.
Developers and support personnel alike are faced, quite often, with a situation where the definitions of the tables have to be altered from one data type to another. In an ideal world nothing else would need modification. Realistically, this can break all the PL/SQL code that is based on this table. Modifying all this code can be a daunting task. Using anchored declarations can bring our ideal world a step closer to us.
Let's assume we have a table called ITEMS, defined as follows:
Name Null? Type --------------- ------------ -------- ITEM_ID NUMBER ITM_DESCRIPTION VARCHAR2(60) PRICE NUMBER(5,2) QTY NUMBER
Also, suppose there is a stored procedure that prints out an alert message when the quantity of an item in stock falls below a certain limit.
CREATE OR REPLACE PROCEDURE inventory_update (item_id_in IN NUMBER) IS v_qty NUMBER; BEGIN SELECT qty into v_qty FROM items WHERE item_id = item_id_in; IF (v_qty < 10 ) THEN DBMS_OUTPUT.PUT_LINE ('Warning: Stock low...'); END IF; END inventory_update;Figure 1: Anchored Declarations
What happens if we later decide to change the definition for the item_id field from NUMBER to VARCHAR2(20)? The code above will fail. In order to write the code in a way that it remains un-effected in the face of the changing data type of a column, replace the definition for the parameter item_id_in from NUMBER to an anchored data type items.item_cd%TYPE like this:
CREATE OR REPLACE PROCEDURE inventory_update(item_id_in IN items.item_id%TYPE) IS
Doing this will cause the code to have the same data type as that of the column in the table. Your code will work without any modification.
Use BULK Collects:
Performance tuning falls under the realm of DBA duties more than developers. However, developers have an equal stake in the performance and there is a lot that can be done at the code-level to achieve this goal. One of these is the use of bulk collects.
By using bulk collects, we can load multiple rows into the collections rather than one at a time thus reducing strain on database resources by reducing the pass to the database. The code segments in figure 2 and figure 3 demonstrate this point.
CREATE OR replace PROCEDURE no_bulk_proc is CURSOR item_cur IS SELECT items.item_id, qty FROM items; item_rec item_cur%ROWTYPE; BEGIN OPEN item_cur; LOOP FETCH item_cur INTO item_rec; EXIT WHEN item_cur%notfound; dbms_output.put_line(item_rec.item_id); dbms_output.put_line(item_rec.qty); END LOOP; END no_bulk_proc;Figure 2: Without using BULK collects
CREATE OR REPLACE PROCEDURE bulk_proc IS CURSOR item_cur IS SELECT item_id, qty FROM items; TYPE t_item IS TABLE OF ITEMS.item_id%TYPE INDEX BY BINARY_INTEGER; TYPE t_qty IS TABLE OF ITEMS.qty%TYPE INDEX BY BINARY_INTEGER; v_item t_item; v_qty t_qty; BEGIN OPEN item_cur; FETCH item_cur bulk collect INTO v_item, v_qty limit 100; FOR i IN v_item.first .. v_item.last LOOP dbms_output.put_line(v_item(i)); dbms_output.put_line(v_qty(i)); END LOOP; CLOSE item_cur; END bulk_proc;Figure 3: Using BULK collects
Here the 'limit 100' loads only 100 records into memory for processing which provides an added benefit that we have reduced the risk of running out of main memory as opposed to the code in figure 3 which may run into memory issues.
Carefully Select Your Control Structures:
Control flow and conditional statements, if-then-else and loops, are the basic building blocks of any programming language. Both these structures provide several different variations and forms to choose from. But in my observation as a programmer, I have noticed that people grow comfortable with using one kind of structure only and rarely do a comparison when implementing, especially in case of loop structures. Consider the following code in figure 4.
IF quantity <= 5 THEN Shipping_method := standard_shipping; END IF; IF quantity > 5 THEN Shipping_method := expedited_shipping; END IF;Figure 4: Multiple Conditional Statements
This is fine as long as the condition is different in each IF clause. But in our case it is obvious that either one of them will be true at any given time. However the way this code is written, both the condition will be checked resulting in an in-efficient module especially when there are more than a few such situations in the code. Use of ELSIF would improve this situation as shown in figure 5.
IF quantity <= 5 THEN Shipping_method := standard_shipping; ELSIF quantity > 5 THEN Shipping_method := expedited_shipping; END IF;Figure 5: Replacing IF with ELSIF
Here, either one of the two will be evaluate to true. In case it is the first one which evaluates to true the rest of the statement will be ignored.
Use of CASE statements, is a much better alternative to nested if-elsif statements, particularly when number of conditions to check for is large.
CASE quantity WHEN <=5 THEN shipping_method := standard_shipping; WHEN > 5 THEN shipping_method := expedited_shipping; ELSE shipping_method := 0 END CASE;Figure 6: Using CASE for mutually exclusive condition checking
Use Bind Variables:
A key to improved code performance is the use of bind variables. It is a simple technique yet powerful enough to improve performance of the PL/SQL code by several orders of magnitude, depending on the type and size of data. The reason for that lies in the manner DML statements are handled in Oracle. When an Oracle SQL statement is issued, the statement is parsed and saved in a shared memory area so that when the same statement is issued again by the program, the system can skip the parsing step, thus saving processing time and resources.
Consider the code in figure 7.
(The code given below is for the purpose of demonstration and comparison only and may not reflect real-life situation.)
DECLARE BEGIN FOR i IN 1 .. 1000 LOOP EXECUTE IMMEDIATE 'SELECT item_id, qty FROM items WHERE qty = ' || i; END LOOP; END;Figure 7: Without bind variables
Every iteration of the loop produces the following statements:
SELECT item_id, qty FROM items WHERE qty = 1;
SELECT item_id, qty FROM items WHERE qty = 2;
SELECT item_id, qty FROM items WHERE qty = 3;
Etc...
When the system checks the shared memory area (shared pool) it considers each statement to be unique and fails to make use of the parsed information already available. We, however, know that the statements are almost identical. Bind variables can help remedy this situation. Consider the code slightly modified code in Figure 3.
DECLARE BEGIN FOR i IN 1 .. 1000 LOOP EXECUTE IMMEDIATE 'SELECT item_id, qty FROM items WHERE qty = :x' using i; END LOOP; END;Figure 7: With bind variables
In this case the parser will get exactly the same information in every loop iteration and would not have to re-parse the SQL statement.
Not only that the code is faster with the use of the bind variables, it is also less CPU intensive and allows better sharing of resources by decreasing the number of latches resulting in not just better performing code but a better performing system as a whole.
As is evident from the code examples above, bind variables are applicable in situations where dynamic SQL is being used. Since today's graphical user interface programming makes very heavy use of dynamic SQL, there are more opportunities of realizing the advantage of bind variables.
Conclusion:
In my personal experience, the hi-tech community tends to ignore standards or a set of best practices if the list gets too long. Also, not all the best practices are applicable in all situations. A practical approach to this would be to keep the list of best practices small, prioritize items in that list, and once done, ensure that the list is referred to at appropriate times. It would be nice if at the end of every PL/SQL development effort the developers would go through a check list to verify if, and how closely, they have followed the best practices.
The need for good programming practices cannot be emphasized enough. This is, arguably, true for database applications more than any other type of programming. Bulk collects, anchored declarations, decision structures and bind variables are just a few of the items from the list of best practices that most experts have proposed to make PL/SQL code more manageable and efficient.
- Irfan Haq's blog
- Log in to post comments
Comments
Hi! this article is really beneficial and most importantly very easy to understand. I will be glad to receive such information.
regards,
Neel
I like the article because it cuts right to the point and conveys the info very quickly and it's based on simple example which beginners can follow. Good stuff.
The article is very good. Although the tips given are very common but generally we tend to ignore them. But my personal feeling is that if we use the database in a proper way it always will be 'self managing'
The Bulk collect information
The Bulk collect information is really a good one. Also liked the other articles. Waiting for more such interesting facts...
Thanks and regards,
Avirup
This article really helped
This article really helped me alot. especially, bind variable usage topic. the use of bind varibles has been clarified clearly.
thank u very much Mr.Irfan
-- dhanunjay
Great!
That was an awesome write-up..
Three cheers to the writer, for inspiring us to write manageable code!
We need more of such articles
These PL/SQL Performance improvement methods are very good, and are helping developers world wide. It would be great if we get some more of such techniques.
Issue: extra condition
I think that the below code could have been written even better if instead of
we did this
Anything that is not less than 5 or equal to 5 will be greater than 5 only. Why do we need to evaluate then
extra condition
Hi Vonid,
We need to evaluate if quantity > 5 because quantity might be NULL.
Jeff
extra condition
Jeff,
I could not see a point to compare a variable that can hold NULL to a numeric value. If you think that the value could be NULL then there should be a check before comparing with 5 for IS NULL. This would be a proactive measure.
condition ordering
Not a pl/sql expert yet, but certainly in other languages it would be normal to test in descending order of likelihood, thus minimising the number of tests performed:
IF my_favourite = c_polled_most_popular THEN
-- often run
ELSIF my_favourite = c_polled_but_unpopular THEN
-- sometimes run
ELSE -- not polled,
-- seldom run
END IF;