giovedì 22 dicembre 2016

JAVA - Portable Java 8 JDK on Windows

Spesso se si è in ambienti protetti, e quando dico protetti significa che non puoi installare nulla sulla macchina che ti hanno messo a disposizione, se devi installare una JDK per far funzionare SQLDeveloper possono sorgere dei problemi.
Per questo oggi vediamo come installare una JDK 8 su Windows partendo dal file di installazione exe.

Ecco di seguito i vari passi:

  1. Download  Java 8 SDK for Windows from Oracle.  Io ho fatto il download di jdk-8u111-windows-x64.exe.
  2. Aprire il .exe con un tool che effettui gli unzip, esempio 7-Zip o altri 
  3. Navigare sotto la directory .rsrc fino a 
      1. ..\Downloads\jdk-8u111-windows-x64.exe\.rsrc\1033\JAVA_CAB10\111\ 
  4. Qui si trova un denominato tools.zip.
  5. Aprirlo e scompattarlo in una directory     ..\Java\jdk1.8.0_111\ estraendone il contenuto
  6. A questo punto una volta estratti tutti i record, poizionarsi nella home della directoy java ed eseguire da una finestra dos il seguente comando:
    • for /R %f in (.\*.pack) do @"%cd%\bin\unpack200" -r -v -l "" "%f" "%~pf%~nf.jar"
  7. Se tutto termina correttamente effettuare per sicurezza un reboot della macchina Windows ed aprire poi SQLDeveloper fornendo il path della directory java.
  8. Fare una prova effettuando un javac -version


 A questo punto avete installato una JDK 8 Portable e funzionante.

lunedì 5 dicembre 2016

ODI 11g-12c - Groovy Come creare un utente ODI ed assegnazione profili

Di seguito due script per la creazione di un utente ODI con relativa assegnazione di profili. Di seguito due versioni una per ODI 11g e una per ODI 12c, la seconda utilizza delle funzioni che possono essere richiamata all'interno di un main.
  • ODI 11g
// Transaction operators:
import oracle.odi.core.persistence.transaction.support.DefaultTransactionDefinition;
import oracle.odi.core.persistence.transaction.ITransactionDefinition;
import oracle.odi.core.persistence.transaction.ITransactionManager;
import oracle.odi.core.persistence.transaction.ITransactionStatus;
// Secutity class:
import oracle.odi.domain.security.finder.IOdiUserFinder
import oracle.odi.domain.security.OdiUserCreationServiceImpl
import oracle.odi.domain.security.OdiUser
import oracle.odi.domain.security.OdiProfile;
import oracle.odi.domain.security.finder.IOdiProfileFinder
// Variabili
def txnDef = new DefaultTransactionDefinition()
def tm = odiInstance.getTransactionManager()
def txnStatus = tm.getTransaction(txnDef)
String  username="PIPPO";
String  pwd="PIPPO1";
String  supervisor="false";
        expirationDate=null;

try {
         odiUser = ((IOdiUserFinder) odiInstance.getTransactionalEntityManager().getFinder(OdiUser.class)).findByName(username);
     
           println("  User              :-->"+odiUser.getName());
           println("  User Note         :-->"+odiUser.getNotes());
           println("  Pwd Date Expire   :-->"+odiUser.getPasswordExpiracyDate());
           println("  Expiracy Date     :-->"+odiUser.getAccountExpiracyDate());
          
           Collection odiUserPro =(odiUser.getOdiProfileList())
            for (Object kk : odiUserPro) {
              OdiProfile odiPro =(OdiProfile)kk ;
              println("  Profile           :-->"+odiPro.getOdiProfileList().getProfileName());
              println("  Profile           :-->"+odiPro.getOdiProfileList().getInternalId());
            }
           
}catch(NullPointerException e_101){

        // obtaining the generic profiles
        genericprofile_connect = odiInstance.getTransactionalEntityManager().getFinder(OdiProfile.class).findByName("CONNECT") ;
        genericprofile_designer = odiInstance.getTransactionalEntityManager().getFinder(OdiProfile.class).findByName("DESIGNER") ;
        genericprofile_topology_admin = odiInstance.getTransactionalEntityManager().getFinder(OdiProfile.class).findByName("TOPOLOGY ADMIN") ;    

        // creating user
    OdiUserCreationServiceImpl service = new OdiUserCreationServiceImpl(odiInstance)
    OdiUser newUser = service.createOdiUser(username, pwd.toCharArray(), supervisor.toBoolean(), expirationDate );

        // adding profiles to the user
        newUser.addOdiProfile(genericprofile_connect);
        newUser.addOdiProfile(genericprofile_designer);
        newUser.addOdiProfile(genericprofile_topology_admin);

        NewodiUser = ((IOdiUserFinder) odiInstance.getTransactionalEntityManager().getFinder(OdiUser.class)).findByName(username);
           println("  User              :-->"+NewodiUser.getName());
           println("  User Note         :-->"+NewodiUser.getNotes());
           println("  Pwd Date Expire   :-->"+NewodiUser.getPasswordExpiracyDate());
           println("  Expiracy Date     :-->"+NewodiUser.getAccountExpiracyDate());
          
 }

tm.commit(txnStatus) 



  • ODI 12c
import oracle.odi.domain.project.finder.IOdiProjectFinder
import oracle.odi.domain.model.finder.IOdiDataStoreFinder
import oracle.odi.domain.project.finder.IOdiFolderFinder
import oracle.odi.domain.project.finder.IOdiKMFinder
import oracle.odi.domain.mapping.finder.IMappingFinder
import oracle.odi.domain.adapter.project.IKnowledgeModule.ProcessingType
import oracle.odi.domain.model.OdiDataStore
import oracle.odi.core.persistence.transaction.support.DefaultTransactionDefinition
import oracle.odi.domain.security.finder.IOdiUserFinder
import oracle.odi.domain.security.OdiUserCreationServiceImpl
import oracle.odi.domain.security.OdiUser
import oracle.odi.core.OdiInstance
import oracle.odi.domain.security.OdiProfile;


// creating  user (Pippo)

def new_user(user, pwd, supervisor ) {
     expirationDate = null

    txnDef    = new DefaultTransactionDefinition()
    tm        = odiInstance.getTransactionManager()
    tme       = odiInstance.getTransactionalEntityManager()
    txnStatus = tm.getTransaction(txnDef)

    // checking if whether user exists
    userf = (IOdiUserFinder) tme.getFinder(OdiUser.class)

    // creating user
    OdiUserCreationServiceImpl service = new OdiUserCreationServiceImpl(odiInstance)
    OdiUser newUser = service.createOdiUser(user, pwd.toCharArray(), supervisor.toBoolean(), expirationDate );
   
    tm.commit(txnStatus)
    return newUser
}

def adding_profiles(username) {

    txnDef    = new DefaultTransactionDefinition()
    tm        = odiInstance.getTransactionManager()
    tme       = odiInstance.getTransactionalEntityManager()
    txnStatus = tm.getTransaction(txnDef)
        
        // finding the user
        user = odiInstance.getTransactionalEntityManager().getFinder(OdiUser.class).findByName(username) ;
        // obtaining the generic profiles
        genericprofile_connect = odiInstance.getTransactionalEntityManager().getFinder(OdiProfile.class).findByName("CONNECT") ;
        genericprofile_designer = odiInstance.getTransactionalEntityManager().getFinder(OdiProfile.class).findByName("DESIGNER") ;
        genericprofile_topology_admin = odiInstance.getTransactionalEntityManager().getFinder(OdiProfile.class).findByName("TOPOLOGY ADMIN") ;
        // adding profiles to the user
        user.addOdiProfile(genericprofile_connect);
        user.addOdiProfile(genericprofile_designer);
        user.addOdiProfile(genericprofile_topology_admin);
    tm.commit(txnStatus)
    return user;
}

//  add odi user "Pippo " and grant profiles like CONNECT, DESIGNER, TOPOLOGY ADMIN
FirstUser1 = new_user("Pippo", "1234abcd5678","false")
FirstUser1Profiles = adding_profiles("Pippo")


Provate e fatemi sapere

venerdì 1 aprile 2016

RDBMS ORACLE - Partition Range By Interval - parte 2

Continuiamo con un esempio di quanto indicato nella parte 1 e due possibili soluzioni.

CREATE TABLE "ICP_ECONOMICO" 
   (
"PERIOD" NUMBER
   ) 
  PARTITION BY RANGE ("PERIOD") INTERVAL (1) 
 (PARTITION "P_0"  VALUES LESS THAN (196905) ) ;

Creato table "ICP_ECONOMICO".


set lines 300
col interval for a30
col TABLE_NAME for a30

select table_name, PARTITIONING_TYPE, STATUS, INTERVAL, PARTITION_COUNT,
(select count(*) from user_tab_partitions pts where pts.table_name = pt.table_name) real_count
from user_part_tables pt
where table_name ='ICP_ECONOMICO'
order by table_name;

TABLE_NAME  PARTITION STATUS   INTERVAL  PARTITION_COUNT REAL_COUNT
----------- --------- -------- --------- --------------- ----------
ICP_ECONOMICO  RANGE     VALID    1          1048575          1

A questo punto inseriamo un record.

insert into ICP_ECONOMICO (period) values(20160101);

Errore con inizio alla riga : 178 nel comando -
insert into ICP_ECONOMICO (period) values(20160101)
Report error -
Errore SQL: ORA-14300: la chiave di partizionamento è mappata a una partizione non inclusa nel numero massimo consentito di partizioni
14300. 00000 -  "partitioning key maps to a partition outside maximum permitted number of partitions"
*Cause:    The row inserted had a partitioning key that maps to a partition number greater than 1048575
           *Action  Ensure that the partitioning key falls within 1048575 partitions or subpartitions.

Come si vede abbiamo l'errore che dicevamo, adesso se portiamo da 1 a 100 il by interval vediamo cosa accade:

ALTER TABLE ICP_ECONOMICO  SET INTERVAL (100);
Table ICP_ECONOMICO modificato.

Inseriamo una riga come prima.

insert into ICP_ECONOMICO (period) values(20160101);
1 riga inserito.

A questo punto se rifacciamo la query di sopra abbiamo:

set lines 300
col interval for a30
col TABLE_NAME for a30

select table_name, PARTITIONING_TYPE, STATUS, INTERVAL, PARTITION_COUNT,
(select count(*) from user_tab_partitions pts where pts.table_name = pt.table_name) real_count
from user_part_tables pt
where table_name ='ICP_ECONOMICO'
order by table_name;

TABLE_NAME     PARTITION STATUS   INTERVAL  PARTITION_COUNT REAL_COUNT
-------------- --------- -------- --------- --------------- -----------------------------
ICP_ECONOMICO     RANGE     VALID    100           1048575          2


Se proviamo ad inserire altri record il problema non sembra più presentarsi a meno che non inserisce un valore che abbia un numero di cifre maggiore ad 8.

insert into ICP_ECONOMICO (period) values(99991201);
1 riga inserito.

insert into ICP_ECONOMICO (period) values(999912010);

Report error -
Errore SQL: ORA-14300: la chiave di partizionamento è mappata a una partizione non inclusa nel numero massimo consentito di partizioni
14300. 00000 -  "partitioning key maps to a partition outside maximum permitted number of partitions"
*Cause:    The row inserted had a partitioning key that maps to a partition number greater than 1048575
           *Action  Ensure that the partitioning key falls within 1048575 partitions or subpartitions.

A questo punto se modifichiamo nuovamente il valore dell'intervallo

ALTER TABLE ICP_ECONOMICO  SET INTERVAL (1000);
Table ICP_ECONOMICO modificato.
           
           
set lines 300
col interval for a30
col TABLE_NAME for a30

select table_name, PARTITIONING_TYPE, STATUS, INTERVAL, PARTITION_COUNT,
(select count(*) from user_tab_partitions pts where pts.table_name = pt.table_name) real_count
from user_part_tables pt
where table_name ='ICP_ECONOMICO'
order by table_name;  


TABLE_NAME     PARTITION STATUS   INTERVAL  PARTITION_COUNT   REAL_COUNT
-------------- --------- -------- --------  ----------------- ----------
ICP_ECONOMICO     RANGE     VALID    1000     1048575          3


insert into ICP_ECONOMICO (period) values(999912010);
1 riga inserito.

A questo punto sembra che il  numero di cifre sia legato al valore dell'intervallo di partizionamento e più precisamente:

Intervallo 1        = Numero di cifre 6
Intervallo 100    = Numero di cifre 8
Intervallo 1000  = Numero di cifre 9

se inserisco adesso un numero di cifre pari a 10 avrò nuovemente l'errore.

insert into ICP_ECONOMICO (period) values(9999120101);

Report error -
Errore SQL: ORA-14300: la chiave di partizionamento è mappata a una partizione non inclusa nel numero massimo consentito di partizioni
14300. 00000 -  "partitioning key maps to a partition outside maximum permitted number of partitions"
*Cause:    The row inserted had a partitioning key that maps to a partition number greater than 1048575
           *Action  Ensure that the partitioning key falls within 1048575 partitions or subpartitions

 E qui vi lascio perchè non so dare alcuna spiegazione a questo comportamento.







RDBMS ORACLE - Partition Range By Interval - parte 1


Salve oggi brevemente vi parlo di un paio di bug di oracle, che sono una gran rottura di BIP e sono legati al partizionamento di una tabella by interval su un campo numerico e by interval su campo data.
Inoltre se si aggiunge al primo partizionamento anche un sottopartizionamento by hash che supera le 4 partizioni siamo al top.

Uno si può chiedere perchè utilizzare questo tipo di partizionamento e la risposta come al solito è dentro di voi ed è pure sbagliata. No a parte le cazzate il perchè p molto semplice le partizioni si autocreano e non c'è bisogno di un software che le crei, questo fa si che la gestione viene demandata completamente al database, purtroppo con le conseguenze indicate sotto.
Purtroppo non ho molto tempo per spiegarvi il problema vi rimando alle note che affrontano nel dettaglio i bugs.

  • Document  1507993.1 ORA-1841 on Select from Interval Partitioned Table when Predicate is 31-Dec-9999
  • Document 1479115.1 Interval Partitioning Essentials - Common Questions - Top Issues Document 1447928.1 PARTITION_COUNT Shows Large Value 1048575 With Interval Partitioning
  • Document 754642.1 How the PSTART/PSTOP Numbers are computed when a Interval Partition is used
  • Document 1472941.1 Insert Fails With ORA-14300 On Partition Table  
Per quanto riguarda la prima nota esiste un workaround che è quello di non far partire la data dal 01 del mese ma di farla partire dal 17 del mese. Il bug in questo caso è legato alla trasformazione della data Juliana nel normale formato anno mese e giorno.
Il secondo bug invece è una emerita stronza.. ed è legato ad uno pseudo valore massimo di partizioni in oracle che però non ha alcuna attinenza col valore contenuto nel campo.
Per verificare quante siano le partizioni indicate sul sistema e quante ve ne siano realmente basta eseguire la seguente query:


set lines 300
col interval for a30
col TABLE_NAME for a30
select table_name, PARTITIONING_TYPE, STATUS, INTERVAL, PARTITION_COUNT,
(select count(*) from user_tab_partitions pts where pts.table_name = pt.table_name) real_count
from user_part_tables pt
where table_name ='MV_FACT_TDB'
order by table_name;

TABLE_NAME                     PARTITION STATUS   INTERVAL      PARTITION_COUNT        REAL_COUNT
------------------------------ --------- -------- ------------- ---------------------- ----------
MV_FACT_TDB                    RANGE     VALID    100                         1048575          2


Dove la tabella ha in realtà 2 partizioni sottopartizionate by hash 4-




Vi lascio alla lettura delle note e dei relativi workaround, appena ritrovo i test che avevo fatto vi integro questo scarno documento.



martedì 8 marzo 2016

ORACLE SQLDEVELOPER - Query Master e Figli

Salve oggi vi voglio far vedere come creare in SQLDeveloper un Repor Utente che abbia come padre una query per l'estrazione delle sessioni su un istanza oracle e poi due differenti modi per creare dei report figli che una volta selezionata la sessione in automatico, se possibile, generano l'explain plan.
Il primo passo è quello di creare un report, quindi andare su visualizza e selezioare Report in modo da aprire il tab dei report.


A questo punto posizionarsi su Report create dall'utente e fare new.
Una volta aperta la Gui in Query_SQL inserire la query che diventerà il master delle esecuzioni, di seguito ne facciamo una semplice:

  • SELECT  INST_ID, SQL_ID,SQL_TEXT FROM GV$SQL
A questo punto creiamo due Report Figli nei quali il valore SQL_ID verrà estratto dalla query indicata sopra. In questo modo ogni volta che ci posizioniamo su una riga estratta dalla query indicata sopra in automatico verranno calcolti nei figli l'explain plan.

Di seguito occorrerà creare du Report Figli aventi due differenti stili uno Tabella ed uno Output DBMS/PLSql.







A questo punto occorrerà inserire le Query Sql che ricevono in input SQL_ID selezionato nella master query.






A questo punto abbiamo terminato salviamo il tutto ed ogni volta che eseguiamo la query master e scegliamo una riga avremo nei figli il relativo explain plan.







In questo modo potete personalizzare ogni tipo di query con Master e Figli.




giovedì 3 marzo 2016

RDBMS ORACLE - Utilizzo di DBMS_SQL per SPOOL su un file

Un amico mi ha chiamato e mi ha chiesto come funziona DBMS_SQL allora ho riscoperto due vecchie procedure e funzioni in cui si utilizza questo package per effettuare lo spool su file di una select oppure di una intera tabella.
Visto che mi piace condividere di seguito il codice:
Dobbiamo creare 3 funzioni ed una procedura di seguito i nomi:


  • Funzione IS_NUMBER_CHAR
  • Funzione ISDATE
  • Funzione DUMP_CSV
  • Procedura DUMP_TABLE_TO_CSV

Le prime due sono solo di contorno e vengono utilizzate nella procedura per capire se il contenuto del campo di cui stiamo per fare lo spoo sia Number, Varchar o Date a seconda della tipologia l'output viene scritto in modo diverso.


CREATE OR REPLACE  FUNCTION is_number_char(
    p_value VARCHAR2)
  RETURN NUMBER
-- 0 Alpha
-- 1 Number
-- 3 Errore
AS
  n_num NUMBER     :=3;
  v_num varchar2(30):=NULL;
BEGIN
  SELECT
    CASE
      WHEN INSTR(p_value,'-')>1
      THEN 'alpha'
      WHEN p_value LIKE '%.%.%'
      THEN 'alpha'
      WHEN p_value LIKE '-%-%'
      THEN 'alpha'
      WHEN REGEXP_LIKE(p_value,'^[-0-9.,]*$')
      THEN 'numeric'
      ELSE 'alpha'
    END is_numeric
  INTO v_num
  FROM dual;
  
IF v_num='alpha' THEN n_num:=0; elsif v_num='numeric' THEN n_num:=1; END IF;

RETURN n_num;

exception
WHEN OTHERS THEN
  n_num:=3;
  RETURN n_num;
END;

CREATE OR REPLACE FUNCTION isdate(
    p_string IN VARCHAR2,
    p_fmt    IN VARCHAR2 := 'yyyy-mm-dd hh24:mi:ss' )
  RETURN NUMBER
AS
  l_date DATE;
BEGIN
  l_date :=to_date(p_string,p_fmt);
  RETURN 0;
EXCEPTION
WHEN OTHERS THEN
  RETURN 1;
END;

A questo punto vi incollo il codice della procedura per lo spool del contenuto di una tabella, che riceve in input il nome della tabella, la directory di spool ed il nome del file.

CREATE OR REPLACE PROCEDURE dump_table_to_csv(
    p_tname    IN VARCHAR2,
    p_dir      IN VARCHAR2,
    p_filename IN VARCHAR2 )
IS
  l_output utl_file.file_type;
  l_theCursor   INTEGER DEFAULT dbms_sql.open_cursor;
  l_columnValue VARCHAR2(4000);
  l_status      INTEGER;
  l_query       VARCHAR2(1000) DEFAULT 'select * from ' || p_tname;
  l_colCnt      NUMBER := 0;
  l_separator   VARCHAR2(1);
  L_DESCTBL DBMS_SQL.DESC_TAB;
  L_QUOTE VARCHAR2(1);
BEGIN
  L_OUTPUT := UTL_FILE.FOPEN( P_DIR, P_FILENAME, 'w', 32760);

-- Altero il formato data per lo spool

  EXECUTE immediate 'alter session set nls_date_format=''yyyy-mm-dd hh24:mi:ss'' ';

  dbms_sql.parse( l_theCursor, l_query, dbms_sql.native );
  DBMS_SQL.DESCRIBE_COLUMNS( L_THECURSOR, L_COLCNT, L_DESCTBL );

  FOR i IN 1 .. l_colCnt
  LOOP
    utl_file.put( l_output, l_separator || '"' || l_descTbl(i).col_name || '"' );

    DBMS_SQL.DEFINE_COLUMN( L_THECURSOR, I, L_COLUMNVALUE, 4000 );
    l_separator := ';';

  END LOOP;
  utl_file.new_line( l_output );
  l_status := dbms_sql.execute(l_theCursor);

  WHILE ( dbms_sql.fetch_rows(l_theCursor) > 0 )
  LOOP
    l_separator := '';
   
   FOR i IN 1 .. l_colCnt
    LOOP
      DBMS_SQL.COLUMN_VALUE( L_THECURSOR, I, L_COLUMNVALUE );
     
   -- Verifico la tipologia del contenuto del campo

      IF IS_NUMBER_CHAR(l_columnValue) =0 AND ISDATE(l_columnValue) =0 THEN
        l_quote                       := '"';

      -- Se di tipo Varchar lo metto tra doppi apici 
      
        l_columnValue := REPLACE(l_columnValue,'"','""');
      ELSE
        L_QUOTE := '';
      END IF;
      UTL_FILE.PUT( L_OUTPUT, L_SEPARATOR || L_QUOTE || L_COLUMNVALUE || L_QUOTE);
      l_separator := ';';
    END LOOP;
    utl_file.new_line( l_output );

  END LOOP;
  
   dbms_sql.close_cursor(l_theCursor);
  utl_file.fclose( l_output );

-- Altero il formato rimettendolo come era prima

  EXECUTE immediate 'alter session set nls_date_format=''dd-MON-yy'' ';

EXCEPTION
WHEN OTHERS THEN
  EXECUTE immediate 'alter session set nls_date_format=''dd-MON-yy'' ';
  raise;
END;

Per lanciarlo possiamo usare:

DECLARE
  P_TNAME VARCHAR2(200);
  P_DIR VARCHAR2(200);
  P_FILENAME VARCHAR2(200);
BEGIN
  P_TNAME := 'GAIA_FILE1_STEP1_WORK';
  P_DIR := 'DATA_PUMP_DIR';
  P_FILENAME := 'GAIA_FILE1_STEP1_WORK.LOg';

  DUMP_TABLE_TO_CSV(
    P_TNAME => P_TNAME,
    P_DIR => P_DIR,
    P_FILENAME => P_FILENAME
  );
--rollback; 
END;

Adesso vediamo l'utilizzo del DBMS_SQL per lo spool fornendo in input una select.
Questa non è opera mia e non ricordo nemmeno più dove abbia recuperato questa funzione di spool, però come si suol dire funziona. Forse da AskTom ma non ricordo, risale ad almeno dieci anni fà.

create or replace function  dump_csv( p_query     in varchar2,
                                      p_separator in varchar2 default ',',
                                      p_dir       in varchar2 ,
                                      p_filename  in varchar2,
                                      p_header    IN VARCHAR2,
                                      p_endline   IN VARCHAR2 DEFAULT chr(13))
   -- This routine makes certain assumptions.
   -- 1) There must be a query and it can't be greater then 32K.
   -- 2) The separator must only be one character in length and can't be 
   --    a CR, LF, binary 0, or null (easy to change).
   -- 3) If the p_dir parameter is null, the p_filename must contain the
   --    path and filename (/tmp/output.txt)
   -- 4) If the p_header parameter is not null, then insert it into the first
   --    row of the output file. If the p_separator parameter is not a comma, 
   --    the comma's in the header string will be replaced with the new
   --    separator. so to add a header use 'NAME,FIRST_NAME,LAST_NAME' and if
   --    the separator is a tab, what is put into the file would be 
   --    'NAME<tab>FIRST_NAME<tab>LAST_NAME'
   -- 5) The value of p_endline will be appended to the end of each line of the 
   --    output file. It can be used to add a carriage return before the 
   --    Line Feed is inserted by the NEW_LINE (unix). If the server is running
   --    on a windows machine, set this to null since the NEW_LINE will save 
   --    a CR,LF pair anyway. This can also be used if you needed to put 
   --    something at the end. For exanple "'|'||CHR(13)" which would put a 
   --    vertical bar and CR,LF on each line on a unix machine.
   -- 
   -- The following are the returned error codes
   -- -1 The query is empty
   -- -2 The output filename is empty
   -- -3 The separator is invalid.
   -- -4 The filename only contains the path, no filename specified.
   -- -5 The output file can not be opened.
   -- -6 The query could not be parsed. It was illegal.
   --  0 The query returned NO records.
   -- >0 The number of records returned.

return number
is
    l_output        utl_file.file_type;
    l_theCursor     integer default dbms_sql.open_cursor;
    l_columnValue   varchar2(20000000);
    l_status        integer;
    l_colCnt        number default 0;
    l_separator     varchar2(10) default '';
    l_cnt           number default 0;
    l_dir           VARCHAR2(500);
    l_filename      VARCHAR2(32);
    x_pnt           NUMBER(4);
    l_header        VARCHAR2(200);
begin
    -- sanity check the input
    IF p_query IS NULL THEN
       RETURN(-1);
    END IF;
    IF p_filename IS NULL THEN
       RETURN(-2);
    END IF;
    -- Do not allow CR, LF,binary 0, or null to be used as a separator. 
    -- The length of the separator must be 1 if it exists.
    IF p_separator IS NULL OR
       p_separator IN (chr(13),chr(10),chr(0)) OR
       length(p_separator) > 1 THEN
       RETURN(-3);
    END IF;
    -- If the directory parameter is blank, assume that the directory
    -- is included in the filename.
    IF p_dir IS NOT NULL THEN
       l_dir := p_dir;
       l_filename := p_filename;
    ELSE
       x_pnt := instr(p_filename,'/',-1,1);
       -- If no path is specified or no filename is specified,
       -- the procedure will not work... get out.
       IF x_pnt = 0 OR x_pnt = length(p_filename) THEN
          RETURN(-4);
       END IF;
       l_dir := substr(p_filename,1,x_pnt-1);
       l_filename := substr(p_filename,x_pnt+1);
    END IF;


    -- Check to see if the file can be opened. If ANY error is 
    -- encountered, exit with a count of -1;
    BEGIN
       l_output := utl_file.fopen( l_dir, l_filename, 'w', 32767 );
    EXCEPTION
       WHEN OTHERS THEN
          RETURN(-5);
    END;

    -- Check to see if the query can be processed. if ANY error is
    -- encountered, close the output file and exit.
    BEGIN
       dbms_sql.parse(  l_theCursor,  p_query, dbms_sql.native );
    EXCEPTION
       WHEN OTHERS THEN
          utl_file.fclose( l_output );
          RETURN(-6);
    END;

    -- If the p_header parameter is not null, then insert the line as
    -- the first line in the output file. This is used if the user wants
    -- to insert column headings. Make sure to use a comma in your header 
    -- line and the routine will replace all comma;s with the specified 
    -- separator.
    l_header := NULL;
    IF p_header IS NOT NULL THEN
       l_header := p_header;
       IF p_separator <> ',' THEN
          l_header := REPLACE(l_header,',',p_separator);
       END IF;
    END IF;

    -- Loop through all the parameters for the select. To support
    -- unknown querys, the assumption is that the query will return
    -- all columns as varchar2 columns where the data is correctly
    -- formatted for inport. A maximum of 255 columns are supported
    -- in the query. Each column can't be greater then 2000
    -- characters in length.

    
    
    for i in 1 .. 255 loop
        begin
            dbms_sql.define_column( l_theCursor, i, l_columnValue, 2000 );
            l_colCnt := i;
        exception
            when others then
                if ( sqlcode = -1007 ) then exit;
                else
                    raise;
                end if;
        end;
    end loop;
    -- This define_column insures that at least one column is defined for the
    -- routine.
    dbms_sql.define_column( l_theCursor, 1, l_columnValue, 2000 );

    -- Fire the query.

    l_status := dbms_sql.execute(l_theCursor);

    -- Loop through all the rows returned by the query. Build up the output file
    -- by looping through the defined columns.

    loop
        exit when ( dbms_sql.fetch_rows(l_theCursor) <= 0 );
        IF l_cnt = 0 AND l_header IS NOT NULL THEN
           utl_file.put(l_output, l_header);
           utl_file.put(l_output, p_endline);
           UTL_FILE.NEW_LINE (l_output,1);
           l_cnt := 1;
        END IF;
        l_separator := '';
        for i in 1 .. l_colCnt loop
            dbms_sql.column_value( l_theCursor, i, l_columnValue );
            utl_file.put( l_output, l_separator || l_columnValue );
            l_separator := p_separator;
        end loop;
       utl_file.put(l_output, p_endline);
       UTL_FILE.NEW_LINE (l_output,1);
       l_cnt := l_cnt+1;
    end loop;

    -- Processing done. close the cursor and output file.

    dbms_sql.close_cursor(l_theCursor);
    utl_file.fclose( l_output );
    
    -- Return the number of rows built in the csv file.

    return l_cnt;
    -- If Any error occures outside of the errors checked above, then raise 
    -- and error and blow out the procedure.
    EXCEPTION
       WHEN OTHERS THEN
          RAISE;
end dump_csv;


Per lanciarla basta eseguire quanto indicato di seguito, ha però qualche problemino nella generazione dell'header. Forse per questo non è mai stata usata.

DECLARE
  P_QUERY VARCHAR2(20000);
  P_SEPARATOR VARCHAR2(200);
  P_DIR VARCHAR2(200);
  P_FILENAME VARCHAR2(200);
  P_HEADER VARCHAR2(200);
  P_ENDLINE VARCHAR2(200);
  v_Return NUMBER;
BEGIN
  P_QUERY := 'SELECT SOCIETA,  SETT_CON, SOCIETA_CONTROPARTE,
          IMPORTO,  IMPONIBILE,  IVA_COSTO,  BOLLO,
          SCENARIO,  CUSTOM1,  CUSTOM2,  CUSTOM3,  CUSTOM4,
         FROM GAIA_FILE1_STEP1_WORK';
  P_SEPARATOR := ',';
  P_DIR := 'DATA_PUMP_DIR';
  P_FILENAME := 'GAIA_FILE1_STEP1_WORK.CSV';
  P_HEADER := '######';
  P_ENDLINE := 'chr(13)';

  v_Return := DUMP_CSV(
    P_QUERY => P_QUERY,
    P_SEPARATOR => P_SEPARATOR,
    P_DIR => P_DIR,
    P_FILENAME => P_FILENAME,
    P_HEADER => P_HEADER,
    P_ENDLINE => P_ENDLINE
  );

DBMS_OUTPUT.PUT_LINE('v_Return = ' || v_Return);

  :v_Return := v_Return;
--rollback; 
END;


Finito.. a Paole.. vanno bene come esempi?
Dimenticavo altre info le trovate qui: