Showing posts with label PL/SQL. Show all posts
Showing posts with label PL/SQL. Show all posts

Saturday, 21 August 2010

Just another implementation of string splitting function in PL/SQL

Here are some of my implementation of string splitting function in PL/SQL.
It is something developers need often, and still can't believe there isn't a function that does something like this in oracle 10g...???

Some developers parse delimited text using substr(text,X,Y) for every token they need to use in their code…

My advice is: Don’t ever do this!!! Use some available tokenizer function!

I found several implementations on the internet, but those functions either does not support delimiter with length more than 1, either it does not return tokens that are NULL, or does not return table of varchar2 index by pls_integer ( what I needed), etc.

So I wrote it myself…

Anyway, here is my first implementation, written entirelly in PL/SQL:

CREATE OR REPLACE FUNCTION splitString(str VARCHAR2,delimit VARCHAR2) RETURN dbms_sql.Varchar2_Table IS
  
  inputString VARCHAR2(32000);
  delimiter VARCHAR2(32);                         

  delimiters dbms_sql.Number_Table;
  tokens dbms_sql.Varchar2_Table;

  array_index PLS_INTEGER := 1;
  pos PLS_INTEGER := 1;
  delimiter_pos PLS_INTEGER;

  delimiter_length PLS_INTEGER;
  inputString_length PLS_INTEGER;

BEGIN

  IF str IS NULL THEN
  RAISE_APPLICATION_ERROR(-20000,'str cannot be null!');
  END IF;
  
  IF delimit IS NULL OR delimit = '' THEN
  RAISE_APPLICATION_ERROR(-20000,'delimiter cannot be null or empty string!');
  END IF;

  inputString := str;
  delimiter := delimit;

  delimiter_length := length(delimiter);
  inputString_length := length(inputString);

  -- Prepare inputText for parsing
  IF substr(inputString,inputString_length - delimiter_length + 1,delimiter_length) != delimiter THEN
      inputString := inputString || delimiter;
  END IF;

  IF substr(inputString,1,delimiter_length) = delimiter THEN
      inputString := substr(inputString,delimiter_length + 1,inputString_length);
  END IF;
  ---------------------


  -- find delimiter occurrences in inputText
   LOOP
      delimiter_pos := instr(inputString,delimiter,pos);
      EXIT WHEN delimiter_pos = 0;
      delimiters(array_index) := delimiter_pos;
      array_index := array_index + 1;
      pos := delimiter_pos + delimiter_length;
   END LOOP;
  --------------------
  
  -- reset temp variables
  array_index := 1;
  pos := 1;
  ---------------------

  -- iterate through delimiters and create tokens
  FOR j IN delimiters.first..delimiters.last LOOP
      tokens(array_index) := substr(inputString,pos,delimiters(j)-pos);
      array_index := array_index + 1;
      pos := delimiters(j)+ delimiter_length;
  END LOOP;
  --------------------

  RETURN tokens;

END;
/
Function’s parameters are string that needs to be tokenized, and a delimiter ( that can have length greater than 1).

Yoy could enchance it by setting


TOKENS     DBMS_SQL.VARCHAR2_TABLE;

to

TYPE t IS TABLE OF varchar2(32000) INDEX BY PLS_INTEGER;
TOKENS t;

if  you are dealing with tokens of length > 2000.
Also, these are case-sensitive functions. You can make it case-insensitive very easy...

Dejan  gave me an idea to write it using SQL.
So I did it, because his implementation using SQL to parse a string is cool:



CREATE OR REPLACE FUNCTION SPLITSTRING_USING_SQL(STR VARCHAR2, DELIMIT VARCHAR2)
  RETURN DBMS_SQL.VARCHAR2_TABLE IS

  INPUTSTRING    VARCHAR2(32000);
  INPUTDELIMITER VARCHAR2(32);

  TOKENS DBMS_SQL.VARCHAR2_TABLE;

  DELIMITER_LENGTH   NUMBER;
  INPUTSTRING_LENGTH NUMBER;

BEGIN

  IF STR IS NULL THEN
    RAISE_APPLICATION_ERROR(-20000, 'str cannot be null!');
  END IF;

  IF DELIMIT IS NULL OR DELIMIT = '' THEN
    RAISE_APPLICATION_ERROR(-20000,
                            'delimiter cannot be null or empty string!');
  END IF;

  INPUTSTRING    := STR;
  INPUTDELIMITER := DELIMIT;

  DELIMITER_LENGTH   := LENGTH(INPUTDELIMITER);
  INPUTSTRING_LENGTH := LENGTH(INPUTSTRING);

  -- Prepare inputText for parsing
  IF SUBSTR(INPUTSTRING,
            INPUTSTRING_LENGTH - DELIMITER_LENGTH + 1,
            DELIMITER_LENGTH) != INPUTDELIMITER THEN
    INPUTSTRING := INPUTSTRING || INPUTDELIMITER;
  END IF;

  IF SUBSTR(INPUTSTRING, 1, DELIMITER_LENGTH) != INPUTDELIMITER THEN
    INPUTSTRING := INPUTDELIMITER || INPUTSTRING;
  END IF;
  
  SELECT SUBSTR(INPUTTEXT,
                INDEKS + DELIMITER_LENGTH,
                NAREDNI - DELIMITER_LENGTH - INDEKS) TOKEN BULK COLLECT
    INTO TOKENS
    FROM (SELECT INDEKS,
                 LEAD(INDEKS) OVER(ORDER BY INDEKS) NAREDNI,
                 INPUTTEXT
            FROM (SELECT LEVEL INDEKS,
                         INPUTSTRING INPUTTEXT,
                         INPUTDELIMITER DELIMITER,
                         (CASE
                           WHEN SUBSTR(INPUTSTRING, LEVEL, DELIMITER_LENGTH) =
                                INPUTDELIMITER THEN
                            1
                           ELSE
                            0
                         END) IS_DELIMITER
                    FROM DUAL
                  CONNECT BY LEVEL <= LENGTH(INPUTSTRING))
           WHERE IS_DELIMITER = 1)
   WHERE NAREDNI IS NOT NULL;

  RETURN TOKENS;

END;
/
It’s basically very similar to his function.
The only difference is that unlike Dejan’s function, this one returns NULL tokens too…

You can test theese two functions easy:


declare 
  
  input VARCHAR2(32000) := 'one***two***three***four***five***six***seven***eight***nine***ten';
  delimiter varchar2(10) := '***';
  result dbms_sql.Varchar2_Table;
  
begin

  result := splitString(input,delimiter);
  --result := splitString_using_sql(input,delimiter);
  
  for i in result.first..result.last loop
      dbms_output.put_line(result(i));
  end loop;
                        
  
end;





Conclusion:
There are CERTAINLLY better ways to write this function, and these are just an example how this could be done.

This is one possible approach, to return an array of tokens. For me , probably the better way would be to create an Object that does something like StringTokenizer Java class. Not to return an array, but to provide a way to iterate through tokens using it’s hasMoreTokens() and nextToken() methods.
I think this is better approach if you are dealing with very large input strings and tokens. If you are dealing with small ones, then it’s OK to use one of my funtions I provided here, or any other similar to this one, that you can find on the internet…

If you intend to use one of these, I suggest  on using first one, because It is much faster than second one.

I didn’t do any optimizations ( which is obvious  ) because I was pleased with speed. Of course, you are welcome to do it yourself

as a matter of fact, I did some speed testing with input strings whose length is ~ 30000, and the first one was (much) faster than many  functions I found on the internet…

Monday, 19 October 2009

Oracle ROWNUM

Na baze-podataka.net je izašao moj novi post, ovaj put posvećen priči o rownum-u u Oracle RDBMS-u.

Tema može biti interesantna ljudima koji ranije nisu detaljnije čitali o ovoj pseudo koloni.
Objašnjeno je  njeno pravilno korišćenje u upitima , i skrenuta pažnja na neke česte zablude u vezi rownum-a...

Link: http://www.baze-podataka.net/2009/10/19/oracle-rownum/

Monday, 25 May 2009

SQL Developer , nebrušeni dijamant

Obožavam maj.
To je mjesec kada se u Crnoj Gori najviše praznuje.
Prvo dođe 1. maj pa onda i 21. maj , prije toga u aprilu i uskrs
i ako se “poklopi” da ovi dani padaju u četvrtak – petak , tu ukupno bude fin broj dana za odmaranje.

To je razlog što me dugo nije bilo sa novim tekstom. Praznovalo se, što ću :-)

Ako se ovome doda da sam u maju bio još i na ( zasluženom ) odmoru od ogromnog i iscrpljujućeg projekta – mogu reći da mi je ovaj maj bio poseban... :-)

Međutim, majski praznični dani su u Crnoj Gori, na žalost, završeni :-(
Ali nije ni to loše. Ja sam jedan od onih koji obožavaju svoj posao.
A tu su i novi tekstovi na devtalks-u …

Prije neki dan mi pade na pamet jedan duži tekst koji sam posvetio recenziji IDE-ova za rad sa Oracle-om, pisanju koda u PL/SQL-u, upita u SQL-u i slično, a koji nikada nisam objavio.
Dakle, o alatima koji su u ponudi na tržištu, i moje impresije o istima.
Konkretno – radi se o “PLSQL Developer”-u , “Toad”-u , “SQL Developer”-u i “SQL Manager 2007 Lite for Oracle”.
Ja u principu nisam baš totalno zadovoljan alatima ove vrste za rad sa Oracle-om i ovo je upravo jedna kategorija IDE-a koje sam vrlo često mjenjao tokom rada sa pomenutom bazom ne bih li našao nešto što mi u potpunosti odgovara.

Ovdje ću prenijeti samo dio teksta, i to onaj koji je posvećen Oracle-ovom SQLDeveloper-u.

Dakle, ovako sam tada opisao ovaj alat:


SQL Developer



Ako pričamo o konkurenciji Toad-u, onda moramo na prvo mjesto staviti program koji nam dolazi pravo iz Oracle-a a koji je ranije bio poznat pod nazivom Raptor: SQL Developer.
Zaista svjetlo na kraju tunela, i pravo osvježenje!



Ovo je nešto što, po meni, najviše liči na jedan pravi alat za rad sa bazom podataka i razvoj aplikacija, kada je u pitanju Oracle.
To što je pisan u Javi je u jednoj mjeri dobra stvar, a u najvećoj mjeri jako loša.
Upravo je moja najveća zamjerka kod ovog programa uperena ka njegovoj brzini. Ustvari ne ka brzini izvršavanja naredbi toliko - koliko na brzinu renderovanja njegovog izgleda i brzinu jave u domenu GUI-ja inače.
Znamo da Java baš i nije svjetski prvak u brzini kreiranja GUI objekata. E , tu ova mana dolazi do izražaja.
Mislim da su momci iz Oracle-a mogli ovaj aspekt malo bolje uraditi. Pravi primjer brzog IDE-a pisanog u Javi kod kojeg nikada nemamo ovaj problem je npr. NetBeans ili Eclipse.
Često mi se desi da nakon dugog rada u nekom drugom programu, kliknem da se vratim na SQL Developer, a onda moram da sačekam ko zna koliko ( dragocjenog ) vremena da mi se prikaže program sa svim elementima i trenutnim stanjem.
Inače, ovo je možda jedina ozbiljnija zamjerka na ovaj program.
Program ima veoma dobar code completion , ima podršku za CVS i subversion, stablo je veoma lijepo urađeno, pregledno je i brzo.
Ima skoro sve elemente koje editor jednog savremenog IDE-a treba da ima.

Pristup tabelama je veoma jednostavan i brz:
Jednim klikom na neku tabelu nam se odmah otvara novi prozor sa svim informacijama po tabovima za odabranu tabelu.

Tabela za prikaz podataka bi mogla biti puno ljepša , a njene ćelije malo većih dimenzija.

Još jedna dobra stvar kod ovog programa je ta da ne moramo imati instaliran nikakav Oracle client kod sebe, jer za konekciju ne koristi tnsnames.ora već JDBC drajvere.

SQL Developer sada zaista zaslužuje da mu se da šansa.

Savjet:
Svim korisnicima savjetujem da prije početka obavezno urade sljedeće:

Tools -> Preferences -> Database -> Object viewer Parameters i štrikirajte “Automatically freeze object viewer windows” jer će se u protivnom svaka tabela na koju kliknemo jednom otvarati uvijek u jednom istom prozoru. Sada će se svaka tabela otvarati u posebnom prozoru.


Da , to je nešto što bih ranije rekao za ovaj alat, nakon nekoliko nedjelja rada u njemu i to u NE kritičnim situacijama, kada se mogu tolerisati određene gluposti koje IDE može izazvati.
Tada mi je ovaj program zaista bio pravo osvježenje jer je izgledao oprilike onako kako sam ja zamišljao da bi jedan IDE za rad sa Oracle-om ( za developere, naravno ) i trebao izgledati.
Ono što bih sada rekao za ovaj IDE je da ga se bar još neko vrijeme zaobilazi u širokom luku!
On je OK da se kući koristi i za učenje, ali ako se odlučite za njega na nekom ozbiljnijem projekrtu - uvijek treba imati pored sebe i jedan od dokazanih alata.

Zašto sam promjenio mišljenje? Pa kao prvo zato što sa ovim alatom nikada ne znaš na čemu si. Recimo da smo prepravili neku funkciju, paket, proceduru...
Nakon kompajliranja istih uopšte nećemo znati da li je kompajliranje uspješno obavljeno, da li je novi objekat validan , u čemu je problem ako problem postoji ( ne prikaže se lijepo poruka kao u npr. Toad-u )…

Evo još jedan primjer: Dešavaće vam se nekada da će program sam od sebe početi prikazivati neki view sa uduplanim kolonama, dok je u drugim alatima taj pogled sasvim OK. A onda ti ako nemaš na produkcionoj lokaciji još neki alat nećeš moći ništa dalje raditi oko pomenutog pogleda...

Ako program ( kojem inače ne treba instalacija ) iskopiramo na neki drugi računar i pokušamo ga pokrenuti – tu nastaju tek posebne situacije i blokiranja računara...
O fantomskom brisanju ili dodavanju znaka tačka-zarez ( ; ) na kraju kodova koje želimo iskompajlirati – ni da ne pričam!

Sve u svemu – SQL Developer je zaista alat kojeg jedna tanka linija dijeli od odličnog alata i vjerovatno će to uskoro i postati, kada se isprave određene nebuloze koje postoje u njemu. Zaista je sjajno zamišljen.
Mislim da Oracle mora sebi priuštiti jedan ovakav, ali bug-free i price-free alat po svaku cijenu.

E, da , za kraj: jedva čekam maj 2010-e. :-)

Friday, 27 March 2009

Easy_Logger for Oracle - Log every insert, update or delete for a table , with details

When it comes to client's demands when creating some IS or designing a DB, very often there's a need to have a complete log of all actions on some table containing important data.
I received a few such demands myself...

Something like this isn't so hard to make it yourself.

We could easily write a
before insert or update or delete trigger and iterate through all the columns saving all old and new values into some log table.
But what if we have many tables that needs such log? Writing all of these triggers would take a quite time...
And what happens when we alter some of these tables?

That is why I created "Easy_Logger".
Thanks to Easy_Logger we can easily create and maintain logging triggers.

Easy_logger is a PL/SQL package , so it can be applied only to Oracle.
It is written in Oracle 10g. I didn't test it on any other version...

So, how do we use Easy_Logger?

First of all, you should run this script:

instal_EL_prerequisites.sql

It will create a table called "EL" ( as Easy Logger ) which will contain all of our logs.
It has 6 columns:

EL_ID - autoincrementing ID , primary key
EL_TABLE - Table name for which the log is created
EL_ACTION - Which action was logged ( insert, update or delete )
EL_LOG - The log itself containing all the old and/or new values in a record
EL_USER - User name which commited action
EL_DATE - When was log created

If this script ended without errors, you can now download Easy_Logger package itself and apply it into your database:

Package declaration
Package body


If everything went well, and without errors, you can now start creating a logging trigger for any table you wish.
I will explain now how to do this:

When creating an Easy_logging trigger for some table, you need to specify some parameters , that are part of a record EL_PREFERENCES:

it looks like this:

TYPE EL_PREFERENCES IS RECORD (
table_name varchar2(64),
create_AFTER_trigger BOOLEAN := true,
on_insert BOOLEAN := true,
on_update BOOLEAN := true,
on_delete BOOLEAN := true
);

so, if you want to create an AFTER [{INSERT},{UPDATE},{DELETE}] TRIGGER, you would set "create_AFTER_trigger" variable to TRUE ( else, if you would like to create a BEFORE [{INSERT},{UPDATE},{DELETE}] TRIGGER, you would set "create_AFTER_trigger" variable to FALSE) .
"Table_name" variable holds information about table's name for which you want to create a trigger,
and "on_insert" , "on_update" and "on_delete" simply tells Easy_logger on which action should a trigger fire.
After you set all of this variables, you need to call a procedure called EASY_LOGGER.CREATE_TRIGGER() by sending this PL/SQL record to it...




EXAMPLE:

if we want to make a trigger that fires on after inserting or deleting a record in a table called MY_TABLE, we would execute following code:

declare
preferences EASY_LOGGER.EL_PREFERENCES;
begin
preferences.table_name := 'MY_TABLE';
preferences.create_AFTER_trigger := true;
preferences.on_insert := true;
preferences.on_update := false;
preferences.on_delete := true;
EASY_LOGGER.CREATE_TRIGGER(preferences); <-- this is the procedure for creating a trigger!
end;

or just call

exec EASY_LOGGER.CREATE_TRIGGER('MY_TABLE',true,true,false,true);

(notice that arguments for this procedure are exactly in the same order as variables in EL_PREFERENCES record)

and a code for creating a logging trigger will generate in dbms_output, so don't forget to set serveroutput on!

All that there is left for you to do is to execute generated code, and you'll have your logging trigger for MY_TABLE table created.

For those who need generated code as a returning value, and not as dbms_output output, there is a function called get_trigger_ddl which has no input parameters.
It returns last generated trigger's  DDL .

You can call it like this:

declare
your_variable clob;
begin
EASY_LOGGER.CREATE_TRIGGER('MY_TABLE',true,true,true,true);
your_variable := EASY_LOGGER.get_trigger_ddl();
...  -- do whatever you like with generated code...
end;

That's it!
Every action specified when creating logging trigger will be logged in EL table.
I hope you'll like it...
You are free to report any bug, or suggest any good idea for me to implement in order to make Easy_logger better...

Friday, 25 July 2008

Quick tip 3: How to get List index in oracle forms builder

As i can see, many people have this problem. They have a hard time to get index of an item selected in some list.
As I can tell, there isn’t a function like GET_LIST_INDEX() or something like that.
I had this problem myself few times before, and then I wrote this PL/SQL function:

FUNCTION getListIndex (LIST IN VARCHAR2, VAL IN VARCHAR2) RETURN INTEGER IS
BEGIN
IF To_Number(Get_List_Element_Count(LIST))>0 THEN
FOR i IN 1..To_Number(Get_List_Element_Count(LIST)) LOOP
IF Get_List_Element_Value(LIST,i) = VAL THEN
Return i;
END IF;
END LOOP;
ELSE
RETURN -1;
END IF;
END;


And to find out which index you selected by clicking on the list, call
getListIndex(‘blockX.listX , :blockX.listX);
This will return selected index, and -1 if the list is empty…
For example, to delete something from some list you just cdouble-clicked, call:

-- on WHEN-MOUSE-DOUBLECLICK trigger:
...
DELETE_LIST_ELEMENT('block3.list4',getListIndex('block3.list4',:block3.list4));
...