01 - Programmazione01a - RPG01e - Varie Programmazione IBM i

Chiamate ricorsive in RPG ILE ed SQL – parte 1

Si può fare una chiamata ricorsiva in RPG ? Impossibile !
Questa è spesso la prima risposta che si ottiene a questa domanda… Tutto vero, ma solo parzialmente. In RPG infatti non è possibile richiamare ricorsivamente un programma. Ma un programma ILE può contenere al suo interno diverse procedure, e qui il discorso cambia…
Le procedure infatti, grazie alle loro specificità e al loro livello di isolamento, possono essere richiamate ricorsivamente: tutto sommato, una procedura è una funzione che viene richiamata passando dei parametri, e restituisce un valore di ritorno (o nessuno). Questa caratteristica fa si che una chiamata ad una procedura possa avvenire tranquillamente anche all’interno della procedura stessa. Tutto sta a scrivere il codice in maniera opportuna, tenendo conto della ricorsività.

Presentiamo qui un classico esempio nel quale la ricorsività permette di ottenere un codice molto più semplice: lo sviluppo di una distinta base.
Per questo esempio abbiamo creato un file anagrafico che contiene il tipo e il codice dell’articolo, il tipo e il codice del componente, ed il fabbisogno di componente necessario. Il “tipo” identifica la tipologia del prodotto (finito, semilavorato, ecc…). Ogni articolo può ovviamente avere più componenti, per cui nel file esisteranno uno o più record per ogni articolo “padre”, e lo stesso avviene per i suoi componenti (che a loro volta sono “padri” di altri articoli).
Ecco l’istruzione SQL per la creazione della tabella (file).

CREATE OR REPLACE TABLE BOMDT00F (                 
 ITEMTYPE CHAR(1)       NOT NULL DEFAULT ' ' ,     
 ITEMCODE CHAR(20)      NOT NULL DEFAULT ' ' ,     
 COMPTYPE CHAR(1)       NOT NULL DEFAULT ' ' ,     
 COMPCODE CHAR(20)      NOT NULL DEFAULT ' ' ,     
 QUANTITY NUMERIC(9, 2) NOT NULL DEFAULT 0 )       
                                                   
 RCDFMT BOMDT   ;   

ITEMTYPE e ITEMCODE identificano l’articolo “padre”, mentre COMPTYPE e COMPCODE sono il componente.
Su questa tabella è stato creato un indice:

CREATE INDEX BomDT01L ON BomDT00F (  
 itemType, itemCode                  
 )                                                                       
 RCDFMT BOMDT   ;    

Questi sono i dati caricati per il nostro test:

Il programma di esempio legge la distinta base e scrive i risultati in un file di lavoro. Partendo da un articolo iniziale, ricevuto come parametro, il programma ricerca i suoi componenti, e per ogni componente ne calcola il fabbisogno scrivendo il file di lavoro. Il processo viene poi ripetuto ricorsivamente per ogni sottocomponente, scendendo di livello fino ad incontrare le materie prime (articoli che non hanno componente). Nell’uso di procedure ricorsive è importante stabilire sempre una condizione di uscita, in caso contrario si rischia un loop infinito.
Di seguito la definizione del file di lavoro:

CREATE OR REPLACE TABLE BOMWRK (                 
 LEVEL    NUMERIC(4, 0),                         
 ITEMTYPE CHAR(1)       NOT NULL DEFAULT ' ' ,   
 ITEMCODE CHAR(20)      NOT NULL DEFAULT ' ' ,   
 COMPTYPE CHAR(1)       NOT NULL DEFAULT ' ' ,   
 COMPCODE CHAR(20)      NOT NULL DEFAULT ' ' ,   
 QUANTITY NUMERIC(9, 2) NOT NULL DEFAULT 0 )     
                                                 
 RCDFMT WBOM   ;    

Questo è il source completo del programma FAQ40014B:

       ctl-opt decedit('0,') datedit(*dmy.)
       actgrp(*caller) dftactgrp(*no) option(*nodebugio:*srcstmt);
      //-------------------------------------------------------------*
      // Bill Of Material development
      //
      // Reads the BOM from base item and develops all components
      //  and related required quantities, down to raw material.
      // Output is written to a work file
      //
      // INPUT PARMS:
      //  - item type     CHAR(1)
      //  - item code     CHAR(20)
      //-------------------------------------------------------------*
      // BOM master file
       dcl-f BomDT01l  disk usage(*input) keyed;
       // work file
       dcl-f BomWRK    disk usage(*output) prefix(W_);
      // ENTRY     -------------------------------------------------
       dcl-pi FAQ40014B;
           dcl-parm pType    char(1);
           dcl-parm pCode    char(20);
       end-pi;
      // Prototypes ------------------------------------------------
       dcl-pr getBOM;
           *n char(1);
           *n char(20);
           *n zoned(9:2);
           *n zoned(4:0);
       end-pr;
      // Variables   -----------------------------------------------
       dcl-ds dsBOM     extname('BOMDT00F') end-ds;
       dcl-s  qty0      like(Quantity);
       dcl-s  level0    zoned(4:0);
      // PSDS ------------------------------------------------------
       dcl-ds pgmDS psds qualified;
           user     char(10) pos(254);
       end-ds;
      //*********************************************************
      // SQL options
       Exec SQL
          SET OPTION COMMIT = *None,
          ALWCPYDTA = *Yes,
          CLOSQLCSR = *EndMod,
          DLYPRP    = *Yes
         ;
      //  Clear work file
        Exec SQL
        DELETE FROM BomWRK ;

      // Get initial item
        chain (pType:pCode) BomDT01l;
        if %found(BomDT01l);
           qty0 = 1;
           level0 = 1;
           getBOM(pType:pCode:qty0:level0);
        endif;

        close *all;
        *inLR = *on;
        return;
        // --------------------------------------------------
        // BOM development
        // --------------------------------------------------
       dcl-proc getBOM;
      // Procedure interface -------------------
         dcl-pi getBOM ;
             dcl-parm   pType  char(1);        // item type
             dcl-parm   pItem  char(20);       // item code
             dcl-parm   pQty   zoned(9:2);     // required quantity
             dcl-parm   pLevel zoned(4:0);     // depth level
         end-pi;
      // BOM master file
       dcl-f BomDT01l  disk usage(*input) keyed;
      // Variables -------------------------------------------------
       dcl-s qtyComp    like(Quantity);
       dcl-s level      zoned(4:0);
       dcl-ds dsBOM2    extname('BOMDT01L':*all) end-ds;

        // read item's components and develop BOM
        setll (pType:pItem) BomDT01l;
        level = pLevel + 1;
        dou %eof(BomDT01l);
            reade (pType:pItem) BomDT01l dsBOM2;
            if not %eof(BomDT01l) and CompCode <> *blank;
               eval(h) qtyComp = pQty * quantity;
               exsr wrtFile;
               // recursive call
               getBOM(CompType:
                      CompCode:
                      qtyComp:
                      level);
            endif;
        enddo;

        // --------------------------------------------------
        // write item component to work file
        // --------------------------------------------------
        begsr wrtFile;
                Clear WBOM;
                W_Level    = pLevel;
                W_itemType = pType;
                W_itemCode = pItem;
                W_compType = compType;
                W_compCode = compCode;
                W_quantity = qtyComp;
                Write WBOM;
        endsr;
        // --------------------------------------------------
       end-proc;

Analizziamo in particolare la procedura getBOM: i parametri di input sono il tipo ed il codice dell’articolo “padre” ed il relativo fabbisogno. La procedura legge tutti i componenti dell’articolo padre, calcola il fabbisogno, scrive un record nel file di lavoro e richiama ricorsivamente la stessa procedura getBOM passando come parametri il tipo e codice del componente, ed il fabbisogno calcolato, che si propaga quindi al livello inferiore. Notare come il file BOMDT01L sia stato dichiarato localmente nella procedura; in questo modo ad ogni richiamo ricorsivo (che di fatto crea una nuova istanza della procedura nello stack dei richiami), non si perde il posizionamento sul file all’interno di cicli di lettura per chiave.
Al termine, il file di lavoro conterrà tutto lo sviluppo della distinta base, fino alle materie prime con i relativi fabbisogni. Lanciando il programma per l’articolo MADRID di tipo 4, ecco il risultato:

La colonna LEVEL indica il livello di profondità dello sviluppo: 1 è il prodotto finito, 2 il primo semilavorato ecc.. in questo caso ci si ferma a 3 livelli. Dal contenuto di questo file si capisce quindi che per produrre una unità di prodotto finito (articolo 4-MADRID), servono tre materie prime, WOOL-002, POLY-001, SILK-002 con un fabbisogno rispettivamente di 144, 104 e 14,29 “unità” (per restare generici non è indicata alcuna unità di misura specifica).

L’uso di procedure ricorsive facilita notevolmente alcune situazioni applicative tipiche, oltre alla distinta base pensiamo allo sviluppo di un albero di menù, oppure la definizione di itinerari in ambito logistica, con partenza e arrivo.
Nella prossima “puntata” vedremo come anche l’SQL non sia da meno e permetta di gestire la ricorsività.

Tutti i source di questo esempio sono disponibili su GitHub a questo link: https://github.com/MD2706GIT/FAQ400

DOCUMENTAZIONE
IBM – Recursive Calls: https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_74/rzasc/recursv.htm

About author

Senior Analyst/Developer e divulgatore in ambito IBM i. Già collaboratore dell'edizione italiana della rivista "System i News" ed autore di diverse pubblicazioni su tools e tecniche di sviluppo in ambiente IBM i.

Rispondi

%d blogger hanno fatto clic su Mi Piace per questo: