Skip to main content
Received this note from a PL/SQL developer today:

I have an idea to build the database of my application in such a way that all DML operations for the application are managed at the back end. There should be generic functions/procedures that will perform the DML operations. Also there should be functions developed that will provide the "Select" data. In short, I want to remove all sort of DML from front end and bring them to back end. In case of any DB structural changes, we should not require a large development effort. For example, if we have to add couple of columns to a table then it should be easily manageable/configurable in order to minimize the development effort cost.

This is the sort of note that makes me happy.

I have proposed for years to Oracle Database developers that we should see SQL as a service that is provided to us, not something we should write over and over and over again in our "high level" application code (as opposed to low level generic utilities like error loggers). 

After all, every SQL statement is a hard-coding just like every literal is a hard-coding.

And now I can all hear you saying, as my granddaughter is wont to say: "Whaaaaat?!"

Yes, every SQL statement is a hard-coding, and they are far worse and more dangerous than the hard- coding of literals.

In both cases, we make the assumption that this thing (a numeric value or a 6 way join) not only "works" (is accurate) today but will stay the same in the future.

Ha!

Ha, ha!

What a funny joke!

Of course it's all going to change. Now, if you hard-code the literal 76034 in your application in 5,000 places, no big deal, really. Just do a global search and replace in your code. 

There is very little chance you will change anything but the literal value of interest.

[Of course, little chance and no chance are two different things entirely. So when doing a GSAR, please do visually check each change. Years ago, right after I left Oracle, I worked as a consultant in the HQ of one of the biggest "fast food" chains in the world. I fixed a bug and "while I was at it" optimized some other code (or so I thought). Passed it on to QA, went into production on Sunday, and on Wednesday they had to roll back three days of production activity. Why? Because my GSAR had change "WHERE X = Y" into "WHERE X = X" - I won't bore you with the details or why the actual change didn't seem quite as stupid as that. The good news? I was not blamed. It was the QA team's fault. Ha! Ha, ha!]

But what if you repeat the same or very similar 6-way join in 3 or 5 places? How can you find and upgrade all those "old" SQL statements?

Bottom line: if you do not build a layer of code around your data, essentially around your SQL statements, you end up with an out of control, hard to optimize, impossible to test and maintain application. Yuch.

So use views to hide complex and often needed WHERE clauses. Build packages to hide transactions and queries and non-query DML operations.

And, ideally, you should be able to generate lots of this code. I have, in the past, build and offered really powerful code generators including the incredibly awfully named QNXO and its descendent, Quest CodeGen Utility. Neither, sadly, are available anymore. But I hope in the next year to offer a simpler substitute that could be used very flexibly.

In the meantime, the next time you start to write a SELECT directly inside your high-level code, stop and ask yourself: Might I or someone else need this elsewhere? Might I someday want to use the result cache feature on this query?"

Then build a package and put the query inside a function (or add it to an existing package). Return a record or a collection, as is appropriate. And call that function. Your code is more robust and you spend less time writing and debugging that code.

For example, don't do this:

CREATE OR REPLACE PROCEDURE do_stuff_with_employee (
   employee_id_in IN employees.employee_id%TYPE)
IS
   l_employee   employees%ROWTYPE;
   l_manager   employees%ROWTYPE;
BEGIN
   SELECT *
     INTO l_employee
     FROM employees
    WHERE employee_id = employee_id_in;
    
   /* Do some stuff... and then, again! */

   SELECT *
     INTO l_manager
     FROM employees
    WHERE employee_id = l_employee.manager_id;   
END;
/

do this:

CREATE OR REPLACE PACKAGE employees_sql
IS
   FUNCTION onerow (employee_id_in IN employees.employee_id%TYPE)
      RETURN employees%ROWTYPE;
END;
/

CREATE OR REPLACE PACKAGE BODY employees_sql
IS
   FUNCTION onerow (employee_id_in IN employees.employee_id%TYPE)
      RETURN employees%ROWTYPE
   IS
      onerow_rec   employees%ROWTYPE;
   BEGIN
      SELECT *
        INTO onerow_rec
        FROM employees
       WHERE employee_id = employee_id_in;

      RETURN onerow_rec;
   END;
END;
/
      
CREATE OR REPLACE PROCEDURE do_stuff_with_employee (
   employee_id_in IN employees.employee_id%type)
IS
   l_employee   employees%ROWTYPE;
   l_manager   employees%ROWTYPE;
BEGIN
   l_employee := employees_sql.onerow (employee_id_in);
    
   /* Do some stuff... and then, again, but now 
      just a function call! */

   l_manager := employees_sql.onerow (l_employee.manager_id);
END;
/

Comments

Popular posts from this blog

Quick Guide to User-Defined Types in Oracle PL/SQL

A Twitter follower recently asked for more information on user-defined types in the PL/SQL language, and I figured the best way to answer is to offer up this blog post. PL/SQL is a strongly-typed language . Before you can work with a variable or constant, it must be declared with a type (yes, PL/SQL also supports lots of implicit conversions from one type to another, but still, everything must be declared with a type). PL/SQL offers a wide array of pre-defined data types , both in the language natively (such as VARCHAR2, PLS_INTEGER, BOOLEAN, etc.) and in a variety of supplied packages (e.g., the NUMBER_TABLE collection type in the DBMS_SQL package). Data types in PL/SQL can be scalars, such as strings and numbers, or composite (consisting of one or more scalars), such as record types, collection types and object types. You can't really declare your own "user-defined" scalars, though you can define subtypes  from those scalars, which can be very helpful from the p...

Three tips for getting started right with Oracle Database development

By "Oracle Database development", I mean, more or less, writing SQL and PL/SQL. I assume in this post that you have access to Oracle Database (which you can get via Cloud services, Docker , GitHub and OTN ). A. Use a powerful IDE, designed with database programming in mind. There are lots of editors out there, and many IDEs that work with Oracle Database. Sure, you could use Notepad, but OMG the productivity loss. You could also use a popular editor like Sublime, and then it get it working with Oracle. I suggest, however, that you  download  and install Oracle's own own, free, powerful IDE: SQL Developer . If you like to complement your graphical IDE with a command line tool (or OMG if you actually prefer  a command line tool to a graphical interface), you should also check out the relatively new and generating-lots-of-excitement SQLcl . B. Enable compile-time warnings and PL/Scope. The database has tons of useful functionality burned right into it, ready for...

Why DBMS_OUTPUT.PUT_LINE should not be in your application code

A database developer recently came across my  Bulletproof PL/SQL  presentation, which includes this slide. That first item in the list caught his attention: Never put calls to DBMS_OUTPUT.PUT_LINE in your application code. So he sent me an email asking why I would say that. Well, I suppose that is the problem with publishing slide decks. All the explanatory verbiage is missing. I suppose maybe I should do a video. :-) But in the meantime, allow me to explain. First, what does DBMS_OUTPUT.PUT_LINE do? It writes text out to a buffer, and when your current PL/SQL block terminates, the buffer is displayed on your screen. [Note: there can be more to it than that. For example, you could in your own code call DBMS_OUTPUT.GET_LINE(S) to get the contents of the buffer and do something with it, but I will keep things simple right now.] Second, if I am telling you not to use this built-in, how could text from your program be displayed on your screen? Not without a lot o...