Not only developing

Luglio 20, 2009

Utilizzo delle collezioni in Oracle-PL/SQL

Archiviato in: Oracle — Tag:, — teox78 @ 12:56 pm

Le collezioni di Oracle semplificano sicuramente la programmazione in PL/SQL e sono in grado di velocizzare notevolmente alcuni processi.

Mostrerò come utilizzarle e includerò qualche esempio di impieghi particolari.

Innanzitutto creiamo il tipo

Create or replace type t_varchar2_table as table of varchar2(4000);

Operazioni base :

Create or replace procedure pr_base_test AS
--inizializzo la lista con zero elementi
x_Varchar2_Table t_varchar2_table := t_varchar2_table();
n_Step NUMBER;
BEGIN
--mettiamo 10 elementi nella lista
FOR n_Step IN 1..10 LOOP
x_Varchar2_Table.EXTEND;
x_Varchar2_Table(x_Varchar2_Table.LAST) := 'Row '||n_Step;
END LOOP;
--visualizzo quanti elementi ho nella lista
Dbms_Output.put_Line('Count = '||x_Varchar2_Table.COUNT);
--visualizzo ogni elemento in lista
FOR n_Step IN x_Varchar2_Table.FIRST..x_Varchar2_Table.LAST LOOP
Dbms_Output.put_Line(x_Varchar2_Table(n_Step);
END LOOP;
--controllo se un elemento è presente nella lista
IF 'Row 7' MEMBER OF x_Varchar2_Table THEN
Dbms_output.put_Line('OK');
END IF;
--Controllo se la lista è univoca, ovvero non ho duplicati
IF x_Varchar2_Table IS A SET THEN
Dbms_output.put_Line('IS A SET);
END IF;
--svuoto la lista
x_Varchar2_Table.DELETE;
END;
/

A partire da Oracle 10g sono presenti una serie di operatori multiset che permettono operazioni sulle liste. Eccone una lista con accanto l’equivalente operatore SQL :

MULTISET UNION DISTINCT UNION
MULTISET UNION UNION ALL
MULTISET UNION ALL UNION ALL
MULTISET INTERSECT INTERSECT
MULTISET INTERSECT DISTINCT N/A
MULTISET EXCEPT MINUS
MULTISET EXCEPT DISTINCT N/A

Per riempire una lista con dei valori provenienti da una query è semplicissimo :

declare
x_table_list t_varchar2_table := t_varchar2_table();
begin
select table_name BULK COLLECT INTO x_table_list
from user_tables
where table_name like 'A%';
--la lista ora contiene l'elenco delle tabelle che iniziano con A
end;

Naturalmente non bisogna abusare dell’uso delle liste in quanto consumano parecchia memoria. Gli utilizzi principali che ho riscontrato sono :

  • creo una lista di dati che poi andrò ad utilizzare parecchie volte : è inutile fare la join con la medesima tabella (magari di grandi dimensioni) molte volte quando è possibile caricare in memoria la lista di ID di cui necessito e unitilizzarla n volte.
  • sono molto utili per trasferire dati da una procedura all’altra : ad esempio, se ho un package che mi gestisce gli utenti e all’interno di questo una procedure che mi restituisce la lista degli utenti è inutile implementare tale logica in un altro package. Quindi il package che avrà bisogno dell’elenco degli utenti invocherà tale procedura e ne utilizzerà la lista ottenuta.
  • Ottimizzazione di insert/update/delete di tante righe grazie all’operatore FORALL

Ecco alcuni esempi :

Esempio 1

declare
x_table_list t_varchar2_table := t_varchar2_table();
n_count Number;
begin
select table_name bulk collect into x_table_list
from user_tables
where table_name like 'A%';
--use the list as a table
select count(*) into n_count
from user_tab_columns a, TABLE(x_table_list) b
where a.table_name = b.column_value;
dbms_output.put_line(n_count);
end;

Esempio 2

create table temp
as select * from user_tables
where 1 = 2;

declare
type t_user_tables is table of user_tables%ROWTYPE;
x_user_tables t_user_tables := t_user_tables();
n Number;
begin
select * bulk collect into x_user_tables
from user_tables;
FORALL n IN x_user_tables.FIRST..x_user_tables.LAST
Insert into temp values x_user_tables(n);
COMMIT;
End;

E se invece avessi bisogno di utilizzare una collezione all’interno di una query dinamica ?? In questo caso è obbligatorio utilizzare collezioni basate su tipi creati all’interno dello schema tramite il comando Create Type. Nell’esempio utlizzerò il tipo t_varchar2_table creato all’inizio del post.


create or replace PACKAGE pk_Test AS
PROCEDURE pr_Load;
PROCEDURE pr_ExecuteQuery;

FUNCTION fn_List RETURN T_Varchar2_Table;

END  pk_Test;

create or replace PACKAGE BODY pk_Test AS
kx_List T_Varchar2_Table := T_Varchar2_Table();

PROCEDURE pr_Load IS
BEGIN
SELECT Table_Name BULK COLLECT INTO kx_List
FROM User_Tables
WHERE Table_Name LIKE ‘A%’;

END pr_Test;

PROCEDURE pr_ExecuteQuery IS
v_Sql VARCHAR2(1000);
n_Count NUMBER;
BEGIN
v_Sql := ‘SELECT COUNT(*)
FROM User_Tables
WHERE Table_Name IN (
SELECT Column_Value FROM
TABLE ( pk_Test.fn_List )
)’;

EXECUTE IMMEDIATE v_Sql INTO n_Count;
Dbms_Output.put_line(n_Count);
END pr_ExecuteQuery;

FUNCTION fn_List RETURN T_Varchar2_Table IS
BEGIN
Return kx_List;
END fn_List;

END pk_Test;

Nella procedura pr_Load non faccio altro che caricare in una collezione tutte le tabelle che iniziano con A.

Nella procedura pr_ExecuteQuery  eseguo una query dinamica tramite il comando Execute Immediate  utilizzando la lista appena caricata che mi viene restituita dalla funzione fn_List.

t_user_tables

Maggio 13, 2009

Oracle : allocation and deallocation of a Clob

Archiviato in: Oracle — Tag:, — teox78 @ 2:38 pm

In a platform based on Oracle, I’m currently developing at work, there is a massive use of clobs so I must make sure to free all the resources allocated by clobs. To do this I created two procedures : one created a temporary clob, the other one frees the clob.

PROCEDURE pr_Open_CLOB(pc_Clob IN OUT NOCOPY CLOB) AS
BEGIN
pc_Clob:=EMPTY_CLOB;
DBMS_LOB.CREATETEMPORARY(pc_Clob, TRUE);
DBMS_LOB.OPEN(pc_Clob, DBMS_LOB.LOB_READWRITE);
END pr_Open_CLOB;


PROCEDURE pr_Close_CLOB(pc_Clob IN OUT NOCOPY CLOB) AS
BEGIN
IF DBMS_LOB.ISOPEN(pc_Clob)=1 THEN
DBMS_LOB.CLOSE(pc_Clob);
END IF;
IF DBMS_LOB.ISTEMPORARY(pc_Clob)=1 THEN
DBMS_LOB.FREETEMPORARY(pc_Clob);
END IF;
END pr_Close_CLOB;

This is how the 2 procedures should be used :

PROCEDURE pr_Test AS
c_Temp CLOB;
BEGIN
pr_Open_Clob(c_Temp);
--use clob
pr_Close_Clob(c_Temp);
EXCEPTION
WHEN OTHERS THEN
pr_Close_Clob(c_Temp);
END pr_Test;

In this way I make sure resources allocated by clobs are always released, even in case of exceptions.

Gennaio 23, 2008

Lanciare un processo sincrono da Oracle tramite Java

Archiviato in: Java, Oracle — Tag:, , — teox78 @ 4:02 pm

Ecco una breve classe java che permette di lanciare un qualsiasi processo del sistema operativo da Oracle.

CREATE OR REPLACE AND RESOLVE JAVA SOURCE NAMED "FileHandler" AS
import java.io.*;
import java.sql.*;
import oracle.sql.*;
import oracle.jdbc.driver.*;


class StreamReader extends Thread {
InputStream inputStream;
String prefix;

StreamReader(InputStream inputStream, String prefix) {
this.inputStream = inputStream;
this.prefix = prefix;
}

public void run() {
try {
InputStreamReader inputStreamReader = new InputStreamReader(
inputStream);
BufferedReader bufferedReader = new BufferedReader(
inputStreamReader);
String line = null;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(prefix + " " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

class StreamWriter extends Thread {
OutputStream outputStream;
String prefix;

StreamWriter(OutputStream outputStream) {
this.outputStream = outputStream;
}

public void run() {
try {
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
while (true) {
bufferedWriter.newLine();
}
} catch (IOException e) {

}
}
}

public class FileHandler {

public static int run (String command) {
Runtime runtime = Runtime.getRuntime();
Process process;
int exitValue = -1;
try {
process = runtime.exec(command);

StreamReader errorReader = new StreamReader(process.getErrorStream(), "<<ERROR>>");
StreamReader outputReader = new StreamReader(process.getInputStream(), "<<OUTPUT>>");
StreamWriter inputWriter = new StreamWriter(process.getOutputStream());
errorReader.start();
outputReader.start();
inputWriter.start();
exitValue = process.waitFor();

} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return exitValue;
}

};

La classe StreamReader serve per poter catturare lo standard output e lo standard error del processo lanciato e loggarli : in questo caso verranno scitti all’interno di trace file nella directory user dump di Oracle ma nulla ci vieta di inserire il tutto in una tabella per una consultazione più rapida. La classe StreamWriter invece impedisce che il processo si blocchi nel caso quest’ultimo sia in attesa sullo standard input. Nel mio caso ero solo interessato al fatto che il processo non si bloccasse quindi non faccio altro che simulare un invio da tastiera, però si potrebbero inviare delle informazioni tramite lo standard input se il processo lo richiedesse.

Il metodo run all’interno della classe FileHandler non fa altro che lanciare il processo e restituire il valore ritornato dal processo, solitamente 0 significa ‘terminato con successo’ e tutto il resto significa che si sono verificati degli errori.

Adesso creiamo una stored procedure che punti a questo metodo :

create or replace FUNCTION OSCommand_Run(Command IN STRING) RETURN NUMBER IS LANGUAGE JAVA NAME 'FileHandler.run(java.lang.String) return int';

Diamo i permessi necessari

EXEC DBMS_JAVA.grant_permission('SCHEMA-NAME', 'java.io.FilePermission', '<<ALL FILES>>', 'read ,write, execute, delete');
EXEC DBMS_JAVA.grant_permission(’SCHEMA-NAME’, ‘SYS:java.lang.RuntimePermission’, ‘writeFileDescriptor’, ”);
EXEC DBMS_JAVA.grant_permission(’SCHEMA-NAME’, ‘SYS:java.lang.RuntimePermission’, ‘readFileDescriptor’, ”);

A questo punto possiamo lanciare e monitorare un qualsiasi processo del sistema operativo da una stored procedure o package.

Novembre 27, 2007

Select * from C:\ ??? Ecco come farlo in Oracle

Archiviato in: Java, Oracle — Tag:, , , , — teox78 @ 3:53 pm

In questi giorni ho iniziato a sviluppare un nuovo processo che ha come database Oracle.

Prima di caricare alcuni file csv,l’utente deve avere la possibilità di vedere il contenuto di alcune directory non sempre visibili dalla macchina dell’utente quindi ho deciso che la soluzione ottimale fosse avere questo elenco all’interno di Oracle, ad esempio tramite una vista che interrogasse direttamente il filesystem. Ecco come fare tutto ciò :

  • CREATE OR REPLACE TYPE StringArray AS TABLE OF VARCHAR2(512);
  • CREATE OR REPLACE AND RESOLVE JAVA SOURCE NAMED "FileHandler" AS
    import java.io.*;
    import java.sql.*;
    import oracle.sql.*;
    import oracle.jdbc.driver.*;
    public class FileHandler {
    public static oracle.sql.ARRAY list(String dirName) throws SQLException {
    Connection conn = null;
    int ret_code;
    String[] files = null;
    File file = new File(dirName);
    if (file.exists()) {
    if (file.isDirectory()) {
    if (file.canRead()) {
    files = file.list();
    }
    }
    }
    try {
    conn = new OracleDriver().defaultConnection();
    ArrayDescriptor x_ad = ArrayDescriptor.createDescriptor(
    "STRINGARRAY", conn);
    ARRAY x_array = new ARRAY(x_ad, conn, files);
    return x_array;
    } catch (SQLException e) {
    ret_code = e.getErrorCode();
    System.err.println(ret_code + e.getMessage());
    conn.close();
    return null;
    } finally {
    closeConnection(conn);
    }
    }private static void closeConnection(Connection connection) {
    if (connection == null) {
    return;
    } else {
    try {
    connection.close();
    } catch (Exception e) {
    System.out.println(e.getMessage());
    }
    connection = null;
    }}
    };
    /
  • CREATE OR REPLACE FUNCTION list (p_path IN VARCHAR2) RETURN StringArray AS LANGUAGE JAVA NAME 'FileHandler.list (java.lang.String) return oracle.sql.ARRAY';
  • EXEC DBMS_JAVA.grant_permission('SCHEMA-NAME', 'java.io.FilePermission', '<<ALL FILES>>', 'read ,write, execute, delete');
    EXEC DBMS_JAVA.grant_permission('SCHEMA-NAME', 'SYS:java.lang.RuntimePermission', 'writeFileDescriptor', '');
    EXEC DBMS_JAVA.grant_permission('SCHEMA-NAME', 'SYS:java.lang.RuntimePermission', 'readFileDescriptor', '');
    GRANT JAVAUSERPRIV TO SCHEMA-NAME;

A questo punto sarà sufficiente fare una query come quella seguente per ottenere tutti i file e directory presenti sul disco C della macchina sulla quale c’è il database.

SELECT * FROM TABLE(LIST('C:\'))

Naturalmente questo è solo un esempio che mostra la strada che ,secondo me, è meglio seguire; si potrebbero creare strutture più complesse che oltre al nome del file restituiscano la dimensione o la data dello stesso.

Novembre 16, 2007

Oracle su Windows ? Attenzione!!

Archiviato in: Oracle, Windows — Tag:, — teox78 @ 3:33 pm

Ieri mattina, arrivo in ufficio e scopro che uno dei database oracle di produzione è giù! Vado nell’alert a vedere se c’è qualche errore e incappo in un
ORA-04030: memoria di processo esaurita nel tentativo di allocare 1483316 byte
La macchina virtuale ha 2.5 Gb di memoria virtuale e ha Windows 2003 Server 32 bit ; il task manager indica che l’occupazione in ram è di soli 2.1 Gb, e poi anche se superasse i 2.5Gb avrebbe ancora tutto lo swap.
Per farla breve, su windows Oracle non crea più processi come fa su uno Unix bensì fa tutto capo al processo oracle.exe che poi al suo interno avrà svariati thread; di conseguenza tutta la memoria allocata fa capo ad un solo processo che , su Windows 2003 server 32 bit , non può superare i 2 Gb di RAM allocata !!! A questo punto sono stato costretto ad abbassare la SGA_MAX_SIZE affinchè la somma di SGA e PGA non superassero i 2Gb.

Quindi, se già sapete di dover creare un database oracle che avrà bisogno di parecchia ram (più di 2 Gb), secondo me, le soluzioni possibili sono :

  • Passare alla versione 64 bit di Windows 2003 Server (ecco le migliorie)
  • Usare un sistema operativo Unix/Linux (scelta da me consigliata)
  • Se si è obbligati ad usare Windows a 32 bit ma il database può essere diviso,allora si possono creare due istanze sulla stessa macchina così i processi oracle diventano 2 e ognuno di loro potrà allocare 2 Gb.

Blog su WordPress.com.