Checklist per l’ottimizzazione di routine ABAP per SAP BW

Condividi questo articolo con chiunque pensi possa trovarlo utile
Tempo stimato per la lettura: 6 minuti

In questo articolo scopriremo un utile check-list di tutte le cose a cui fare attenzione per ottimizzare una routine ABAP scritta per SAP BW (e SAP BW4/HANA). Cercherò di fornire uno strumento agile e snello da poter tenere sempre a portata di mano.

Se questi argomenti ti interessano e vuoi rimanere sempre aggiornato iscriviti alla News letter

Perchè ho deciso di creare una checklist sull’ottimizzazione delle routine ABAP per BW?

Capita molto spesso di dover scrivere codice, a volte anche molto complesso nelle transformazioni di un flusso BW.

A volte basta rileggere alcune informazioni da una tabella mentre in altri casi è necessario creare veri e propri algoritmi che consentano di ottenere il risultato sperato.

In ogni caso, la scrittura di questo codice può notevolmente cambiare le performance di caricamento dell’intero flusso. Ecco perché in molti casi diventa essenziale riuscire a scrivere routine attentamente ottimizzate e revisionate anche in ottica delle perfomance.

L’importanza, e di conseguenza l’attenzione necessaria in fase di ottimizzazione, ovviamente dipende dai casi.

In caricamenti che presuppongono moli dati da milioni di righe e che vengono effettuati regolarmente è indispensabile prestare molta attenzione al codice, in caricamenti spot o che prevedono poche decine di record, l’impatto è notevolmente più basso.

Quadro generale

Questa è una check list stilata nell’ottica di ottimizzare le prestazioni di routine in ABAP all’interno di flussi e modelli dati su BW e BW4/HANA.

Di conseguenza la logica con cui viene pensata questa check-list è di fornire uno strumento che tenga in considerazione che il codice si troverà a lavorare con pacchetti di dati.

Questo è vero sia nel caso di routine presenti nelle transformation che caricano un flusso che nel caso in cui si ragioni su un estrattore in function module o un ampliamento ad un estrattore standard.

 

Check-list operativa

Select single

La prima cosa a cui fare assolutamente attenzione è l’uso delle select single.

Mi capita molto spesso nel mio lavoro di vedere select single all’interno di loop (se vuoi approfondire un pò di più chi sono e di cosa mi occupo, puoi leggere la pagina Chi sono ).

Questo perché è semplice ed immediato scrivere il codice con questa modalità, tuttavia l’effetto che si ottiene è quello di peggiorare in maniera davvero evidente le prestazioni del caricamento.

La select single presuppone che si vada a selezionare il valore preciso di un campo (o di una serie di campi) in base alle condizioni espresse nelle where condition della select stessa.

Poiché il risultato di questa select sarà solamente di un valore e non restituirà una tabella contenente più valori, è necessario impostare correttamente le where condition in modo da selezionare il valore corretto.

Questo viene fatto andando a loopare il pacchetto dati in nostro possesso ed utilizzare il valore dei campi presenti nella riga che stiamo esaminando per filtrare i possibili risultati della select.

Di conseguenza effettuiamo una select all’interno di un loop, il che implica che per ogni riga del nostro pacchetto ripetiamo l’operazione di select.

Ovviamente questo approccio è poco efficiente perché viene fatto un nuovo accesso al database per ogni riga, rovinando inevitabilmente le performance.

Esempio di codice

Loop at result_package assigning <result_fields>.

Select single nome campo

From  nome tabella

Into <result_fields>- nome campo

Where campo tabella del DB = campo del <result_field>.

Endloop.

Consiglio di ottimizzazione

Come detto, la problematica sta nell’effettuare un accesso al database per ogni riga del nostro pacchetto.

Pertanto la soluzione consiste nel fare un unico accesso (più grande) portandosi le informazioni in una tabella interna per poi rileggerle riga per riga.

Questo rende enormemente più veloce il processo in quanto abbiamo minimizzato il collo di bottiglia.

Limitando quindi ad una sola occasione questo accesso, abbiamo la possibilità di prelevare tutte le righe di nostro interesse.

Le successive letture della tabella interna sono molto più veloci in quanto le informazioni non sono più presenti solamente su una tabella fisica, ossia scritte sulla memoria fisica dell’applicazione, ma le abbiamo anche in memory nel nostro programma e quindi già in nostro possesso.

Pertanto l’ottimizzazione richiede di :

    • Definire una taballa interna con il/i campi di nostro interesse (tenendo conto anche delle chiavi che ci servono nelle where condition)
    • Effettuare una select di tutte le righe di nostro interesse e posizionandole nella tabella interna appena creata ( potrebbe rivelarsi utile utilizzare una for all entries ma dipende ovviamente dai casi)
    • Sostituire la select single all’interno del loop con delle read table.

 

Per approfondire la questione rimando ad un articolo che mostra le differenze di performance.

Read su Standard Table

In un precedente articolo (Fondamenti di ABAP: Tabelle interne, cosa sono e quale tipologia scegliere) mi sono soffermato sulle tipologie di tabelle interne e sulle peculiarità di ognuna di esse.

Abbiamo capito che le select single vanno sostituite con delle letture di tabelle interne, ma dobbiamo prestare attenzione alla tipologia di tabella che decidiamo di utilizzare.

Una situazione molto frequente di scarsa ottimizzazione del codice si verifica quando si prova a fare una read table di una tabella standard.

La standard table è una tabella che non ha bisogno di chiave e pertanto non ha un elemento secondo cui i record vengono ordinati né un indice di lettura associato ad uno o più campi.

Questo fa si che tutti i campi hanno la stessa “importanza” all’interno della tabella e che quindi i record sono inseriti semplicemente nell’ordine in cui vengono selezionati dalla select.

In una situazione in cui non si ha un ordinamento delle righe e si voglia effettuare una ricerca, l’unica possibilità a nostra disposizione è quella di effettuare una lettura sequenziale.

L’algoritmo, cioè, leggerà la prima riga e se non trova quello che sta cercando passerà alla successiva fino a quando non troverà la riga di interesse.

Ovviamente questo non è il modo più intelligente e veloce di leggere una tabella. Equivarrebbe a voler cercare il numero di telefono  di una persona all’interno dell’elenco telefonico (che esempio da vecchio) e iniziare a sfogliarlo dalla prima riga fino all’ultimo.

Possibili ottimizzazioni

Esistono due possibili soluzioni a questo problema che ottengono risultati migliori in base al numero di record della tabella interna:

    • Utilizzare una tabella interna di tipo sorted o hashed
    • Continuare ad utilizzare la tabella standard, sortarla in base all’elemento per cui faremo la read e poi effettuare read con binary search

Sono entrambe soluzioni valide e performanti.

Personalmente preferisco utilizzare la prima strada in quanto effettuo letture per chiave della tabella e perché, se voglio utilizzare una tabella sorted o hashed, sono costretto a dover riflettere sulla chiave della tabella.

In questo modo mi viene imposto di conoscere la granularità della tabella interna e quante righe mi aspetto per una determinata chiave (ricorda che esiste anche la sorted with non unique key).

Questo apparente onere mi da il vantaggio di sapere se posso effettuare una read perché mi aspetto di trovare univocamente una riga in base alla mia chiave oppure se sono costretto a loopare la tabella.

Consiglio di ottimizzazione

Per la ragione appena spiegata, parlerò della prima strada.

La differenza algoritmica che fa si che le performance migliorano sta nel fatto che utilizzando una tabella ordinata, avremo  uno o più campi (la chiave della tabella interna) che determinano l’ordine delle righe al suo interno.

Per questa ragione quando vorrò leggere questa tabella potrò farlo in maniera selettiva e non in sequenziale.

Riprendendo l’esempio dell’elenco telefonico, equivarrebbe a dire che poiché l’elenco è ordinato per cognomi in ordine alfabetico e io sto cercando il numero associato ad un cognome mi basterà iniziare a leggere l’elenco esattamente dalla pagina, anzi dalla riga in cui è presente il cognome che mi interessa.

Il vantaggio è evidente.

Ovviamente ci sarà da pagare un piccolo prezzo in fatto di performance nel momento in cui la tabella viene popolata in quanto deve essere anche ordinata, ma questa è un’attività che viene eseguita una sola volta per pacchetto e non è un’attività molto onerosa per cui ne vale abbondantemente la pena.

Questa ottimizzazione pertanto richiede di:

    • Sostituire la tabella standard con una sorted o hashed (in base alla mole dati che ci si aspetta per la tabella in questione)
    • Definire una corretta chiave di questa tabella
    • Nel momento in cui viene effettuata la read, tipicamente all’interno di un loop, utilizzare esattamente la chiave che abbiamo definito in precedenza per selezionare il nostro record.

 

Per chi volesse approfondire questo discorso può far riferimento ad un articolo abbastanza dettagliato e tecnico che può trovare qui

Loop all’interno di altri loop

Un altro  aspetto molto rilevante è quello dei loop.

Un loop è un ciclo in cui si “guarda” una riga per volta della tabella in questione. Questo presuppone che più il nostro pacchetto o la nostra tabella interna hanno moli dati importanti, tanto più tempo ci vorrà per completare il ciclo.

Esistono situazioni in cui è necessario eseguire dei cicli all’interno del loop sul pacchetto di dati.

Questo accade quando, ad esempio, stiamo loopando il nostro result_package e per ognuna delle righe dobbiamo associare un valore presente in un’altra tabella.

Se questa tabella interna ha una granularità maggiore rispetto al result_package, ci troviamo più righe da associare a quella del pacchetto e potremmo dover aggregare, oppure dobbiamo selezionarne una specifica scartandone le altre.

Per questa casistica non esiste un’unica ottimizzazione e non è detto che si possa evitare il doppio loop. Dipende dal contesto e da qual è l’obiettivo.

Il consiglio è quello sicuramente di utilizzare il loop con delle where condition limitandoci così ad analizzare solamente le righe che ci interessano.

Altro consiglio valido potrebbe essere quello di lavorare sulla tabella interna prima di looparla. Se ad esempio avessimo più righe con la medesima chiave di lettura (ossia con i campi in comune con il result_package) potremmo utilizzare la delete adjacent duplicats per eliminare le righe duplicate oppure potremmo doverle collettare secondo questa chiave per sommarle.

 

Into corresponding fields of

Torniamo ad un aspetto puramente tecnico e che non dipende molto dal contesto o dalla situazione specifica.

Abbiamo capito che non vanno usate le select single e quindi ci siamo creati la nostra tabella interna, dopo di che ci siamo interrogati sulla tipologia di tabella da utilizzare e la chiave da definire.

È il momento di effettuare il nostro accesso al DB e riempire questa tabella interna.

Nel farlo selezioneremo una serie di campi della tabella fisica e li sposteremo nei campi della nostra tabella interna.

Spesso si vedono istruzioni di “into corresponding fields of”. Questa istruzione sposta il campo della tabella fisica nel campo con lo stesso nome e la stessa lunghezza presente nella tabella interna.

L’algoritmo sotteso a questa istruzione andrà a cercare il campo all’interno della tabella interna e solo dopo averlo trovato andrà ad inserirvi i valori.

Ci sono situazioni in cui questa istruzione è necessaria, tuttavia è chiaro che stiamo chiedendo al nostro algoritmo di effettuare un passettino in più per ogni campo della nostra tabella perché non gli basterà selezionare le informazioni dal DB e metterle in una tabella interna ma per ogni campo dovrà prima “pensare” dove posizionarle.

Nel caso in cui la tabella interna dovesse avere più campi rispetto a quella del DB da cui preleviamo le informazioni, perché ad esempio abbiamo bisogno di campi che valorizzeremo in seguito mediante un’altra select, potremmo non poter fare a meno di questa istruzione.

Se tuttavia abbiamo gli stessi campi, nello stesso ordine, questa istruzione diventa del tutto inutile in quanto l’algoritmo prenderà le informazioni dalla tabella del DB nell’ordine in cui le trova e le sposterà nella nostra tabella interna.

Consiglio di ottimizzazione

Il consiglio in questo caso è davvero molto semplice e consiste nel definire la tabella interna rispettando l’ordine dei campi presente nella tabella fisica del DB ed eliminare l’istruzione “into corresponding fields of” dalla nostra select.

Questo consiglio potrebbe valere anche nel caso in cui avessimo più campi della tabella fisica perché ci basterà definire i campi in più alla fine della nostra tabella interna.

 

Selezionare tutti i record della tabella del DB e non solo quelli di interesse

Ci rimane da fare attenzione ad ultimo aspetto nella nostra select.

Abbiamo detto che vogliamo prendere alcune righe della tabella fisica e portarle in una tabella interna.

Ovviamente ci interessano solo le righe che hanno qualche legame con il pacchetto di dati che stiamo analizzando e non tutte le occorrenze della tabella stessa.

Non ha senso quindi riempire la nostra tabella interna con tutto quello che c’è nella tabella del DB per poi perdere molto tempo in fase di lettura per trovare quelle che ci interessano e scartare quelle “inutili”.

Possiamo applicare un filtro a monte nel momento in cui effettuiamo la select.

Per farlo ci basterà utilizzare l’istruzione “for all entries” .

Questa istruzione ci permetterà di dire che siamo interessati a selezionare tutte le righe per cui è verificata una condizione ossia per cui si ha un eguaglianza tra il valore di un campo del nostro pacchetto e quello della tabella da cui preleviamo i dati.

Facciamo un esempio per chiarirne il funzionamento.

Esempio di utilizzo della for all entries

Abbiamo il nostro pacchetto di dati che riporta il flusso delle vendite giornaliero dell’azienda.

Uno dei campi di questo flusso è il cliente che ha effettuato l’acquisto.

Ci serve trovare alcune informazioni di quel cliente, ad esempio l’indirizzo.

Pertanto effettueremo una select sulla tabella dei clienti per prendere il cliente e il suo indirizzo.

In questo caso ovviamente non ha senso prendere l’indirizzo di chiunque abbia acquistato almeno una volta dall’azienda ma ci basterà prendere solamente gli indirizzi dei clienti che sono presenti nel pacchetto di dati che stiamo analizzando.

Per farlo potremmo scrivere:

select cliente

indirizzo

from “nome tabella indirizzi”

into table “nome tabella interna”

for all entries in source_package

where cliente = source_package-cliente

 

Consiglio di ottimizzazione

Anche in questo caso il consiglio è abbastanza semplice ed immediato.

Ci basterà analizzare le select presenti e vedere se ci sono situazioni in cui è possibile snellirle selezionando solamente alcune righe.

Per farlo ci basterà inserire la condizione di for all entries.

Per una trattazione più completa rimando alla pagina ufficiale di SAP in cui se ne parla che potete consultare da questo link

 

Riassunto della check-list

Dopo aver elencato tutti i punti su cui fare attenzione è ora di riassumerli in un formato facilmente ricordabile e consultabile.

Per questo ho preparato la seguente check-list.

 

 

 

 

Spero che questa check list ti sia stata utile e che possa conservarla ed utilizzarla per il tuo lavoro quotidiano.

Se non vuoi perderti articoli come questo iscriviti qui sotto alla news letter

Condividi questo articolo con chiunque pensi possa trovarlo utile

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *