Truly Translating Booleans Part 1

Programming is, with few exceptions, performed by human beings. Human beings tend to live and work in ruts: You figure out ways to get things done and then you rely on those familiar and proven pathways again and again. There is nothing wrong with this approach, except that you can end up missing out on an awful lot of what life (and programming languages) has to offer.

I see this dynamic all the time with PL/SQL developers. You work with the language for a few months and you are fairly certain you know what's there and how to use it. That attitude astounds me, because I have been working intensively with PL/SQL for several years now and I am always learning new tricks.

One of the important lessons I have learned is that you have to do more than understand the syntax and documented features of a language. You have to learn how to fully leverage all of those features. To get that point across and also to offer some tricks and techniques I have picked up, I am going to spend the next two issues of OReview taking a look at Booleans in PL/SQL.

In this issue, I review what a Boolean is and how it can be used in Oracle's procedural language. In the next issue I will move on to how and why you might want to build a layer of code around Booleans to make your life easier and your code cleaner.

What is a Boolean?

A Boolean is a scalar data structure that can have only one of three values: TRUE, FALSE, or NULL. Notice that there are no single quotes around any of those values; they are literal values. Boolean is the "logical" datatype of PL/SQL. Notice that when I refer to the datatype, I capitalize the "B" in Boolean. I do so because the Boolean is named afer George Boole, a mathematician who lived in the first half of the 19th century and is considered the "father of symbolic logic."

Boolean values and variables are very useful in PL/SQL. Because a Boolean variable can be either TRUE, FALSE, or NULL, you can use that variable to explain what is happening in your code. With Booleans you can write code that is easily readable because it is more English-like. The example below, chock full of Booleans, should give you a sense of that readability:

CREATE OR REPLACE FUNCTION
 good_hire 
  (birthdate_in IN DATE, 
   reqsal_in IN NUMBER)
  RETURN BOOLEAN
IS
  too_young BOOLEAN := 
    ADD_MONTHS 
     (SYSDATE, -216) > 
     birthdate_in;
  too_expensive BOOLEAN :=
    reqsal_in > fin.dept_bal;
BEGIN
  RETURN NOT (too_young 
   OR too_expensive);
END;

This example shows a Boolean function as well as two local Boolean variables inside that function. When you supply a prospective employee's birthdate and requested salary, the function checks to see if an employee is at least 18 years old (18 3 12 = 216) and if there is enough money left in the department budget (a department's budget information is stored or available through the fin package's dept_bal function - or maybe a variable; you cannot tell from the code).

Inside the function, I declare two local variables to hold the values (TRUE, FALSE, or NULL) corresponding to these conditions. As a result, the body of my function is very concise and readable:

RETURN NOT (too_young 
 OR too_expensive);

In other words, this person is a "good hire" if she is not too young or too expensive. If I had not used those "intermediate" Booleans to encapsulate my logic, the body of the function would have been much longer and more difficult to understand. And this is, of course, just a very simple example.

But that is just the beginning. Let's see how this function can be used inside another PL/SQL block (in this case, an Oracle Forms Pre-Insert trigger):

IF good_hire 
  (:newemp.birthdate, 
   :newemp.proposed_sal)
THEN
  SELECT empseq.NEXTVAL 
   INTO :newemp.empno
    FROM dual;
ELSE
  RAISE FORM_TRIGGER_FAILURE;
END IF;

In other words: If the new employee is a good hire, then get the next sequence number, stuff it into the primary key, and continue with the row insert. Otherwise, raise an exception to stop the row insert. Again, the Boolean nature of the function lets it "plug and play" into my code very easily. The result is a very readable chunk of PL/SQL.

To summarize: You can define and use Boolean functions, variables, and constants. Every time you use a Boolean, you encapsulate or hide a (potentially) complicated Boolean expression involving many different variables and tests behind a name that directly expresses the intention and meaning of the text. Although this statement is actually true for any identifier you define in PL/SQL, it has maximum impact with Booleans because they return a logical value and bring you closer to writing code that can be read as if it were a human rather than computer language.

Writing Lean Boolean Code

So use Booleans! Take advantage of their readability, but make sure that you understand fully the way they can work in your programs. Consider the following fragment of PL/SQL code:

IF v_hiredate < SYSDATE
THEN
  date_in_past := TRUE;
ELSE
  date_in_past := FALSE;
END IF;

What could be more straightforward? If the v_hiredate comes before the current "system date," then set the date_in_past variable to TRUE; otherwise, set it to FALSE. The basic problem with this code is that it is way too much code. Why not just type the following statement?

date_in_past := 
 v_hiredate < SYSDATE;

In other words, apply the condition directly to the variable. You can assign complex (or simple) Boolean expressions to a Boolean variable. You don't have to rely on the IF statement to then assign the literal values of TRUE or FALSE. Whenever you see that type of conditional logic, check to see if it is really necessary or if it can be replaced with a single line of equivalent code.

But wait! Is that single-line assignment truly equivalent to the IF statement? Actually, it is not equivalent when the v_hiredate variable has a NULL value. With the IF statement, a NULL v_hiredate results in date_in_past being set to FALSE. With the direct assignment, a NULL v_hiredate results in date_in_past being set to NULL. To make these two statements truly identical, I need to add some NULL-value handling:

date_in_past := 
 NVL (v_hiredate, SYSDATE) < 
 SYSDATE;

Now if v_hiredate is NULL, a comparison is made with SYSDATE and, because nothing is less than itself, date_in_past is set to FALSE.

No Booleans in the RDBMS

Just when you are thinking that life with Booleans is great, I have some bad news: The Oracle RDBMS does not support a Boolean datatype. I do not understand why Oracle has not taken this step. But the fact is that you cannot define a column in a table with type BOOLEAN. Instead, you can (must) create a table with a column of datatype CHAR(1) and store either "Y" or "N" in that column to indicate TRUE or FALSE, or create a numeric column and store either 1 or 0 for TRUE or FALSE. That is a poor substitute, however, for a datatype that stores true Boolean values (or NULL).

Because there is no counterpart for the PL/SQL Boolean in the Oracle RDBMS, you can neither SELECT into a Boolean variable nor insert a TRUE or FALSE value directly into a database column. There is, in other words, a disconnect between your code and the database. And any time you have a disconnect, you have a much higher probability of introducing errors into your programs (or your database!).

What's the problem? Well, suppose you have tables that contain True/False columns. You cannot define them as BOOLEAN, so you have to adopt a substitute. Here is your first challenge: No matter which approach you pick, you must ensure that everyone who defines tables and columns in the program takes the same approach. If you are using a product such as Oracle Designer/2000, or if you have tightly controlled who sets up data structures, this may not be a problem. In many organizations, however, there is a whole lot more independence (chaos?), and ensuring consistency can be a real challenge.

But let's just assume that you at least have your act together on the database side. The standard is that you store "Y" for TRUE and "N" or FALSE. Now let's look at the code side of your life. You will want to read from and write to tables with these pseudo-Boolean columns.You have, roughly, two choices:

1. The LCD approach: The database doesn't have Booleans, so you go with the "lowest common denominator" and do not use Boolean variables in your code.

2. The TFA approach: You are committing to taking full advantage of what PL/SQL has to offer. You will use Booleans at every possible opportunity.

I am a strong proponent of the TFA approach. You should maximize your use of PL/SQL so that your code is as readable, maintainable, and bug-free as possible.

Translator Needed!

If you are going the TFA route, you need to find a way to bridge the gap between pseudo-Booleans in the database and real Booleans in PL/SQL. You need a translator that lets you read from and write to the database reliably from within a PL/SQL program. Reliability in this case means:

In the context of PL/SQL, this translator is an "API" or programmatic interface to the underlying data structures. Let's look at the different elements we will need in this API.

A Translator API

By providing a programmatic interface, we can guarantee consistency and quality of both process and data. Following are the types of actions I want my API to support:

With these programmatic pieces in place, I can move smoothly from native Boolean values in my PL/SQL programs to consistent pseudo-Booleans in my database tables.

Building the Translator

In my May column, I will show you how to implement the translator I've outlined here. While you are waiting anxiously to see this code, I suggest that you give it a try yourself. How would you build an API to pseudo-Booleans and ensure consistency of values (hint: use a package)?

Steven Feuerstein is the author of Advanced Oracle PL/SQL: Programming with Packages (O'Reilly & Associates, 1996) and is a senior technology officer for RevealNet (www.revealnet.com). You can reach Steven via email at [email protected].



This is a copy of an article published @ http://www.oreview.com/