Scenario:
- tabella di milioni di righe, dove si può assumere che non ci siano modifiche/eliminazioni a ritroso
- data warehouse in cui si voglia creare un QVD per periodo
Succede abbastanza di frequente, soprattutto con tabelle che si limitano a loggare eventi.
Assumendo che la lunghezza del periodo del singolo QVD sia un mese, ecco quel che si può ragionevolmente voler ottenere:
- evitare di ricaricare i dati già caricati
- evitare l'intervento manuale: deve capire da solo cosa ricaricare
- se un periodo dev'essere ricaricato, dev'essere sufficiente cancellare il qvd
Insomma, la scaletta delle cose da fare sarebbe la seguente:
- caricare la lista di periodi esistenti sul database
- per ogni periodo, eccetto quello odierno:
- se c'è già il QVD, saltarlo a piè pari
- se non c'è già il QVD, caricare e salvarlo
- per il periodo odierno, salvare e sovrascrivere il qvd
Faccio un esempio con Oracle come database:
// i .qvd saranno del tipo Tabella200802.qvd,
// oppure TabellaMeseOdierno.qvd
set prefissoQvd=Tabella;
trace Carico la lista di tutti i mesi...;
tmpAnniMese:
load
AAAAMM as annoMese
;
SQL SELECT DISTINCT TO_CHAR(DATA, 'YYYYMM') AAAAMM FROM TABELLA;
let iMax=noofrows('tmpAnniMese')-1;
let meseOggi=peek('annoMese', -1, 'tmpAnniMese');
for i=0 to $(iMax);
let mese=peek('annoMese', $(i), 'tmpAnniMese');
let righeFile='0' & qvdnoofrecords('$(prefissoQvd)$(mese).qvd');
if ($(righeFile) = 0) then
trace Carico $(mese)...;
AnagJn:
load ...;
SQL SELECT ... FROM TABELLA WHERE TO_CHAR(DATA, 'YYYYMM') = '$(mese)';
trace Salvo $(mese) su QVD...;
// verifico se è il mese odierno, per scegliere il nome del file
if ('$(mese)' = '$(meseOggi)') then
store AnagJn into $(prefissoQvd)MeseOdierno.qvd;
else
store AnagJn into $(prefissoQvd)$(mese).qvd;
end if
drop table AnagJn;
else
trace Salto $(mese)!;
end if
next i
drop table tmpAnniMese;
Essenzialmente, carica in una tabella temporanea tutti i valori distinti di anno-mese, e poi per ogni riga di quella tabella va a verificare se il file non esiste ancora (o se è il mese corrente), se sì lo carica, altrimenti passa al successivo.
Così facendo i tempi diminuiscono sensibilmente rispetto a caricare tutto ogni volta, nell'ordine di decine di volte, tanto più quanto grande è la tabella.
Una nota di contorno: se sulla tabella c'è un indice sul campo DATA, le due SELECT del codice di esempio non ne fanno uso poiché usano una trasformazione del campo stesso, costringendo il DBMS a leggere tutti i valori, scorrendo tutta la tabella.
Per sfruttare al massimo gli indici, si può sostituire la prima SELECT (che prende tutti i valori distinti di anno-mese con la seguente, che ritornerà tutti i valori pressoché istantaneamente (strettamente legata alla sintassi Oracle):
SELECT TO_CHAR(START_DAY, 'YYYYMM') AAAAMM
FROM TABLE(CALENDAR.MONTHS((SELECT MIN(DATA) FROM TABELLA),
(SELECT MAX(DATA) FROM TABELLA)));
La clausola WHERE della seconda, invece, si può sostituire con la seguente:
WHERE DATA BETWEEN TO_DATE('$(mese)', 'YYYYMM') AND
TO_DATE(TO_CHAR(LAST_DAY(TO_DATE('$(mese)', 'YYYYMM')),
'YYYYMMDD') || '235959', 'YYYYMMDDHH24MISS')
Leave a comment