Saturday, December 31, 2011

I See Data, People

I've been working with Databases for many years now. As a result, I'm conditioned to see data everywhere I look and I always wonder how that data is stored and how it came to be presented in the format in which I saw it. Here's an example.

I like golf. Actually, I love golf. No...I'm completely obsessed with golf. I follow all of the tours, players, and news. I'll wake up at 3 o'clock in the morning on weekends to watch The Open Championship being played in Great Britain in July. It's OK, I don't expect you to understand.

As with any sport, in golf there is no end of statistics, scores, and mounds upon mounds of data to read. There is a specific type of scoring in golf called Match Play. The gist of the system is that two players playing against each other on the same course will record their score for each hole. The player with the lower (better) score on a hole will go "one up" and the player with the higher (worse) score will go "1 down". As play progresses the "up" and "down" scoring is cumulative. Once a player is "up" more points than there are holes remaining, that player has won the match as their opponent would be unable to win enough remaining holes to close the gap. If you'd like to read more about match play scoring you can do so here.

If you imagine how a golfer's scores would be recorded in a database table, a very simplified approach would be a table that looked like this.

PLAYER_SCORECARD
Player Hole Score
Bob 1 4
Bob 2 4
Bob 3 3
Bob 4 3
Bob 5 5
Bob 6 5
Bob 7 6
Bob 8 3
Bob 9 4
...

and so on.

Typically, when the scores for a Match Play type match are displayed for two opponents, it is done in a "horizontal" manner.

Hole 1 2 3 ...
1 up 2 up 1 up ...
Bob 3 2 4 ...
Joe 4 3 3 ...
1 dn 2 dn 1 dn ...

Since the scores for players are not stored horizontally in our PLAYER_SCORECARD table this is a great example of how the PIVOT function introduced in 11g can be very helpful.

Wednesday, December 14, 2011

I learned something new today (I think)

I'm not sure if I had not seen this before, or if I had and simply forgotten about it.

You can use the assignment statement ":=" to designate a parameter as having a default value as well as using the DEFAULT keyword!

DECLARE
  PROCEDURE p_default_param1(in_param IN VARCHAR2 := 'Default')
  IS
  BEGIN
    DBMS_OUTPUT.PUT_LINE('in_param = '||in_param);
  END p_default_param1;

  PROCEDURE p_default_param2(in_param IN VARCHAR2 DEFAULT 'Default')
  IS
  BEGIN
    DBMS_OUTPUT.PUT_LINE('in_param = '||in_param);
  END p_default_param2;
BEGIN
  p_default_param1;
  p_default_param1('A');

  p_default_param2;
  p_default_param2('B');
END;
/
in_param = Default
in_param = A
in_param = Default
in_param = B


PL/SQL procedure successfully completed.

For the sake of semantic clarity I much prefer the DEFAULT keyword. The extra keystrokes are worth it, in my opinion.

Monday, December 12, 2011

NVL() does not short circuit


Here's an interesting tidbit about NVL(expr1,expr2).  The documentation states that " If expr1 is null, then NVL returns expr2. If expr1 is not null, then NVL returns expr1." What you may not know is that both expr1 and expr2 are evaluated even if expr1 is not null. NVL does not short circuit. Fortunately alternative methods like COALESCE and the CASE statement will.
DECLARE
  str VARCHAR2(1);

  FUNCTION dummy_function RETURN VARCHAR2
  IS
  BEGIN
    DBMS_OUTPUT.PUT('(Function ran)');
    RETURN 'X';
  END dummy_function;
BEGIN
  str := 'A';

  DBMS_OUTPUT.PUT_LINE('NVL: '||NVL(str,dummy_function));
  DBMS_OUTPUT.PUT_LINE('COALESCE: '||COALESCE(str,dummy_function));
  DBMS_OUTPUT.PUT_LINE('CASE: '||CASE WHEN str IS NOT NULL THEN str ELSE dummy_function END);
END;
/
(Function ran)NVL: A
COALESCE: A
CASE: A


PL/SQL procedure successfully completed.
This is definitely something to keep in mind if, like in the example above, you are calling a function for expr2. If that function does a significant amount of processing and/or the NVL() is inside of a loop with many iterations, the cycles it takes to execute the function every time could add up.

Saturday, December 10, 2011

Implicit Cursors are Wonderful

I was sent this script for review. Can you see what's wrong with this picture?

DECLARE
  l_limit CONSTANT PLS_INTEGER := 1000;
  
  TYPE varchar2_tab_typ IS TABLE OF VARCHAR2(100) INDEX BY PLS_INTEGER;
  
  varchar2_tab   varchar2_tab_typ;
  
  CURSOR grants_cur IS
   SELECT 'GRANT SELECT ON '||table_name||' TO some_schema'
   FROM   user_tables;
   
BEGIN
  varchar2_tab.DELETE;
  
  OPEN grants_cur;
  
  LOOP
    FETCH grants_cur BULK COLLECT INTO varchar2_tab LIMIT l_limit;
    
    EXIT WHEN grants_cur%NOTFOUND;
  END LOOP;
  
  CLOSE grants_cur;
  
  FOR i IN 1..varchar2_tab.COUNT LOOP
    EXECUTE IMMEDIATE varchar2_tab(i);
  END LOOP;
END;
/

Monday, December 5, 2011

Too much of a good thing?

I remember one fateful day when I found the following line of PL/SQL code at a client site

g_period CONSTANT VARCHAR2(1) := '.';

Constants are great. They allow you to rely on a persistent value for processing. They are also really good if you need to use a value again and again in code, the value must be the same throughout the code, and if the value could change due to new requirements, change in direction, etc. For example, your code uses the following constant to denote a value of "True"

g_true CONSTANT VARCHAR2(4) := 'TRUE';

At some later point it is decided that the value should simply be the letter 'T' and not 'TRUE'. If you've used the constant throughout your code it's as simple as changing the value of the constant in one place - where it's declared. Quick and easy. That's just good programming.

g_true CONSTANT VARCHAR2(1) := 'T';

The g_period example listed above is a little overboard. All the programmer had accomplished was declared a constant of something that is already a constant! If we followed that example we might as well declare every character as a constant and use them everywhere in the code.

g_c CONSTANT VARCHAR2(1) := 'C';
g_h CONSTANT VARCHAR2(1) := 'H';
g_r CONSTANT VARCHAR2(1) := 'R';
g_i CONSTANT VARCHAR2(1) := 'I';
g_s CONSTANT VARCHAR2(1) := 'S';

Now I can spell my name and never worry about the letters changing!

DECLARE
  l_my_name VARCHAR2(5);
BEGIN
  l_my_name := g_c||g_h||g_r||g_i||g_s;
END;
/

It looks a little silly when it's put like that, doesn't it?

Using constants properly can save you a lot of pain, and using them improperly can cause just as much pain as you may save. Food for thought.

Happy coding.

Wednesday, November 16, 2011

Me too

Yup. I have a blog now. I make no promises regarding the frequency of posts, nor do I harbour any delusions on the matter.