Con questo numero del blog iniziamo una serie di articoli, tratti dal libro “RPG: Stile e produttività” di Massimo Duca (noto articolista del passato System-i News della Duke), nei quali presentiamo tecniche moderne, idee, esempi pratici per chi sviluppa su sistemiPower IBM. Informazioni e consigli utili per ottenere il meglio dalla nostra piattaforma preferita !
L’SQL è ormai un consolidato standard multipiattaforma (anche se permangono alcune differenze “dialettali” fra i vari sistemi), e la sua conoscenza è imprescindibile anche per gli sviluppatori RPG. Da molti anni IBM ha rivolto i propri sforzi nell’ottimizzazione dei motori SQL, che oggi garantiscono flessibilità, potenza ed ottime performance.
Utilizzare l’SQL incorporato nei programmi ha diversi vantaggi:
Selezione dinamica dei dati: con l’SQL è possibile definire dinamicamente l’accesso ai dati, selezionando solo i campi desiderati, creando join con altri file, specificando l’ordinamento, il tutto senza dover creare ulteriori viste logiche e loop di lettura complessi.
Prestazioni: grazie al Query Optimizer, l’SQL è in grado di stabilire da solo quali sono le migliori vie d’accesso per reperire i dati nel modo più performante, liberando i programmatori da questa valutazione. L’SQL permette di eseguire letture e scritture a blocchi di record (vedi più avanti), che garantiscono le massime prestazioni in caso di letture massive.
Leggibilità: la sintassi SQL per sua stessa natura risulta molto leggibile e più vicina ad un linguaggio “naturale”. Chi lavora da anni e anni in RPG non ha certo problemi a leggere un sorgente in puro RPG, ma più si acquista confidenza con l’SQL, più se ne apprezza la leggibilità, che diventa più evidente in presenza di statement complessi.
Portabilità: il fatto che l’SQL sia un linguaggio cross-platform rende possibile integrare le istruzioni in modo che accedano ad altri database. Oppure, una stessa istruzione SQL che fa parte di una applicazione scritta in un altro linguaggio, può essere replicata in un programma RPG semplicemente con un “copia e incolla”.
Insomma, molti vantaggi che si possono riflettere direttamente nelle nostre applicazioni. Per esempio, grazie all’SQL dinamico possiamo finalmente seppellire le odiate illeggibili OPNQRYF… Per carità, una volta per fare selezioni dinamiche c’era solo questa istruzione ed era già qualcosa, ma quei tempi sono decisamente passati ed ora è sicuramente consigliabile far sparire dai programmi questo residuo del secolo scorso e sostituirlo con una lettura SQL.
Vediamo un esempio pratico di come l’SQL incorporato possa migliorare l’efficienza dei programmi RPG.
Ciclo di lettura con cursore
La dinamicità dell’SQL permette di leggere dati da qualsiasi tabella (file fisico), mettendo insieme più tabelle in join, indicando clausole di filtro ed ordinamenti a scelta. Per poter sfruttare tutto questo in un programma RPG è necessario definire un “cursore”, ovvero una sorta di puntatore ai record restituiti dalla query specificata. Oltre al cursore va definita una DS che conterrà tutti i campi del record del file letto. Non necessariamente la DS deve contenere l’intero record; nel caso occorresse reperire solo alcuni campi è possibile definire una DS parziale oppure una variabile per ogni singolo campo letto dal file.
Di seguito un semplice esempio di loop di lettura di un file con cursore: da un file anagrafico vengono elaborati tutti i clienti non annullati con nazionalità italiana, in ordine di importo del fido decrescente.
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
dcl-ds DSAncli00f extname(Ancli00f) end-ds;
dcl-s $exit ind;
Exec SQL SET OPTION Commit = *None, CloSQLCsr = *EndActGrp, AlwCpyDta = *Yes;
Exec SQL DECLARE c1 CURSOR FOR SELECT * FROM Ancli00f WHERE Ata10 = ‘ ‘ AND NazCl = ‘ITA’ ORDER BY FidoCl DESC FOR FETCH ONLY ;
Exec SQL OPEN c1;
$exit = *off; dow $exit = *off; Exec SQL FETCH c1 INTO :DSAncli00f; select; when SQLCODE = 0;
// < elaborazione del record letto > . . . . when SQLCODE = 100; $exit = *on; other; iter; endsl; enddo; |
Riga | Spiegazione |
1-2 | definizione della DS che conterrà il record letto e della variabile di uscita dal loop. |
4-8 | opzioni SQL: controllo di sincronia non attivo (Commit = *None); chiusura cursori alla chiusura del gruppo di attivazione (CloSQLCsr = *EndActGrp), permessa all’Optimizer la copia dei dati in tabelle temporanee (AlwCpyDta = *Yes). |
10-16 | dichiarazione del cursore SQL. Qui c’è il vero “cuore” della query che verrà eseguita in seguito. La clausola FOR FETCH ONLY (o FOR READ ONLY) migliora le prestazioni quando il file è in sola lettura e non sono richiesti aggiornamenti. |
19-20 | apertura del cursore. Qui l’SQL esegue l’elaborazione dell’istruzione contenuta nella DECLARE ed identifica i risultati. |
22-23 | avvio loop |
24-25 | lettura di un singolo record dal set di risultati elaborato nella OPEN. In questo caso tutti i campi del file vengono caricati nella struttura dati DSAncli00f. |
27 | nel campo SQLCODE è contenuto un valore con cui verificare l’esito della FETCH. Un valore 0 indica tutto OK, record letto. |
31 | se SQLCODE=100 abbiamo raggiunto la fine del set di risultati; viene acceso l’indicatore di uscita dal ciclo. |
33 | se SQLCODE assume qualsiasi altro valore, qualcosa è andato storto… il record non viene considerato e si passa al prossimo. |
Per brevità abbiamo scelto una query abbastanza semplice, ma ovviamente nella DECLARE CURSOR è possibile specificare più file in join, colonne derivate da estrazione o concatenazione di stringhe, funzioni di aggregazione (totali, conteggi, valori massimi e minimi) e chi più ne ha più ne metta.
Il codice nazione usato come filtro può anche essere variabile: supponendo che sia contenuto nella variabile codNaz, sarà sufficiente impostare la DECLARE CURSOR in questo modo:
1
2 3 4 5 6 7 |
Exec SQL
DECLARE c1 CURSOR FOR SELECT * FROM Ancli00f WHERE Ata10 = ‘ ‘ AND Acnaz = :codNaz FOR FETCH ONLY ; |
Occhio a non dimenticare i due punti prima del nome della variabile !
Nel caso di lettura parziale del record (cioè solo di alcuni campi), le istruzioni DECLARE e FETCH si modificano in questo modo:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
dcl-s codice like(CodCl);
dcl-s localitalike(LocCl); dcl-s indirizzo like(IndCl); dcl-s caplike(CapCl); Exec SQL DECLARE c1 CURSOR FOR SELECT CodCl, LocCl, IndCl, Capcl FROM Ancli00f WHERE Ata10 = ‘ ‘ AND NazCl = ‘ITA’ ORDER BY FidoCl DESC FOR FETCH ONLY ; Exec SQL FETCH c1 INTO :codice, :localita, :indirizzo, :cap; |
Nel prossimo numero del blog vedremo un utilizzo più sofisticato di questa tecnica: la lettura per blocchi di record.