I’ve written about PL/SQL before, but I’ve recently started working on a project that uses it heavily. Given the amount of code written for Oracle databases, I’m rather suprised that there’s not a PL/SQL Cookbook, where, like the Perl Cookbook and the Java Cookbook (more cookbooks from O’Reilly are listed here). There is an Oracle Cookbook, but based on a quick scan of Amazon, it’s is focused, as you’d expect, more on the database design than on PL/SQL programming. (Interestingly, there is a Oracle+PHP cookbook, and a PL/SQL sample code page but neither of those is quite what I’m looking for.)
The reason that I’d like a PL/SQL cookbook is that there are large sets of problems that routinely need to be solved in PL/SQL, but the language is so low level (though they just added some regex support in 10g; bravo!) that doing these routine tasks and making sure they’re correctly implemented can be difficult and tedious. This is especially true when it’s a programmer from a different language who’s used to higher levels of abstraction (like, for example, the good folks who author CPAN modules provide)–it’d be well worth my $70 to make sure that I never had to deal with a problem like, say, unescaping a string.
For that’s the problem I recently had to solve. Essentially, we have a string that looks like this:
yellow,apple. This string represents two values, which need to be put in different places by splitting them up into ‘yellow’ and ‘apple’. All well and good until the possiblity of embedded commas arises, for it’s possible that the desired end values were ‘yellow,blue’ and ‘apple,banana’. The answer, of course, is to escape the commas on the way in (turning the second input into something like this:
yellow:,blue,apple:,banana, and when processing to unescape those special characters (both the comma and the escape character, which in the example is the colon). That’s what these three functions do. They take a string like the above examples and parse it into a table, to be iterated over at your leisure.
/* ------------------- function splitit ------------------*/ FUNCTION splitit(p_str VARCHAR2, p_del VARCHAR2 := ',',p_idx PLS_INTEGER, p_esc VARCHAR2 := ':') RETURN INTEGER IS l_idx PLS_INTEGER; l_chars_before VARCHAR2(32767); l_escape_char VARCHAR2(1) := p_esc; l_chars_before_count PLS_INTEGER := 0; BEGIN > LOOP l_idx := instr(p_str,p_del, p_idx); IF l_idx > 0 then WHILE substr(p_str, l_idx-l_chars_before_count-1, 1) = l_escape_char LOOP l_chars_before_count := l_chars_before_count +1; END LOOP; IF mod(l_chars_before_count, 2) = 0 THEN -- if chars_before_count is even, then we're at a segment boundary RETURN l_idx; ELSE -- if odd, then we're at an escaped delimiter, want to move past RETURN splitit(p_str, p_del, l_idx+1, p_esc); END IF; l_chars_before_count := 0; ELSE RETURN l_idx; EXIT outer; END IF; END LOOP; END splitit; /* ------------------- function splitit ------------------*/ /* ------------------- function unescape ------------------*/ FUNCTION unescape(p_str VARCHAR2, p_del VARCHAR2 := ',', p_esc VARCHAR2 := ':') RETURN VARCHAR2 IS l_str VARCHAR2(32767); BEGIN l_str := replace(p_str, p_esc||p_del, p_del); l_str := replace(l_str, p_esc||p_esc, p_esc); RETURN l_str; END unescape; /* ------------------- function unescape ------------------*/ /* ------------------- function split ------------------*/ FUNCTION split(p_list VARCHAR2, p_del VARCHAR2 := ',') RETURN split_tbl IS l_idx PLS_INTEGER; split_idx PLS_INTEGER := 0; l_list VARCHAR2(32767) := p_list; l_chars_before VARCHAR2(32767); l_escape_char VARCHAR2(1) := ':'; l_array split_tbl := split_tbl('','','','','','','','','',''); BEGIN l_list := p_list; LOOP split_idx := split_idx + 1; IF split_idx > 10 then EXIT; END IF; l_idx := splitit(l_list, p_del, 1, l_escape_char); IF l_idx > 0 then l_array(split_idx) := unescape(substr(l_list,1,l_idx-1), p_del, l_escape_char); l_list := substr(l_list,l_idx+length(p_del)); ELSE l_array(split_idx) := l_list; EXIT; END IF; END LOOP; RETURN l_array; END split; /* ------------------- function split ------------------*/ /* in the header file, split_tbl is defined */ TYPE split_tbl IS TABLE of varchar2(32767)
Not all of this code is mine–I built on a solution from a colleague. But I hope this saves one other person from the afternoon I just endured. And if you are a PL/SQL expert and care to critique this solution, please feel free.
These sort of codes are not meant to be criticized but Endured .
As a java and php developer, I’d probably say that about most PL/SQL. As I’m sure some would say about java and/or php. 🙂
I’m not familiar with the cookbooks (never had to use them). Just noting though that there is a pl/sql pocket reference ‘Oracle PL/SQL Language Pocket Reference, nth Edition’ by O’Reilly (nth being what ever edition they’re up to at the time you read this – currently 4th).
I haven’t read it myself but they keep releasing editions so presumably it must sell.
I usually find trial and error and a few web resources go a long way…. snippets such as what you’ve done here are great. Keep up the good work.