ISIN validation code in PL/SQL

Monday, 22.02.2010 – Dejan

ISIN = International Securities Identification Number http://en.wikipedia.org/wiki/International_Securities_Identification_Number

CREATE OR REPLACE FUNCTION sfCheckISIN(pISIN IN VARCHAR2) RETURN NUMBER
IS
  s number := 0;
  a number;
  c number;
begin
  a := case length(pISIN) when 12 then 1 else 2 end;
  
  for i in reverse 1..length(pISIN) loop
    c := ascii(substr(pISIN, i, 1));
    if c > ascii('9') then
        c := c - (ascii('A')-10);
        s := s + trunc((3 - a) * trunc(c / 10) + a * c + (a - 1) * (trunc(mod(c,10))) / 5);
    else
      c  := c - ascii('0');
      s :=  s + (a * c + (a - 1) * trunc(c / 5));
      a := 3 - a;
    end if;

  end loop;
  
  s := trunc(mod(s, 10));
  
  return trunc(mod(10 - trunc(mod(s, 10)), 10));

end sfCheckISIN;
/


Oblikujte novi Oracle Forms builder

Thursday, 04.02.2010 – Darko

Nedavno je na Oracle-ovom forumu pokrenuta tema na kojoj možete predložiti šta bi bilo dobro da se doda u Oracle Forms.

http://forums.oracle.com/forums/thread.jspa?threadID=1021732&start=0&tstart=0

Na ovaj način je ljudima koji koriste ovaj alat je data sjajna prilika da oblikuju narednu verziji Forms-a.
Naravno, neće se svaki predlog prihvatiti, ali vrijedi pokušati…

Lično, prve stvari koje su mi pale na pamet su

– dodavanje kalendarčića ( ovo trenutno radim preko Java binova, ali bi bilo puno bolje imati neki gotovi formsov objekat )
– poboljšanje editora PL/SQL-a ( trenutni editor je više nego smiješan. Čak kada razmislim – ne znam za gori )
– ugrađena podrška za webutil ( nepotrebno zakomplikovano )

– bolje odrađena podrška za slike ( slike importovane preko Image item-a su neprihvatljivo loše )

– bolje urađena podrška za kreiranje i importovanje Java binova ( nepotrebno zakomplikovano, bez ozbiljnog modula u samom IDE-u koji bi developerima pomogao pri kreiranju binova )

– veća sloboda pri kreiranju i oblikovanju data blokova. ( Npr. da se može zadati upit na osnovu kojeg će se u run time-u izgenerisati data blok ( ok, koji bi bio read-only ) . Npr. kao data grid koji imamo u BCB-u, .NET-u… .)

Naravno, ima još toga čime bi se trebalo pozabaviti u formsu, kao što je npr. debugger, VCS , prelaženje sa aplet aplikacija na ajax aplikacije , standardizacija IDE-a itd i još dosta dosta toga…

Dakle, ko ima ideje – neka predloži…


Oracle okončao akviziciju Sun-a

Wednesday, 27.01.2010 – Dejan

Nakon zabrinute diskusije oko budućnosti Sun-a, a samim tim Solarisa, Jave i MySQL-a, Larry je stavio tačku na “i” i najavio pozitivni razvoj postojećih tehnologija i proizvoda u sklopu Sun-a, što bi trebalo unijeti malo optimizma glede opstanka dotičnih, te ukloniti strahove i strepnje unutar Open Source zajednice. Usputno je Larry najavio IBM-u borbu na području hardware-a.

sun_customers_lg


FormatOracle2Excel – Kako prebaciti podatke iz Oracle baze u Excel

Tuesday, 24.11.2009 – Dejan

… a da podaci budu uredno posloženi u odgovarajućem formatu, sa raznim bojama, linkovima i sl. karakefama !?

Vjerujem da ste mnogo puta pravili export podataka u CSV formatu (Comma Separated Values) namijenjenih za korištenje u Excelu, a da su vaši korisnici često priželjkivali podatke u određenom formatu ili sa nekom bojom u pozadini.
Da li ste od onih, koji su svojim korisnicima odgovarali: “Nažalost, tehnički to nije moguće…” ?

Ako je tako, onda ćete nakon čitanja kompletnog teksta moći taj odgovor promijeniti u :”Naravno da je moguće! Recite nam samo, kakav format želite i koje boje da uzmemo? Želite li podatke odvojene u više tablica (sheets) ? Da Vam pošaljemo Excel datoteku na email ili da bude prikazana direktno u browseru? Nema problema – sve se može obaviti!!

Pročitaj kompletan tekst »


REF CURSOR IN and OUT

Tuesday, 10.11.2009 – Noctua4u

Obećao jesam nastavak o tipovima, ali obzirom da sam zatrpan nekim poslovima, ne stižem da dovršim započeto. Lepa stvar je što sam u tim poslovima naučio nešto novo, naravno na ličnim greškama i PIG metodom (metoda Probe I Greške), što se “nadovezuje” na priču o tipovima.

Naime, nedavno sam dobio zadatak da prebacim podatke sa jedne Oracle DB na drugu Oracle DB. Obzirom da se serveri fizički “ne vide”, tj. nisu na istoj mreži, to je nametalo neka rešenja. Takođe, neki od “projektnih zadataka” su takođe uticali na izbor načina prenosa i obrade tih podataka.

Najlogičniji pristup (sa aspekta DBA) koji odgovara svim uslovima je bio:

  1. Na SOURCE bazi spakuj podatke u neki RECORDSET koji će preko jedne stored procedure da bude dostupan klijentu
  2. Klijent preuzima podatke i samo ih prosleđuje ih drugoj bazi
  3. Na TARGET bazi bazi prihvati podatke, isti RECORDSET, isto stored procedurom i obradi ih (i na kraju upiši u odgovarajuće tabele)

Krajnje jednostavno, ne?
E, ne! Sa stane developera nemoguć posao!
Da kratim priču, zbog ograničenja “klijentske arhitekture” susrećemo se sa REF CURSOR-ima.

Šta je REF CURSOR?

Treba praviti razliku između kursora (CURSOR) i kursorske varijable ili kursorske promeljive (REF CURSOR). Kursorska varijabla ukazuje na trenutni red (ROW) u skupu podataka koji se dobija izvršenjem nekog upita. Kursorska varijabla {u daljem textu CVR} je fleksibilnija jer nije vezana za određeni upit (kao CURSOR), već se može koristiti više puta za različite upite.
CVR se može tretirati kao pointer.

Zašto koristiti REF CURSOR?

Logično pitanje koje se nameće iz definicije CVR je: zašto bih koristio REF CURSOR kada mogu da koristim CURSOR ili neki strogo definisani skup podataka ili neki moj tip?
Odgovori su mahom vezani za koncepciju manipulaciju i strukturu podataka:

  • Rad sa različitim upitima u različito vreme korišćenjem samo jednog CVR-a. Drugim rečima, jedna CVR se može koristiti za “sakupljanje” podataka iz različitih upita.
  • Kada želimo da izvršimo upit u jednom programskom modulu (procedura, funkcija) i procesuriramo rezultate u drugi podprogram (najčešće pisan u drugom jeziku!) na klijentu. Vidi sliku.
    REFCURSOR
  • Ukoliko postoji PL/SQL engine na strani klijenta nema restrikcija u komunikaciji između klijenta i servera. Npr. možete deklarisati CVR na strani klijenta, otvoriti je i popuniti podacima na strani servera, vratiti klijentu, nastaviti “popunjavanje” na strani klijenta, vratiti serveru da je zatvori itd. Moguće su sve kombinacije.

Posebno je potrebno obratiti pažnju na deo “pisan u drugom jeziku”, videćemo posle i zašto.

Deklaracija REF CURSOR tipa i kursorske varijable (promenljive):

DECLARE
TYPE RFC_Slab IS REF CURSOR; -- Deklarisanje TIPA kursora (slab tip)
TYPE RFC_Jak IS REF CURSOR RETURN odeljenja%ROWTYPE; -- Deklarisanje TIPA kursora (jak tip)
in_cursor RFC_Slab; -- Deklaracija CVR
out_cursor RFC_Jak; -- Deklaracija CVR
BEGIN
OPEN in_cursor FOR SELECT * FROM gradovi;
OPEN out_cursor FOR SELECT * FROM odeljenja;
END;

JAK TIP REF CURSORA ima u deklaraciji RETURN neka_tabela%ROWTYPE što označava kako mora da izgleda u trenutku deklaracije. To praktično znači da bilo koji SQL upit, koji koristi CVR, mora da vrati strukturu (kolone) kao što je ima neka_tabela. O prednosti jakog tipa nad slabim nećemo ovom prilikom, ali jedna od njih je i ta što komplajler može da odredi da li je developer ispravno “upario” podatke iz CVR-a.

SLAB TIP onaj koji nije vezan za neku strukturu podataka i može se koristiti sa bilo kojim upitom.

Ograničenja REF CURSORA (CVR):

Veoma bitno je znati šta NE možete raditi:

  1. CVR se ne može deklarisati u paketu (nema stvarnu vrednost već je samo “pointer” na nešto što će se kasnije koristiti). Deklariše se samo TIP podataka kao REF CURSOR!
  2. Ne može se prosleđivati preko Remote Procedure Calls (zaboravite DB link) sa jednog na drugi server
  3. Ukoliko se prosleđuje kao “bind” ili host varijabla u PL/SQL, ne može se uraditi FETCH na serveru ukoliko CVR nije otvorena u istom pozivu (Praktično: da bi stored procedura mogla da koristi podatke koje joj šaljete u REF CURSORu, morate u okviru poziva i da ga otvorite!)
  4. Ne može se testirati jednakost, nejednakost ili da li je NULL pomoću operatora komparacije.
  5. Ne može se dodeliti NULL vrednost CVR
  6. Kolone ne mogu biti tipa RFC (u CREATE TABLE/VIEW ne možete staviti tip REF CURSOR. Takođe, ništa VARRAY i ostali tipovi)
  7. Ne mogu se koristiti u dinamički generisanom SQL-u

Evo još nekih stvari koje su bitne za REF CURSOR a koje NE možete:

  1. Iz klijentskih jezika ne možete prihvatiti nikakve podatke ukoliko nisu u REF CURSORu (zapravo, ponekad i možete, ali to zahteva mnogo više akcija nego kod REF CURSOR-a)
  2. Ne možete da “prosledite” REF CURSOR natrag u bazu!!! (php, java, perl provereno, a za .Net i ostale samo pročitao u dokumentaciji)

Upotreba REF CURSOR-a kao IN/OUT parametra

Znači, ukoliko morate da, npr. “napunite TREE” komponentu na strani klijenta, neki logičan sled bi bio:

  • Pripremite podatke (CONNECT BY PRIOR možda?)
  • Smestite ih u REF CURSOR
  • Iz klijentskog jezika pozivate neki modul (funkcija, procedura…) koji vam isporučuje podatke
  • Dalje radite sa njim (menjate čvorove, potege, veze, šta-god..)

Šta ako baš, baš, morate da pošaljete podatke nazad u bazu (recimo, gore pometnuti TREE koji je izmenjenog oblika)??

Odgovor je jednostavan: NE MOŽE!

Mada…

Evo malog trika koji je primenljiv nad jednom konekcijom u ISTOJ bazi (uzimamo u obzir treću stavku iz dela Zašto koristiti REF CURSOR). Pretpostavimo da ste u okviru paketa deklarisali REF CURSOR i proceduru koja “prihvata” REF CURSOR.

create or replace package test_Pkg as
type cur_test is REF CURSOR;
end test_Pkg;

create or replace procedure testSP(param1 IN test_Pkg.cur_test, param2 OUT NUMBER)
as
begin
FETCH param1 into param2;
-- Mozda insert into neka_tabela... sta god...
end;

Uzimajući u obzir sve gore pomenuto u kodu je moguće uraditi sledeću stvar (kod nije prilagođen jeziku već je samo opisan postupak):

...
# Prethodni otvaranje konekcije i podesavanje parametara
#
# Pripremamo kod za izvrsenje
cmdTxt1 = "begin open :1 for select * from neka_tabela; end;";
# U zavisnosti od jezika se radi BIND za cmdTxt1
# nakon BIND-a, a u zavisnosti od jezika
conn.execute(cmdTxt1);
# Ovim smo otvorili kursor na strani klijenta!!!
# Radimo nesto sa tim podacim
...
# pripremamo kursor za povratak u Bazu
cmdTxt2 = "begin testSP (:1, :2); end;";
# pri cemu bind prvog parametra predstavlja obradjeni REF CURSOR!!!
conn.execute(cmdTxt2);
...

Da ponovim, nije moguće prosleđivati REF CURSOR među bazama! Na meta-linku sam pročitao da je postavljen zahtev za tom mogućnošću u JDBCu… da li će i kada će biti… videćemo.

Do tada, nadam se da će vam ovaj “trik” pomoći.


Oracle ROWNUM

Monday, 19.10.2009 – Darko

Kada bi vas pitali da razmislite I navedete koja je to bila najmanja i najtrivijalnija stvar koja je vašim kolegama i vama prilikom pisanja upita stvarala nevolje koje su obrnuto proporcionalne njenoj veličini, šta bi odgovorili?

Pa…?

Evo dok razmislite, ja ću reći šta bih ja odgovorio da ste me isto pitali prije par dana dok sam radio paginaciju u jednoj web aplikaciji: “ rad sa rownum-om!”.

Postoji li nešto što vas je više navelo na pomisao kako  u potpunosti shvatate šta znači i kako se sa tim radi, kao što je slučaj sa rownum-om, kada ga prvi put vidite?
A na kraju uhvatite sebe kako ne možete da shvatite zašto vaš najjednostavniji mogući upit ne radi kada upotrebljavate ovu riječ?

Ma ustvari , rownum i jeste izuzetno jednostavna stvar ali  zna da pravi velike probleme ljudima koji ne uđu u srž prirode ove pseudokolone…

Šta je rownum?

Rownum je upravo to  – pseudo kolona. Kolona koja ne postoji fizički u bazi već je RDBMS dinamički dodaje selektovanim kolonama dobijenih upitom , jedinstveno označavajući svaki od dobijenih redova autoinkrementirajućim brojem.

Kada i kako upotrebljavati rownum?

Rownum je sjajna funkcionalnost Oracle RDBMS-a ( naravno , slične i iste funkcionalnosti imaju i druge baze ) koja ima mnogo primjena.
Dvije vjerovano najveće primjene su prilikom

–    Prikazivanja N top vrsta u nekom upitu
–    Kreiranje paginacije u web aplikacijama

Implementacija paginacije u jednoj web aplikaciji me je upravo i navela da napišem ovaj tekst. Dakle, trebalo je izvesti da se klikom na strelicu navigatora , npr. udesno, selektuje samo N kolona , počevši od K-tog reda nekog upita.

Da vidimo kako bi to izgledalo. Recimo da je N=10 , a K=31.
Ono što nama treba je, svakako:

Select * from moja_tabela where rownum between 31 and 40;

Ovaj upit, naravno, vraća  0 vrsta.
Ali postavlja se pitanje zašto vraća nula vrsta?

Zašto npr. upit

Select * from moja_tabela where rownum < 10

vraca rezultat – 9 prvih vrsta.

a upit

Select * from moja_tabela where rownum > 10

ne vraca nista ( iako u rezultatu postoji vise od 10 vrsta )?

Da bi to znali, vratimo se na priču o prirodi rownum-a.

Rownum se prilikom izvršavanja upita vezuje ( ali se ne setuje odmah ) za vrstu  prije ispitivanja uslova i bilo kakvog sortiranja, i vrlo je važno znati da:

1.    Prva vrsta uvijek ima rownum = 1
2.    Rownum se dalje inkrementira tek nakon što je uspješno setovan u početnoj/prethodnoj vrsti.

Zbog ovoga npr. upit

select * from moja_tabela where rownum = 2

nikada neće vratiti rezultat. Šta se dešava: upit će selektovati prvu vrstu, i provjeriti da li je 1 = 2. Ovo nije tačna tvrdnja i rownum se neće inkrementirati, niti će se prva vrsta selektovati. Prema tome, ni jedna naredna vrsta neće ispunjavati ovaj uslov.

Isto je i za upit

Select * from moja_tabela where rownum > 10

Ali zato, ako kažemo

Select * from moja_tabela where rownum < 2

selektovaće se prva vrsta iz tabele moja_tabela, provjeriće se da li je 1 < 2. Pošto je ova tvrdnja istinita i vrsta će se selektovati a rownum će joj se setovati na 1.
Rownum se zatim inkrementira ( pošto je uspješno setovan ) i tako dalje za sljedeću vrstu…

Zbog ove osobine ćete sresti dosta ljudi koji tvrde da se sa rownum-om ne mogu selektovati bilo koje vrste iz upita, već samo prvih N , upotrebljavajući “where rownum < N “.

Dakle, kako ovo prevazići?

Veoma lako!
Podupitom!

Jednostavno:

Select * from
(
Select moja_tabela.* , rownum as vrsta from moja_tabela
)
where vrsta > 2

Dodijelimo alijas koloni rownum, selektujmo je sa originalnim upitom i onda profiltrirajmo rezultat.

Takođe, kada se radi o npr. grupisanju (sjećate li se, dodjela rownum-a se vrši prije grupisanja pa se može desiti da je rownum totalno izmješan nakon grupisanja ) , vrlo je važno znati postaviti stvari po pravom redoslijedu.
Npr:


select * from
(
Select a.*, rownum as vrsta from
(
Select *  from moja_tabela order by id asc
) a
)
where vrsta > 2

Kao sto sam na početku rekao, zaista se radi o “najmanjoj i najtrivijalnijoj stvari” , ali i kao takva zna da stvori nevolje i otvori pitanja…
Do prije paginacije nisam ni ulazio mnogo u razmišljanje o ovoj temi. Možda jer nikad ranije nisam imao potrebu pisati upite sa rownum-om osim sa “where rownum = 1“ ili “where rownum < N“…
Međutim, kada su problemi počeli da se pojavljuju, ispalo je da rad sa rownum-om baš i nije kakav sam zamišljao da jeste…
E sad, da li je ovo moglo biti urađeno malo jednostavnije za upotrebu – mislim da jeste…
Po meni, provjera uslova „where rownum > N“ se trebao implementirati onako kako korisnik zamišlja da treba da radi: da se svi uslovi isprovjeravaju, sve se izgrupiše pa tek onda selektuju vrste na osnovu ovog uslova… Ipak bi  ( po meni ) rownum trebao biti tu samo da “izbroji” vrste koje smo željeli selektovati.

I, kakav bi bio vaš odgovor na početno pitanje?


Transparent Gateway problem: Oracle NUMBER -> MS SQL DECIMAL

Friday, 16.10.2009 – Dejan

Ovih dana smo imali jedan neočekivan problem prilikom prebacivanja podataka iz Oracle baze preko Transparent Gateway-a u jednu MS SQL Server bazu. Pojavljivala se nejasna greška:

ORA-28500: connection from ORACLE to a non-Oracle system returned this message

Pošto je sam tekst greške uopšten i ne prikazuje njen stvarni uzrok, izgubio sam nekoliko sati dok nisam otkrio u čemu je problem… A otkrio sam sasvim slučajno šta je uzrokovalo tu grešku, posmatrajući u debugging režimu podatke, koji se šalju MS SQL Serveru…

Naime, jedna od naših aplikacija se vrti na Windows platformi, gdje su lokalna podešavanja naštimana za njemački jezik (GERMAN_AUSTRIA.WE8MSWIN1252). Ta aplikacija se spaja na Oracle bazu sa podešavanjem AMERICAN_AMERICA.AL32UTF8, uzima određene podatke i preko TG4MSQL (Transparent Gateway for MS SQL) prebacuje ih u MS SQL Server bazu. Sve je radilo savršeno dok u igru nisu došli brojevi sa decimalama (npr. 104,589). Kada se pošalje cijeli broj (npr. 104), onda je sve u redu… MS SQL Server je bio podešen na AMERICAN_AMERICA.WE8MSWIN1252.

Posumnjao sam da MS SQL Server, tj. Transparent Gateway, ne prepoznaje decimalni separator kako treba… Pokušao sam simulirati drugačiji decimalni separator sa
TO_NUMBER(kolona,’999999999999999D999999′,’NLS_NUMERIC_CHARACTERS=”.,”’)
i sa
EXECUTE IMMEDIATE ‘alter session set NLS_NUMERIC_CHARACTERS=”.,”’;
, ali nije donijelo pozitivan rezultat…

Nakon googleanja i metalinkovanja uspio sam pronaći putokaz, koji me doveo do rješenja.

Ključni parametar, koji se mora podesiti u konfiguracijskoj init.ora datoteci (%ORACLE_HOME%\tg4msql\admin\initExampleMSSQL_SID.ora) je HS_LANGUAGE !

Nakon što sam taj parametar podesio na:

HS_LANGUAGE=AMERICAN_AMERICA.WE8MSWIN1252

mogli su se prebaciti i decimalni brojevi, a greška se više nije pojavljivala…


Ponuda za posao (Beč, Austrija): Oracle Database Administrator

Friday, 09.10.2009 – Dejan

U zadnje vrijeme sam dobio nekoliko zanimljivih ponuda za posao, od kojih sam jednu prihvatio, tako da uskoro prelazim u drugu firmu (u sklopu Erste banke u Austriji). Od preostalih firmi sam zamoljen da se raspitam, ima li još kandidata za poziciju Oracle DBA, pa ako je neko zainteresovan da radi u Beču kao Oracle DBA, neka mi se javi. Trenutno je ova ponuda najzanimljivija:

Naziv firme: Vienna Insurance Group (Wiener Städtische –  http://www.wienerstaedtische.at/ , http://www.wienerstaedtische.com/group/unternehmen/konzernstruktur/)
Mjesto: Beč
Pozicija: Oracle DBA
Vrsta zaposlenja: interno na neodređeno vrijeme
Plata: 4 000 – 4 500 EUR bruto mjesečno (14 puta godišnje) + jednom godišnje bonus u iznosu jedne mjesečne plate, znači ukupno 15 plata godišnje
Uslovi: min. 2 godine radnog iskustva, solidno znanje njemačkog jezika, radna viza za Austriju

Zainteresovani neka mi pošalju svoj CV na njemačkom jeziku na adresu dejanATbaze-podataka.net.

Edit (04.11.2009.): Mjesto je popunjeno, hvala svima, koji su se javili…


Oracle Tipps & Tricks: Kako da spojim zapise (rekorde) u jedan string? [How to make a string out of records?]

Monday, 21.09.2009 – Dejan

U ovom članku ću napisati rješenje za problem – kako spojiti više zapisa u jedan string.

Kao primjer ću uzeti jedan “real world case”, kojeg i ja lično imam u svakodnevnoj praksi.

Naime, imam tabelu, koja sadrži popis aplikacija i podatke o osobama, koje rade na toj aplikaciji. Kada nešto mijenjam u bazi, moram putem E-Maila obavijestiti sve osobe zadužene za određenu aplikaciju. Naravno da bih mogao napisati jednu petlju i pomoću UTL_MAIL slati pojedinačno E-Mail svakome, ali sam za ovu, a i druge svrhe, odlučio napisati funkciju, koja mi vraća selektovane zapise (u ovom slučaju ime, prezime i E-Mail adresu) spojene u jedan string.

Potrebno je najprije kreirati uskladištenu proceduru ili funkciju, koja prima dva parametra (REF CURSOR i delimiter), a kao rezultat vraća “zaljepljeni” string, tj. sve selektovane zapise spojene u jedan string.

  CREATE OR REPLACE FUNCTION sfJoinRecords
  (
      pCursor IN sys_refcursor,
      pDelimiter IN VARCHAR2 := ','
  ) RETURN VARCHAR2
  IS
    lvTempValue   VARCHAR2(32767);
    lvReturnValue  VARCHAR2(32767);

  BEGIN
    LOOP
      FETCH pCursor INTO lvTempValue;

      EXIT WHEN pCursor%NOTFOUND;

      IF lvReturnValue IS NOT NULL THEN
          lvReturnValue := lvReturnValue || pDelimiter;
      END IF;

      lvReturnValue := lvReturnValue || lvTempValue;

    END LOOP;

    CLOSE pCursor;

    RETURN lvReturnValue;
  END sfJoinRecords;
  /

Korištenje ove funkcije izgleda ovako:

  SELECT ap.first_name,
         ap.last_name,
         ap.email_address
    FROM tb_apps_persons ap
   WHERE ap.app_id = 8
     AND ap.email_address IS NOT NULL;
FIRST_NAME LAST_NAME  EMAIL_ADDRESS
---------- ---------- -----------------------------------
Pero       Peric      pero.peric@bazepodataka.net
Marko      Markovic   marko.markovic@bazepodataka.net
Laza       Lazic      laza.lazic@bazepodataka.net
  SELECT DISTINCT
                  ap.first_name ||' '||
                  ap.last_name ||' <' ||
                  ap.email_address ||'>'
               as persons
             FROM tb_apps_persons ap
            WHERE ap.app_id = 8
              AND ap.email_address IS NOT NULL;
PERSONS
--------------------------------------------------
Laza Lazic <laza.lazic@bazepodataka.net>
Pero Peric <pero.peric@bazepodataka.net>
Marko Markovic <marko.markovic@bazepodataka.net>
SELECT sfJoinRecords(
         CURSOR(
           SELECT DISTINCT
                  ap.first_name ||' '||
                  ap.last_name ||' <' ||
                  ap.email_address ||'>'
             FROM tb_apps_persons ap
            WHERE ap.app_id = 8
              AND ap.email_address IS NOT NULL),
       ',') as persons
  FROM dual;
PERSONS
-------------------------------------------------------------------------------------------------------------------
Laza Lazic <laza.lazic@bazepodataka.net>,Pero Peric <pero.peric@bazepodataka.net>,Marko Markovic <marko.markovic@bazepodataka.net>

Naravno da se ova funkcija može koristiti za bezbroj drugih stvari, pa ako ju budete koristili u praksi, napišite u komentaru za šta ju koristite, kako bi i drugi dobili ideju ili iskoristili tu metodu.

Još jedna veoma bitna napomena: ukoliko funkciju koristite u kompleksnijim DML upitima u sklopu nekog paketa, onda ćete vjerovatno morati u specifikaciju paketa (package header) dodati ovo ograničenje:

PRAGMA RESTRICT_REFERENCES(sfJoinRecords, WNDS);

Objašnjenje za WNDS: “Asserts that the subprogram writes no database state (does not modify database tables).”


Oracle Tipps & Tricks: Kako rasparčati niz znakova [How to split a string]

Tuesday, 15.09.2009 – Dejan

Ukoliko imate zadatak da rasparčate neki string (koristiću ovaj uvriježeni naziv umjesto prijevoda “niz znakova”), možete to obaviti na ovaj način.

Za tu svrhu moramo kreirati jednu funkciju, kojoj kao ulazni parametar predajemo string, a koja nam vraća array sa parčadima tog stringa:

CREATE OR REPLACE FUNCTION SplitString
  (pString     IN VARCHAR2,
   pDelimiters IN VARCHAR2
  ) RETURN DBMS_SQL.VARCHAR2S
IS
  StringArray DBMS_SQL.VARCHAR2S;
BEGIN

  WITH tblString AS 
      (SELECT pString AS StringToSplit
         FROM dual)
  SELECT SUBSTR(StringToSplit, beg + 1, end_p - beg - 1) token
   BULK COLLECT INTO StringArray
   FROM (SELECT beg, 
                lead(beg) OVER (ORDER BY beg) end_p, 
                StringToSplit
           FROM (SELECT beg, StringToSplit
                   FROM (SELECT LEVEL beg, 
                                StringToSplit
                           FROM tblString
                        CONNECT BY LEVEL <= LENGTH(StringToSplit)
                        )
                   WHERE INSTR(pDelimiters ,SUBSTR(StringToSplit, beg, 1)) > 0
               UNION ALL
                 SELECT 0, StringToSplit 
                   FROM tblString
               UNION ALL
                 SELECT LENGTH(StringToSplit) + 1, StringToSplit 
                   FROM tblString
                )
         )
    WHERE end_p IS NOT NULL
      AND end_p > beg + 1;
      
  RETURN StringArray;
END;
/

Primjer korištenja te funkcije izgleda ovako:

DECLARE
  laStringArray dbms_sql.varchar2s;
  lvString VARCHAR2(512) := 'Prvo;parce;dummy;teksta;a;ovo;je;parce;zadnje';
BEGIN
  laStringArray := SplitString(lvString,';');
  
  -- ispisi zeljenu parcad pojedinacno: 
  dbms_output.put_line('Prvo parce stringa: '|| laStringArray(1));
  dbms_output.put_line('Zadnje parce stringa: '|| laStringArray(laStringArray.COUNT));
  
  -- ili npr. svu parcad pomocu petlje:
  FOR i IN laStringArray.FIRST..laStringArray.LAST
  LOOP
    dbms_output.put_line('Parce br. '|| TO_CHAR(i) ||': '|| laStringArray(i));
  END LOOP;
  
END;

a rezultat izgleda ovako:

Prvo parce stringa: Prvo
Zadnje parce stringa: zadnje
Parce br. 1: Prvo
Parce br. 2: parce
Parce br. 3: dummy
Parce br. 4: teksta
Parce br. 5: a
Parce br. 6: ovo
Parce br. 7: je
Parce br. 8: parce
Parce br. 9: zadnje

Nadam se da će vam koristiti. 🙂