27
PL/SQL Best Practices 25+ Best Practices For Your PL/SQL Development Ben Brumm www.databasestar.com

PL/SQL Best Practices

  • Upload
    others

  • View
    1

  • Download
    0

Embed Size (px)

Citation preview

 

 

 

 

PL/SQL Best Practices  

 

 

 

 

 

25+ Best Practices For Your PL/SQL Development  

 

 

 

 

 

 

 

 

 

 

Ben Brumm 

www.databasestar.com 

 

 

PL/SQL Best Practices 

 

Welcome! 

Thanks for purchasing this PL/SQL Best Practices guide. 

In this guide you'll find a range of tips for writing and improving your PL/SQL code. 

Some of them are pretty firm tips, or things that you really should not do if you want efficient code, such as running SQL statements individually inside loops. 

Other tips can be tweaked or applied if you and your team are happy to, such as using explicit cursors or using PLS_INTEGER. 

The tips are split into different sections to make it easier to navigate and understand. 

If you have any questions about this guide, contact me at [email protected].  

Coding Style and Functions 

 

Write as Little Code as Possible  

You're probably wondering why the first tip in a guide to PL/SQL best practices is about writing less code. 

Oracle has included a lot of functionality in the built-in PL/SQL functions and procedures. This code has been developed and tested by Oracle, and is likely used by many other PL/SQL developers. 

Before you start to write your own code to do something, first see if there is anything in the standard PL/SQL codebase that will do what you need to do. If you can use a built-in feature, use it, instead of writing your own. 

Reducing the amount of code you write also applies to the code you actually write. The more code you write, the more complex it is, and the higher chance you have of inserting bugs. 

The rest of this guide highlights a few ways that you can reduce the amount of code you write, but here are a few: 

● Consider a way to use a single SQL query instead of a loop. ● If you write a piece of functionality more than once, move it to another procedure and call that 

procedure each time. ● Consider declaring a new type if you find yourself declaring several variables when working 

with data. 

 

Follow Your Team's Coding Standards  

Each team will have defined a set of coding standards, which is a way of writing code that the team can follow. 

 

www.DatabaseStar.com  

 

PL/SQL Best Practices 

 

In many languages, including PL/SQL, there are a lot of variations in the way that things can be done. Names of variables, types of loops, and structure of code are just some of the things that can vary. 

You may have your own preference for how things should be done. If you're working by yourself, then you should define your own set of standards to follow. If you're working in a team, then the team should agree on a set of standards to follow for PL/SQL code. 

If the code standards are followed, the code is easier to maintain, easy for others to read, and easy for you to work with now and in the future. 

Some standards to consider are: 

● Names of functions and procedures should be named using verbs (to define an action being taken) or using nouns (to define the result being returned). 

● Indentation of code and the number of spaces to use ● Line breaks when calling long procedures ● How to name and abbreviate packages ● How to name variables, and whether you use a prefix in the variable name 

 

The thing to remember here is that it's more important to be consistent than to be right. If you personally don't agree with the standard that the team is using, feel free to bring it up with them. However, if the team sticks to the standard, then you should follow it, rather than follow your own standard. 

 

Avoid Using OUT Parameters with Functions  

Functions in PL/SQL can have parameters, and parameters can have different types: 

● IN: A parameter passed in to the function ● OUT: A parameter that is passed out from the function to the function that called it ● IN OUT: A parameter that is both passed in and out. 

Using OUT parameters can be useful for procedures. However, functions are often written to be used in SQL and therefore use the "return" keyword which defines when to return to the calling code and what value to return. 

Functions should only ever return values using the return keyword and not OUT parameters. 

An example of a function that returns a value using RETURN is: 

CREATE FUNCTION subtract_numbers(p_num1 IN NUMBER, p_num2 IN NUMBER) AS

BEGIN

RETURN p_num1 - p_num2;

END;

 

 

 

www.DatabaseStar.com  

 

PL/SQL Best Practices 

 

Ensure Functions Always Have a Return  

When you write a function, ensure that it always has a return statement as the last executable line of the function. This is so it's clear that the function returns something, and that the code that calls this function always gets a result. 

A function without a return looks like this: 

CREATE FUNCTION subtract_numbers(p_num1 IN NUMBER, p_num2 IN NUMBER) AS

num_result NUMBER(10);

BEGIN

num_result := p_num1 - p_num2;

END; 

Adding a return to the function means it looks like this: 

CREATE FUNCTION subtract_numbers(p_num1 IN NUMBER, p_num2 IN NUMBER) AS

num_result NUMBER(10);

BEGIN

num_result := p_num1 - p_num2;

RETURN num_result;

END; 

 

Use Packages For All PL/SQL Code  

When you start learning PL/SQL, you learn how to create procedures and functions. Later on you learn how to create packages. 

Packages are collections of related PL/SQL code. They work similar to classes or modules in other programming languages. You can include many procedures, functions, and variables inside packages. Code from packages can reference code from other packages. 

Why would you use packages for your code and not standalone functions and procedures? 

First, it organises your code. Once you start getting above a certain number of functions and procedures, it gets hard to navigate them in your IDE and hard to find them when you're coding. Adding them into packages makes this easier. 

It also encourages encapsulation. You can include all of the code required to interact with data in the database inside a package, and then expose the functions required to other applications or users. This means the access to the database is consistent. 

Using packages also allows you to do several things: 

 

www.DatabaseStar.com  

 

PL/SQL Best Practices 

 

● You can define some parts of your code as public (accessible by others) and other areas as private (accessible only by your code). This improves the quality of your code. 

● It's easier to debug and enhance, because code is split into functional areas ● You can change the way that the underlying code works or how the data is accessed without 

impacting anyone who interacts with the package. Yes, this works regardless of you having a package or not, but it's easier with a package. 

Steven Feuerstein, an author of many books on PL/SQL and the Developer Advocate for PL/SQL at Oracle, recommends using packages for all of your code (which is where I got this tip from). 

 

   

 

www.DatabaseStar.com  

 

PL/SQL Best Practices 

 

Variables 

 

Use Constants Instead Of Hardcoded Values  

Have you ever used a hardcoded value inside your PL/SQL program? 

I've done this many times. 

Here's one example: 

order_tax_amount := sales_total * 0.15; 

This is multiplying the sales_total by 0.15 to arrive at an order tax amount. 

But what does the 0.15 represent? If you were to guess, you might say the tax amount, given that the variable the calculation is assigned to is called order_tax_amount. Or it could be the number of orders that are taxed. Or some other value. 

It's a hardcoded value in your code, otherwise known as a "magic value". This is something to be avoided, not only in PL/SQL but in programming in general. To understand what this calculation is doing, we need to make some assumptions about the values. We should avoid making assumptions. 

How can we fix this? 

We can use a constant. We define a variable, give it a name, and set it to the value that we are using in our code. We then use that constant instead of the hardcoded value. 

DECLARE

sales_tax_rate CONSTANT NUMBER(4, 2) := 0.15;

BEGIN

//some code

order_tax_amount := sales_total * sales_tax_rate;

//some more code

END;

 

This code shows that a new variable has been declared, called sales_tax_rate. I've called it that so it's clear what it refers to. It's also a CONSTANT, which means the value can't be changed while the code is running. It's been set to a value of 0.15, which is the value used in our code. 

Further down in the code, the order_tax_amount is set to the sales_total * sales_tax_rate. This is a lot clearer. It's easy to see what is being done here. 

Another benefit of this is that it's easy to change the value being used if it's declared as a variable. Does the sales_tax_rate change need to change to 0.17? If so, you can just change it in one place: 

 

www.DatabaseStar.com  

 

PL/SQL Best Practices 

 

DECLARE

sales_tax_rate CONSTANT NUMBER(4, 2) := 0.17;

BEGIN

//some code

order_tax_amount := sales_total * sales_tax_rate;

//some more code

END; 

If it's not in a variable, you'll have to search your code for all instances of 0.15, check if it really is referring to the tax rate or some other value, and then update it. 

 

Declare Variable Types to Match Tables and Columns  

If you need to declare a variable and it's related to a column in your database, it might be tempting to find what the type of that data is and declare it as the same type. 

For example, you may have a table like this: 

CREATE TABLE employee (

id NUMBER(10),

last_name VARCHAR2(100)

);

If you want to store a last_name in your PL/SQL code, you might do this: 

DECLARE

new_last_name VARCHAR2(100);

BEGIN

//some code

UPDATE employee

SET last_name = new_last_name

WHERE id = 1;

END; 

This makes sense, as you've declared the variable as the same type and size of the column in the table. 

But what if the size changes? For example, you may discover that 100 characters is not enough to store a last_name, so you increase the database column to 200 characters. Two things could then happen: 

 

www.DatabaseStar.com  

 

PL/SQL Best Practices 

 

1. You'll then need to search your PL/SQL code to update all references to any variables to use the new size, then test your code. 

2. You don't update any code and the code causes an error, because the data now doesn't match. 

Both of these outcomes are not ideal. There is a better way though. 

The way to avoid this issue is to declare your variables to synchronise with the type and size of the database columns. You can use the %TYPE keyword to do this. 

For example, you can change the PL/SQL code to ensure the new_last_name variable matches the employee.last_name column type by modifying the declaration like this: 

DECLARE

new_last_name employee.last_name%TYPE;

BEGIN

//some code

UPDATE employee

SET last_name = new_last_name

WHERE id = 1;

END; 

We've replaced the VARCHAR2(100) with a reference to the employee.last_name column, and used the %TYPE keyword. This means it uses the same data type as that column. 

For more information, look at chapter 6 in my PL/SQL Tutorial series of blog posts. 

 

Use PLS_INTEGER For All Integers  

If you're using whole numbers (integers) in your code, consider declaring them as a PLS_INTEGER data type instead of a NUMBER data type. 

PL/SQL can perform mathematical operations and other processes on the PLS_INTEGER data type faster than it can on the NUMBER data type if it's a whole number. 

In versions of Oracle before 10g (so quite a few years ago now), there was a big difference in performance between PLS_INTEGER and BINARY_INTEGER, with PLS_INTEGER being much faster. Since Oracle 10g, both data types are exactly the same. 

So, if you're working with integers, use PLS_INTEGER rather than NUMBER. 

 

Avoid Using Table Names or Column Names for Variable Names  

PL/SQL lets you choose names for your variables. It's a good idea to choose a name that is descriptive and meets the team's coding standards (as mentioned above). 

 

www.DatabaseStar.com  

 

PL/SQL Best Practices 

 

You might come across a situation where you want to name a variable with the same name as an existing column or table. If you do this, you can get compilation errors in your code, or errors when you run your code. Even worse is you might not get any errors at all but your code doesn't do what you expect it to do. 

So, if you have a table called employee and code that looks like this: 

DECLARE

employee VARCHAR2(200);

BEGIN

//some code

END;

You might encounter issues when running this code, because you have a table called employee and a variable called employee. 

The better approach is to name your variable what it represents. Does it represent an entire record? Give it a name that indicates that, such as rec_employee. Does it represent a particular value of an employee, such as a number or name? Name the variable to indicate that, such as employee_firstname. 

DECLARE

employee_firstname VARCHAR2(200);

BEGIN

//some code

END;

This will avoid errors in your code and undesirable outcomes due to confusion with your names. 

 

Avoid Setting the Default Value of Variables to NULL  

When you declare a variable in PL/SQL, you have the option of assigning it a value. Sometimes, it can be empty. 

DECLARE

first_name VARCHAR2(100) := NULL;

BEGIN

//some code

END;

This is often done in other programming languages to ensure that a variable is empty when it is declared. 

 

www.DatabaseStar.com  

 

PL/SQL Best Practices 

 

However, in PL/SQL, it is not required. PL/SQL automatically assigns the value of NULL to variables by default. So, remove any assignments of variables to NULL by default. 

DECLARE

first_name VARCHAR2(100);

BEGIN

//some code

END;

 

   

 

www.DatabaseStar.com  

 

PL/SQL Best Practices 

 

Loops 

 

Avoid Exiting From a FOR or WHILE Loop  

PL/SQL lets you use several different types of loops. Two of these are the FOR loop and the WHILE loop. Both of these loops run until a certain condition is met. 

A FOR loop looks like this: 

BEGIN

FOR l_loop_counter IN 1 .. 5 LOOP

DBMS_OUTPUT.PUT_LINE('Loop number: ' || l_loop_counter);

END LOOP;

END; 

A WHILE loop looks like this: 

DECLARE

l_loop_counter NUMBER(3);

BEGIN

WHILE (l_loop_counter < 5) LOOP

DBMS_OUTPUT.PUT_LINE('Loop number: ' || l_loop_counter);

END LOOP;

END; 

PL/SQL allows you to use an EXIT command to exit from a loop, or a RETURN command to exit from the entire procedure. I would suggest avoiding both of those commands inside a FOR or WHILE loop. 

A FOR loop and a WHILE loop are created to run until a certain criteria is met. It's better to let the criteria define the exit, rather than specify the exit yourself. If you want to exit a FOR loop at a certain point, perhaps you should be using a WHILE loop and refer to the condition in the WHILE loop. 

If you want to exit a WHILE loop at a certain point, ensure the criteria for the WHILE loop to keep running is false, which will cause the WHILE loop to end. 

Avoid using either EXIT or RETURN statements inside these loop types. 

 

Don't Declare the Loop Index in a FOR Loop  

 

www.DatabaseStar.com  

 

PL/SQL Best Practices 

 

A FOR loop is declared in PL/SQL to execute a number of times and perform the same action. An example of a FOR loop is: 

DECLARE

L_loop_counter NUMBER(3);

BEGIN

FOR l_loop_counter IN 1 .. 5 LOOP

DBMS_OUTPUT.PUT_LINE('Loop number: ' || l_loop_counter);

END LOOP;

END;

Notice that the variable l_loop_counter is declared before it is used in the FOR loop. This seems normal and is a common practice in many programming languages. 

However, in PL/SQL, it's not necessary. The FOR loop in PL/SQL will implicitly declare this variable when it runs the FOR loop, so you don't need to declare it separately. This code will perform the same thing without the variable declaration. 

BEGIN

FOR l_loop_counter IN 1 .. 5 LOOP

DBMS_OUTPUT.PUT_LINE('Loop number: ' || l_loop_counter);

END LOOP;

END;

 

Avoid Writing a Lot of Code Inside a Loop  

Loops in PL/SQL are a powerful way to perform the same action many times. However, they can get out of hand quite quickly. 

The more code you have inside the loop, the more that needs to be done each time the loop is run, which often means the slower the code will run. 

So, for each line of code you have inside the loop, consider if you really need to run it inside the loop. One common example is counting records. In many cases, you don't need to manually increment a counter inside a loop. You can count the result of the loop after it has run, avoiding the need to count it in each loop iteration. 

That's just an example, and the decision would depend on what your code is doing, but this is something to consider for your code. 

 

Don't Update Data using SQL Inside a Loop  

 

www.DatabaseStar.com  

 

PL/SQL Best Practices 

 

This is a common issue found in code written by those new to PL/SQL, or those who don't know another way of doing the same thing. 

Loops often run and perform different checks and calculations. They can be quite useful. It's also possible to run an SQL statement inside a loop that updates data in your database (INSERT, UPDATE, or DELETE statement). 

For example, your code might look like this: 

DECLARE

desk_section VARCHAR2(100);

BEGIN

desk_section := 'North';

FOR l_loop_counter IN 1 .. 5 LOOP

DBMS_OUTPUT.PUT_LINE('Loop number: ' || l_loop_counter);

UPDATE employee

SET desk_no = desk_section || '-' || l_loop_counter

WHERE id = l_loop_counter;

END LOOP;

END;

This code runs a loop and updates the employee table with a new desk number. 

However, the code runs the loop 5 times and runs 5 separate UPDATE statements. The code switches between running PL/SQL code (the loop) and running SQL code (the UPDATE statement). This is called a context switch, and doing too many of them can have an impact on performance. 

This kind of code is something to avoid. This code processes each row individually, running separate UPDATE statements. There are many switches between SQL and PL/SQL. 

A better way to do this is to move the UPDATE statement outside of the loop: 

DECLARE

desk_section VARCHAR2(100);

BEGIN

desk_section := 'North';

FOR l_loop_counter IN 1 .. 5 LOOP

DBMS_OUTPUT.PUT_LINE('Loop number: ' || l_loop_counter);

END LOOP;

UPDATE employee

SET desk_no = desk_section || '-' || id;

 

www.DatabaseStar.com  

 

PL/SQL Best Practices 

 

END;

This code now runs only one UPDATE statement and has the same result, but is much faster. 

It's a simple example, but the concept applies to any PL/SQL code. Avoid using SQL statements that update your data inside loops. 

 

   

 

www.DatabaseStar.com  

 

PL/SQL Best Practices 

 

Errors 

 

Use Consistent Error Management Techniques  

Managing and handling errors in PL/SQL code is often forgotten. We assume our code works well and our users will behave as we expect them to. However, both of those are often not true. 

If we do get around to writing code to handle errors, it's often handled inconsistently, resulting in a range of methods of logging and handling errors. 

A good approach is to define a set of error handling standards for you and your team to follow. This would mean: 

● A single package is created to handle all error functionality. ● Procedures inside this error package are used to define the error codes, messages, and 

treatment of errors. ● Developers never need to assign names to error numbers, or call RAISE APPLICATION ERROR 

with hardcoded numbers and text. 

This way, all that a developer needs to do is to call a specific function in the error package if an error is encountered (or create the function if it does not yet exist). The code is consistent and easier to debug. 

 

Avoid Using Exceptions for Branching Logic  

When you write exception code in PL/SQL, the code will be run if an exception is found. It's a form of branching, but it should only be used for errors or exceptions in your code. 

Avoid declaring your own exceptions and using the exception section to perform branching. This makes the application harder to maintain and debug. 

If you want to implement branching, use an IF statement or a CASE statement. 

So, instead of this code: 

DECLARE

ex_more_than_ten EXCEPTION;

BEGIN

//some code

IF (record_count > 10) THEN

RAISE ex_more_than_ten;

ELSE

//do something

 

www.DatabaseStar.com  

 

PL/SQL Best Practices 

 

END IF;

EXCEPTION

WHEN ex_more_than_ten THEN

//do something valid with more than ten records

END;

 

 

Use code like this: 

BEGIN

//some code

IF (record_count > 10) THEN

//do something valid with more than ten records

ELSE

//do something

END IF;

END; 

 

Do Something When An Error Is Found  

When you write some code and you expect an exception to be found, it can be tempting to do nothing with the error. 

However, it's a good practice to do something with it, because: 

● It helps you find any issues when you're debugging the code later ● It helps the users that use the system 

When you write an exception handler into your code, consider performing the following actions: 

● Write a message to your log table ● Perform a user-friendly task, such as using a friendly error message 

 

Log Errors Before Re-Raising Them  

So you've written an exception handler into your code to handle any errors that come up. But your code looks something like this: 

 

www.DatabaseStar.com  

 

PL/SQL Best Practices 

 

EXCEPTION

WHEN OTHERS THEN

RAISE;

END;

This is bad code because it doesn't help you find the source of the error when you're looking into an issue. The error is found but then it's just raised to the procedure that calls it. 

It's OK to re-raise the error, but you should log the error before doing this, so you can determine where the error actually happened. 

EXCEPTION

WHEN OTHERS THEN

your_error_logger.log_error(some_parameters);

RAISE;

END;

Ideally you would use your own error logging package and function that you have developed, as mentioned in an earlier tip. 

 

   

 

www.DatabaseStar.com  

 

PL/SQL Best Practices 

 

SQL 

 

Write Less SQL and Use a Data Access Layer  

It's possible to write SQL inside your PL/SQL code, and it's something you'll do quite regularly. PL/SQL lets you write SQL to retrieve values from the database and perform additional processing on them. You can also insert or update values in a database using standard DML queries, and much more. 

One recommendation to using SQL within PL/SQL code is to not repeat the same SQL statements in your PL/SQL. Having the same SQL statements in multiple places is a form of code duplication and can be hard to track down errors. 

A better solution is to use a separate set of procedures for running SQL. This is often called a "data access layer". You have a set of procedures, inside a package, each of which will perform a separate SQL statement. The results of some procedures will be a set of records returned by a SELECT statement, and other procedures will take parameters and perform inserts, updates, or deletes. 

So, instead of having code that looks like this: 

//Procedure 1

BEGIN

//some code

SELECT first_name, last_name, salary

INTO l_fn, l_ln, l.salary

FROM employee

WHERE id = p_id;

//more code

END;

//Procedure 2

BEGIN

//some code

SELECT first_name, last_name, salary

INTO l_fn, l_ln, l.salary

FROM employee

WHERE id = p_id;

//more code

 

www.DatabaseStar.com  

 

PL/SQL Best Practices 

 

//much more code

END;

 

You could have a separate procedure: 

CREATE PROCEDURE select_employee (p_id IN NUMBER, emp_record OUT employee%ROWTYPE) AS

BEGIN

SELECT first_name, last_name, salary

INTO emp_record

FROM employee

WHERE id = p_id;

END;

Your two procedures would then look like this: 

//Procedure 1

BEGIN

//some code

select_employee(p_id, emp_record);

//more code

END;

//Procedure 2

BEGIN

//some code

select_employee(p_id, emp_record);

//more code

//much more code

END;

This way, the employee record is only selected in one place, and you can easily find where issues are coming from and where the code is slowing down. 

 

 

www.DatabaseStar.com  

 

PL/SQL Best Practices 

 

Use Explicit Cursors Instead Of Implicit Cursors  

Using SELECT INTO is quite easy to store data from your database into variables. This is called an implicit cursor, because Oracle declares a cursor for you in the background. 

Within teams it's often debated whether to use an implicit cursor like this or use an explicit cursor (where you declare the cursor and fetch data into it). 

I would recommend using explicit cursors for a few reasons. 

First, you have more control over it. You have a name for the cursor and can use some of PL/SQL's cursor features on it. 

Secondly, you can follow a standard. Rather than using implicit cursors sometimes and explicit cursors for other times, using explicit cursors in all cases makes it easy to follow a standard. 

 

Avoid Using SELECT COUNT(*)  

A SELECT COUNT(*) query will show you how many rows are in your table. This is helpful when investigating a table, but in code meant for production, it can be slow and unnecessary. 

If you need to know whether there is more than one match, you can fetch a second row using an explicit cursor. 

 

Don't use SELECT FROM DUAL in PLSQL code?  

The concept of SELECT FROM DUAL is useful when writing SQL to get a result from functions like SYSDATE that don't need a table. However, you don't need to use it in production SQL code or even in PL/SQL code. 

SELECT SYSDATE

FROM dual;

However, there is no need to use SELECT FROM DUAL in PL/SQL anymore. You don't need to do this anymore: 

SELECT SYSDATE

INTO some_variable

FROM dual;

It's easier and better for performance to do this: 

some_variable := SYSDATE;

This is because it avoids running a SELECT query just to get the value of a function. This can work with any function, not just SYSDATE. 

 

www.DatabaseStar.com  

 

PL/SQL Best Practices 

 

So, if you have any reference to the DUAL table in your PL/SQL, it's likely not necessary. 

 

Don't use WHEN NO_DATA_FOUND For Queries Other Than SELECT  

If you use a SELECT query in your PL/SQL code, you can use WHEN NO_DATA_FOUND to check if no data was found in your exception section: 

DECLARE

l_id employee._id%TYPE;

BEGIN

SELECT id

INTO l_id

FROM employee

WHERE first_name = 'Jooohn';

EXCEPTION

WHEN NO_DATA_FOUND THEN

DBMS_OUTPUT.put_line ('No results found');

END; 

This works well. But what if you use an SQL query that is not a SELECT query? You can try to use an UPDATE query with a NO_DATA_FOUND exception: 

BEGIN

UPDATE employee

SET last_name = 'Smith'

WHERE first_name = 'Jooohn';

EXCEPTION

WHEN NO_DATA_FOUND THEN

DBMS_OUTPUT.put_line ('No results found');

END;

However, you'll never see that output message, because PL/SQL does not raise a NO_DATA_FOUND error for non-SELECT statements. 

It's better to use the sql%ROWCOUNT attribute. 

BEGIN

UPDATE employee

 

www.DatabaseStar.com  

 

PL/SQL Best Practices 

 

SET last_name = 'Smith'

WHERE first_name = 'Jooohn';

IF sql%ROWCOUNT = 0 THEN

DBMS_OUTPUT.put_line ('No results found');

END IF;

END;

 

   

 

www.DatabaseStar.com  

 

PL/SQL Best Practices 

 

Other 

 

Process Data in Bulk using FORALL and BULK COLLECT  

Every time you switch between PL/SQL and SQL in your code, it incurs a performance hit. This might not seem like much, but if you're working with hundreds or thousands of rows, the performance can really add up. 

Consider a program like this where you are selecting data, doing something to the data, and updating a table: 

BEGIN

FOR current_rec IN (SELECT fname

FROM person) LOOP

DBMS_OUTPUT.PUT_LINE('Name: ' || current_rec.fname);

UPDATE person p

SET p.salary = p.salary + 1000

WHERE p.fname = current_rec.fname;

END LOOP;

END; 

This code runs a SELECT statement and for each result that is found, an UPDATE statement is run. This results in many update statements being run, one for each record. 

A far better way to do this is to use two PL/SQL features: 

● BULK COLLECT for selecting data ● FORALL for performing the same code on all results 

You'll need to write a bit more code for this, but it generally performs better: 

DECLARE

TYPE name_type IS TABLE OF person.fname%TYPE;

name_list name_type;

BEGIN

SELECT fname

BULK COLLECT INTO name_list

FROM person;

FORALL i IN 1 .. name_list.count

 

www.DatabaseStar.com  

 

PL/SQL Best Practices 

 

UPDATE person p

SET p.salary = p.salary + 1000

WHERE p.fname = name_list(i);

END; 

The changes we have made here are: 

● We’ve declared a new TYPE called name_type, which is a table of values of the same type as the person.fname column. 

● We’ve declared a new variable based on that type, called name_list. ● We’ve run a SELECT statement which selects the fname column into the name_list variable, 

using BULK COLLECT. This means all values from the query are added into this collection. ● We've added a FORALL loop to UPDATE the data for each record. 

The loop is then run based on the data already loaded into the name_list variable. 

This new code will likely perform better than running the same operations individually. 

 

Prepare Automated Tests For Your Code  

One of the best ways to improve the quality of your code is to write and run automated tests on it. This is true for application code as well as database code. This concept of automatically testing your code is part of a concept called Continuous Integration and it's something I'm a big advocate of. 

To improve the quality of your code and reduce the number of bugs found in your code, consider writing: 

● Unit tests, which test individual procedures ● Integration tests or component tests, which test an entire process end-to-end 

This can be done with various PL/SQL tools, including utPLSQL, PLUTO, PL/Unit, DbFit, and Quest Code Tester for Oracle. 

 

Ensure Your Code is Included in Version Control  

Version control for application code (such as C# or Java code) is easier because the source code files are what is used to run the code. It's a bit harder with PL/SQL because the compiled code is stored on the database. 

It's important to store the code in version control so that you always have the most up to date version of the code in a single place, and can roll back to earlier versions if needed. 

This can take a bit more work (getting the code from source control rather than the database), but it's a good habit to get into, and it will save you time in the long run. 

 

www.DatabaseStar.com  

 

PL/SQL Best Practices 

 

Version control also captures the history of your code (if you're checking in regularly, which you should), so you can go back to a previous version of the code. 

 

Be Careful with ELSIF Logic  

When using multiple IF statements in one set of code, you need to use ELSIF. Pay attention to the values you use in your ELSIF statements and ensure they are not overlapping each other. 

For example, the criteria in this IF statement overlaps: 

IF salary BETWEEN 0 AND 40000 THEN

//run some code

ELSIF salary BETWEEN 40000 AND 80000 THEN

//run some other code

ELSIF salary BETWEEN 80000 AND 150000 THEN

//run code

ELSE

//run more code

END IF;

This can be useful if you need to cater for numbers that may have decimals. For example, starting your second ELSIF with BETWEEN 40001 will exclude any numbers between 40000 and 40001. This kind of check can be a good thing, so just be aware of the criteria you're using. 

If you're only working with whole numbers, then consider not overlapping your conditions, as it's not clear what will happen on overlap. If a salary is 80000, what happens then? In this code, the second condition is true, but it might seem like the third condition is true. 

 

Only Process Your Results Once  

When creating your PL/SQL code, you often need to load results from the database using a SELECT statement. It's good practice to minimise the amount of individual SQL queries that are run, and one way to do that is to only process your results once. 

This means only run one SQL query on a table if you can, not several queries to get different data. 

It also means only running one UPDATE statement to update a table, not several UPDATE statements to update different data. Try to combine queries if you can, or use other techniques such as FORALL to run many queries at once. 

 

 

 

www.DatabaseStar.com  

 

PL/SQL Best Practices 

 

Consider Using Boolean Logic Instead of Number or Char  

Older versions of Oracle database did not include boolean logic in PL/SQL. This meant that in order to use a boolean, you had to declare a variable as a one-character CHAR or VARCHAR2, or use a one-digit NUMBER. 

In recent versions, you can use a BOOLEAN data type for this. BOOLEAN can store true, false, or NULL. So, rather than checking a number or a char value, you can use boolean. 

So, instead of this code which uses a CHAR: 

CREATE PROCEDURE CHECK_VALID(p_num IN NUMBER) AS

result_status CHAR(1);

BEGIN

IF (p_num > 100) THEN

result_status := 'Y';

ELSE

result_status := 'N';

END IF;

RETURN result_status;

END;

 

Use this code which uses BOOLEAN: 

CREATE PROCEDURE CHECK_VALID(p_num IN NUMBER) AS

result_status BOOLEAN;

BEGIN

IF (p_num > 100) THEN

result_status := TRUE;

ELSE

result_status := FALSE;

END IF;

RETURN result_status;

END;

Boolean logic also allows you to reduce the complexity of your code: 

CREATE PROCEDURE CHECK_VALID(p_num IN NUMBER) AS

 

www.DatabaseStar.com  

 

PL/SQL Best Practices 

 

BEGIN

RETURN p_num > 100;

END;

 

Avoid Nested Triggers In Your Code  

The final tip I have is to avoid triggers that call other triggers. 

Triggers are powerful features in PL/SQL. They are pieces of PL/SQL code that are executed whenever a particular action is taken on a specified table. One example of this is when an employee table is updated, a trigger runs to update the last_updated_date on the row. Triggers are helpful for auditing and for automatically performing actions. 

However, using triggers too often can result in undesirable consequences. One of those is triggers that call other triggers. This happens when a trigger performs an action on another table that has a trigger - or "nested triggers". 

There are a few reasons this is bad. 

● It's hard to determine all of the code that runs if there are many layers of triggers. ● The performance of the code can be poor because there's a mix of PL/SQL code for triggers 

and SQL code, and multiple pieces of code can run. ● It's hard to debug. 

So if your code uses triggers, one way to improve it is to redesign it so it does not use triggers, by using a stored procedure and adding logic there. If you are using triggers, ensure they are only run once and are not nested. 

 

Conclusion 

 

I hope these tips and best practices for PL/SQL have been useful to you. Whether they are all brand new to you, or you're aware of many of them already, knowing a few ways to improve your code will make your application and database run better and make it easier for you and your team to maintain in the future. 

Ben Brumm 

www.DatabaseStar.com 

 

 

www.DatabaseStar.com