Non è bello far paragoni, ma…

…a volte è utile e necessario.

Quali paragoni? Confrontare il contenuto di due librerie, di due cartelle IFS, di due tabelle o di due stream files.

Esistono vari modi per confrontare. Ci concentreremo in particolare sui metodi che sfruttano istruzioni SQL, ma non solo.

Vediamo in questo articolo come confrontare:


Confrontare due tabelle

Tabelle di esempio:

Tabella BANCOMAT libreria MR731
Tabella BANCOMAT libreria MR732

Metodo 1: funzione HASH_ROW

Nel DB2 esistono alcune funzioni per calcolare l’hash con diversi algoritmi: HASH_MD5, HASH_SHA1, HASH_SHA256, and HASH_SHA512. Dalla versione IBM i 7.4 TR2 è stata introdotta la funzione HASH_ROW, che restituisce l’hash calcolato con algoritmo SHA512 di un record di una tabella. Per identificare quali siano i record diversi a parità di chiave primaria tra due tabelle con identico tracciato record ma potenzialmente contenuto differente, è sufficiente calcolare l’hash di ogni record e poi confrontarlo.

Nell’esempio la tabella BANCOMAT ha definito come chiave primaria il campo BANCOD che viene utilizzato nella clausola di join per abbinare i record corrispondenti della tabella 1 con la tabella 2. In questo modo anche se i record nelle due tabelle sono in posizioni RRN differenti, il risultato del confronto non è inficiato.

with
 TAB1 as
 (select BANCOD as ROW_KEY, hash_row(T) as ROW_VALUE, rid(T) as RRN
    from MR731.BANCOMAT as T),
 TAB2 as
 (select BANCOD as ROW_KEY, hash_row(T) ROW_VALUE, rid(T) as RRN
    from MR732.BANCOMAT T)
 select coalesce(T1.ROW_KEY, T2.ROW_KEY) as ROW_KEY, T1.RRN as T1_RRN, T2.RRN as T2_RRN
   from TAB1 as T1
     full outer join TAB2 as T2
       on T1.ROW_KEY = T2.ROW_KEY
   where T1.ROW_VALUE is distinct from T2.ROW_VALUE;

Risultato:

Metodo 2: funzione COMPARE_FILE

Da IBM i 7.4 TR2 e 7.3 TR8 è disponibile la funzione di tabella COMPARE_FILE specifica per eseguire la comparazione di due tabelle. Non solo per il contenuto ma anche per gli attributi di definizione.

I parametri COMPARE_ATTRIBUTES e COMPARE_DATA consentono di scegliere il tipo di confronto che si desidera eseguire: attributi di definizione e/o contenuto dei record. In entrambi i parametri è possibile scegliere un confronto rapido 'QUICK' o dettagliato 'YES'.

Molto interessante il parametro RDB2 che consente di specificare per la seconda tabella il nome del db remoto.

Bisogna porre attenzione a due caratteristiche di questa funzione:

  • la corrispondenza dei record tra le due tabelle è basato su RRN, quindi anche se il contenuto di due file è identico ma i record hanno rrn differenti saranno considerati diversi
  • non è possibile confrontare un file fisico creato con le DDS e una tabella creata con SQL DDL

Vediamo alcuni esempi sempre utilizzando le tabelle di esempio BANCOMAT:

select *
  from table(COMPARE_FILE(LIBRARY1 => 'MR731', FILE1 => 'BANCOMAT',
                          LIBRARY2 => 'MR732', FILE2 => 'BANCOMAT',
                          COMPARE_ATTRIBUTES => 'QUICK',
                          COMPARE_DATA => 'QUICK'
                         )
            );

select *
  from table(COMPARE_FILE(LIBRARY1 => 'MR731', FILE1 => 'BANCOMAT',
                          LIBRARY2 => 'MR732', FILE2 => 'BANCOMAT',
                          COMPARE_ATTRIBUTES => 'YES',
                          COMPARE_DATA => 'NO'
                         )
            );

Nota: la libreria MR731 è stata creata con istruzione SQL CREATE SCHEMA e quindi le tabelle sono per default registrate su giornale, invece la libreria MR732 è stata creata con CRTLIB e per la tabella BANCOMAT non sono state eseguite le istruzioni di creazione della chiave primaria e dei vincoli.

select *
  from table(COMPARE_FILE(LIBRARY1 => 'MR731', FILE1 => 'BANCOMAT',
                          LIBRARY2 => 'MR732', FILE2 => 'BANCOMAT',
                          COMPARE_ATTRIBUTES => 'NO',
                          COMPARE_DATA => 'YES'
                         )
            )
  order by FILE1;

Per utilizzare questa funzione senza scrivere l’istruzione SQL è possibile sfruttare la gestione schemi di ACS.

ACS COMPARE_FILE – scelta della prima tabella

Dalla lista delle tabelle aprire il menu contestuale selezionando la prima tabella che si desidera confrontare. Quindi visualizzare la lista in cui è presente la seconda tabella e dal menu contestuale scegliere la voce “Confronta con…”

ACS COMPARE_FILE – scelta della seconda tabella

A questo punto scegliere le opzioni di confronto che corrispondono ai parametri COMPARE_ATTRIBUTES e COMPARE_DATA della funzione SQL COMPARE_FILE.

ACS COMPARE_FILE – scelta delle opzioni di confronto

COMPARE_FILE per confrontare file sorgenti

I file sorgenti sono tabelle “particolari”. La funzione COMPARE_FILE consente anche di confrontare due file sorgenti (PF-SRC).

Prendiamo ad esempio i due file sorgenti QTOOLS nelle librerie MR731 e MR732

File sorgente QTOOLS in libreria MR731
File sorgente QTOOLS in libreria MR732

E proviamo a confrontarli

select *
  from table(COMPARE_FILE(LIBRARY1 => 'MR731', FILE1 => 'QTOOLS',
                          LIBRARY2 => 'MR732', FILE2 => 'QTOOLS',
                          COMPARE_ATTRIBUTES => 'YES',
                          COMPARE_DATA => 'NO'
                         )
            );

Metodo 3: operatori insiemistici

Un altro metodo per scrivere un’istruzione SQL che confronti il contenuti di due tabelle è utilizzare gli operatori insiemistici EXCEPT e UNION.

select 'MR731 vs MR732' as "Confronto", T1.*
  from MR731.BANCOMAT as T1
 except
select 'MR731 vs MR732' as "Confronto", T2.*
  from MR732.BANCOMAT as T2
 union all
select 'MR732 vs MR731' as "Confronto", T2.*
  from MR732.BANCOMAT as T2
 except
select 'MR732 vs MR731' as "Confronto", T1.*
  from MR731.BANCOMAT as T1;

Metodo 4: procedura GENERATE_FILE_COMPARE

Vi suggerisco di consultare questo gist di Scott Forstie.

Nel gist trovate le istruzioni SQL per creare la procedura GENERATE_FILE_COMPARE. Una volta creata la procedura la potete eseguire per ottenere un’istruzione SQL di confronto del contenuto di due tabelle.

Sempre utilizzando come esempio le due tabelle BANCOMAT possiamo eseguire la procedura con questa istruzione

values GENERATE_FILE_COMPARE(
         SOURCE_SCHEMA => 'MR731',
         SOURCE_TABLE  => 'BANCOMAT',
         TARGET_SCHEMA => 'MR732',
         TARGET_TABLE  => 'BANCOMAT');

Il risultato sarà questa istruzione

select 'MR731.BANCOMAT' as table, 
       rrn(a) as rrn, a.* from MR731.BANCOMAT a
  left exception join MR732.BANCOMAT b
    on A.BANCOD IS NOT DISTINCT FROM B.BANCOD AND A.BANDESC IS NOT DISTINCT FROM B.BANDESC AND A.BANADDR IS NOT DISTINCT FROM B.BANADDR AND A.BANCITY IS NOT DISTINCT FROM B.BANCITY AND A.BANPROV IS NOT DISTINCT FROM B.BANPROV AND A.BANZIP IS NOT DISTINCT FROM B.BANZIP AND A.BANLOC IS NOT DISTINCT FROM B.BANLOC
 UNION ALL
select 'MR732.BANCOMAT', 
       rrn(b), b.* from MR731.BANCOMAT a
  right exception join  MR732.BANCOMAT b
    on A.BANCOD IS NOT DISTINCT FROM B.BANCOD AND A.BANDESC IS NOT DISTINCT FROM B.BANDESC AND A.BANADDR IS NOT DISTINCT FROM B.BANADDR AND A.BANCITY IS NOT DISTINCT FROM B.BANCITY AND A.BANPROV IS NOT DISTINCT FROM B.BANPROV AND A.BANZIP IS NOT DISTINCT FROM B.BANZIP AND A.BANLOC IS NOT DISTINCT FROM B.BANLOC
  order by 2;

Eseguendola otterremo come risultato il confronto delle tue tabelle BANCOMAT

Confrontare due librerie

Metodo 1: funzione OBJECT_STATISTICS

Sfruttando la funzione di tabella OBJECT_STATISTICS possiamo costruire delle istruzioni SQL per confrontare il contenuto di due librerie, eventualmente presenti anche su sistemi remoti.

Queste istruzioni sono presenti nello script Query di tutti i giorni.

-- [B06a] confronto lista oggetti tra due librerie
with
 LIB1 as
 (select 'L1' as PROV, coalesce(OBJLONGNAME, OBJNAME) as OBJNAME, OBJTYPE, OBJATTRIBUTE, OBJTEXT, CHANGE_TIMESTAMP
    from table(QSYS2.OBJECT_STATISTICS(OBJECT_SCHEMA => :Libreria1, OBJTYPELIST => '*ALL')) as L
    --where 
 ),
 LIB2 as
 (select 'L2' as PROV, coalesce(OBJLONGNAME, OBJNAME) as OBJNAME, OBJTYPE, OBJATTRIBUTE, OBJTEXT, CHANGE_TIMESTAMP
    from table(QSYS2.OBJECT_STATISTICS(OBJECT_SCHEMA => :Libreria2, OBJTYPELIST => '*ALL')) as L
    --where 
 )
 select 
        coalesce(LIB1.OBJNAME, LIB2.OBJNAME) as "Oggetto", coalesce(LIB1.OBJTYPE, LIB2.OBJTYPE) as "Tipo",
        coalesce(LIB1.OBJATTRIBUTE, LIB2.OBJATTRIBUTE) as "Attr.",
        LIB1.PROV, LIB1.OBJTEXT "Lib1 descrizione", timestamp(LIB1.CHANGE_TIMESTAMP, 0) as "Lib1 Data/ora mod.", 
        LIB2.PROV, LIB2.OBJTEXT "Lib2 descrizione", timestamp(LIB2.CHANGE_TIMESTAMP, 0) as "Lib2 Data/ora mod."
   from LIB1
     -- oggetti esistenti in entrambe le librerie
     --inner join LIB2
     -- oggetti mancanti in Libreria1 rispetto a Libreria2
     --right join LIB2
     -- oggetti mancanti in Libreria2 rispetto a Libreria1
     --exception join LIB2
     -- confronto completo
     full outer join LIB2
       on LIB1.OBJNAME = LIB2.OBJNAME and LIB1.OBJTYPE = LIB2.OBJTYPE
     -- oggetti mancanti in Libreria1 rispetto a Libreria2
     --where LIB1.OBJNAME is null
   order by 1, 2;

Questa istruzione SQL sfrutta la sintassi delle CTE (Common Table Expression) per creare due risultati temporanei LIB1 e LIB2 che contengono la lista degli oggetti della libreria 1 e della libreria 2 che si desidera confrontare. Se si desidera restringere il confronto solo ad un sottoinsieme di oggetti è possibile modificare i parametri della funzione di tabella OBJECT_STATISTICS o la clausola where delle sottoquery che definiscono LIB1/LIB2.
Una volta che si ha a disposizione  l’elenco restituito da LIB1 e LIB2 si può operare il confronto con diverse tipologie di clausole join in base al risultato che si desidera ottenere:

  • lista oggetti presenti in entrambe le librerie: clausola di inner join eventualmente specificando nella clausola where regole di confronto su uno o più attributi degli oggetti (data creazione, dimensione, ecc.)
  • lista oggetti mancanti nella libreria 1 rispetto alla libreria 2: clausola right join unitamente alla clausola where per filtrare solo i record con LIB1.OBJNAME nullo
  • lista oggetti mancanti nella libreria 2 rispetto alla libreria 1: clausola exception join
  • confronto completo tra libreria 1 e libreria 2: clausola full outer join

Con un’istruzione simile è possibile confrontare il contenuto di due librerie presenti su sistemi diversi. In questo caso occorre creare su uno dei due sistemi (lo chiameremo locale) una tabella temporanea con il risultato della funzione di tabella OBJECT_STATISTICS eseguita sul sistema remoto.

-- [B06b] confronto lista oggetti tra due librerie su sistemi remoti
  -- step 1) creare in locale una tabella temporanea con la lista oggetti della libreria remota
          -- se utente/password locali non coincidono con utente/password remoti occorre...
          -- ...o connettersi esplicitamente al sistema remoto
          --connect to OTHERSYS user REMUSER using 'REMPWD';
          -- ...o aggiungere una voce di autorizzazione server per il nome server DRDA RDB NAME (N.B. il valore di sistema QRETSVRSEC deve essere uguale a 1)
          -- ADDSVRAUTE USRPRF(LOCUSER) SERVER(OTHERSYS) USRID(REMUSER) PASSWORD(REMPWD)
create table QTEMP.LIB_REM as
 (select 'LR' as PROV, coalesce(OBJLONGNAME, OBJNAME) as OBJNAME, OBJTYPE, OBJATTRIBUTE, OBJTEXT, CHANGE_TIMESTAMP
    from remote table(OTHERSYS.QSYS2.OBJECT_STATISTICS(OBJECT_SCHEMA => :LibreriaRem, OBJTYPELIST => '*ALL')) as L
    --where
 ) with data;

A questo punto si può costruire una query di confronto del tutto simile all’esempio precedente

  -- step 2 confronto libreria locale con la lista ottenuta dal sistema remoto 
with
 LIBC as
 (select 'LC' as PROV, coalesce(OBJLONGNAME, OBJNAME) as OBJNAME, OBJTYPE, OBJATTRIBUTE, OBJTEXT, CHANGE_TIMESTAMP
    from table(QSYS2.OBJECT_STATISTICS(OBJECT_SCHEMA => :LibreriaLoc, OBJTYPELIST => '*ALL')) as L
    --where 
 ),
 LIBR as
 (select *
    from QTEMP.LIB_REM
    --where 
 )
 select 
        coalesce(LIBC.OBJNAME, LIBR.OBJNAME) as "Oggetto", coalesce(LIBC.OBJTYPE, LIBR.OBJTYPE) as "Tipo",
        coalesce(LIBC.OBJATTRIBUTE, LIBR.OBJATTRIBUTE) as "Attr.",
        LIBC.PROV, LIBC.OBJTEXT "Lib.loc. descrizione", timestamp(LIBC.CHANGE_TIMESTAMP, 0) as "Lib.loc. Data/ora mod.", 
        LIBR.PROV, LIBR.OBJTEXT "Lib.rem. descrizione", timestamp(LIBR.CHANGE_TIMESTAMP, 0) as "Lib.rem. Data/ora mod."  
   from LIBC
     -- oggetti esistenti in entrambe le librerie
     --inner join LIBR
     -- oggetti mancanti in Libreria1 rispetto a Libreria2
     --right join LIBR
     -- oggetti mancanti in Libreria2 rispetto a Libreria1
     --exception join LIBR
     -- confronto completo
     full outer join LIBR
       on LIBC.OBJNAME = LIBR.OBJNAME and LIBC.OBJTYPE = LIBR.OBJTYPE
     -- oggetti mancanti in Libreria1 rispetto a Libreria2
     --where LIBC.OBJNAME is null
   order by 1, 2;

Metodo 2: vista SYSTABLES del catalogo DB2

Nel caso in cui il confronto tra le due librerie è limitato ai soli file fisici/logici si può evitare l’uso della funzione OBJECT_STATISTICS e consultare direttamente la vista SYSTABLES del catalogo DB2.

Queste istruzioni sono presenti nello script Query di tutti i giorni.

Confronto rapido basato sul conteggio degli oggetti presenti nelle due librerie:

-- [F14] confronto conteggio oggetti database tra due librerie
with
 LIB1 as
 (select DESCRIPTION, count(*) as NUM
    from SYSTABLES
      inner join (values ('A', 'ALIAS'), ('L', 'LOGICAL FILE'), ('M', 'MQT'), ('P', 'TABLE'), ('T', 'TABLE'), ('V', 'VIEW'))
                        as TYPE(CODE, DESCRIPTION)
        on TABLE_TYPE = TYPE.CODE
    where FILE_TYPE = 'D'
      and SYSTEM_TABLE_SCHEMA = :Libreria1
    group by DESCRIPTION with rollup),
 LIB2 as
 (select DESCRIPTION, count(*) as NUM
    from SYSTABLES
      inner join (values ('A', 'ALIAS'), ('L', 'LOGICAL FILE'), ('M', 'MQT'), ('P', 'TABLE'), ('T', 'TABLE'), ('V', 'VIEW'))
                        as TYPE(CODE, DESCRIPTION)
        on TABLE_TYPE = TYPE.CODE
    where FILE_TYPE = 'D'
      and SYSTEM_TABLE_SCHEMA = :Libreria2
    group by DESCRIPTION with rollup)    
 select coalesce(L1.DESCRIPTION, L2.DESCRIPTION) as "Tipo oggetto", 
        coalesce(L1.NUM, 0) "Conteggio per lib 1",
        coalesce(L2.NUM, 0) "Conteggio per lib 2"
   from LIB1 as L1
     full outer join LIB2 as L2
       on coalesce(L1.DESCRIPTION, 'TOTALE') = coalesce(L2.DESCRIPTION, 'TOTALE')
   order by 1;

Confronto dettagliato della lista oggetti di database delle due librerie:

-- [F15] confronto lista oggetti database mancanti tra due librerie
with
 LIB1 as
 (select SYSTEM_TABLE_NAME, TABLE_NAME, TABLE_TYPE, TABLE_TEXT, LAST_ALTERED_TIMESTAMP
    from SYSTABLES
    where FILE_TYPE = 'D'
      and SYSTEM_TABLE_SCHEMA = :Libreria1
 ),
 LIB2 as
 (select SYSTEM_TABLE_NAME, TABLE_NAME, TABLE_TYPE, TABLE_TEXT, LAST_ALTERED_TIMESTAMP
    from SYSTABLES
    where FILE_TYPE = 'D'
      and SYSTEM_TABLE_SCHEMA = :Libreria2
 )
 select coalesce(LIB1.SYSTEM_TABLE_NAME, LIB2.SYSTEM_TABLE_NAME), coalesce(LIB1.TABLE_NAME, LIB2.TABLE_NAME) 
   from LIB1
     -- oggetti mancanti in Libreria1 rispetto a Libreria2
     --right join LIB2
     -- oggetti mancanti in Libreria2 rispetto a Libreria1
     exception join LIB2
       on LIB1.SYSTEM_TABLE_NAME = LIB2.SYSTEM_TABLE_NAME
   -- oggetti mancanti in Libreria1 rispetto a Libreria2
   --where LIB1.SYSTEM_TABLE_NAME is null
   order by 1;

Confrontare due stream files

Stream file di esempio: aeroporti_certificati.csv in cartella /home/MR73/FLR1 e /home/MR73/FLR2

Metodo 1: funzione hash*

Anche per gli stream file è possibile adottare una logica di confronto basato sul calcolo dell’hash. Così come per le tabelle abbiamo utilizzato la funzione hash_row per calcolare l’hash del record, in modo simile possiamo utilizzare una delle funzioni hash (HASH_MD5, HASH_SHA1, HASH_SHA256, and HASH_SHA512) per calcolare l’hash dell’intero stream file. Lo stream file viene letto da IFS tramite la funzione GET_CLOB_FROM_FILE e passato come parametro alla funzione hash*.

select hash_md5(get_clob_from_file('/home/MR73/FLR1/aeroporti_certificati.csv')) as F1,
       hash_md5(get_clob_from_file('/home/MR73/FLR2/aeroporti_certificati.csv')) as F2
  from SYSIBM/SYSDUMMY1;

Comparazione membri sorgente

La funzione GET_CLOB_FROM_FILE consente di leggere non solo stream file ma anche membri di file sorgenti, quindi è possibile calcolare l’hash di due membri sorgente per effettuare un confronto rapido.

select hash_md5(get_clob_from_file('MR731/QTOOLS(GENCMDXML)')) as M1,
       hash_md5(get_clob_from_file('MR732/QTOOLS(GENCMDXML)')) as M2
  from SYSIBM/SYSDUMMY1;

Calcolo hash con md5sum

L’hash di uno stream file si può calcolare anche tramite il comando PASE md5sum.

Per esempio:

Metodo 2: funzione COMPARE_IFS

Dalla versione IBM i 7.5 TR4 e 7.4 TR10 è disponibile la funzione COMPARE_IFS che consente di identificare gli attributi differenti tra due stream files.

Questa funzione consente di confrontare due stream files specifici oppure il contenuto di due cartelle di IFS. La funzione può lavorare solo sugli oggetti contenuti nel file system root (/) e QOpenSys.

Molto interessante il parametro RDB2 che consente di specificare per il secondo file il nome del db remoto.

Il parametro COMPARE_ATTRIBUTES consente di scegliere a quale livello di dettaglio di desidera conoscere le differenze:

  • 'NAMES': viene restituito la lista dei nomi file non presenti in entrambe le cartelle. Valido solo se gli argomenti 1 e 2 della funzione sono due percorsi e non due stream files specifici
  • 'YES': viene restituito il dettaglio delle differenze tra i vari attributi

Per esempio:

select *
  from table(COMPARE_IFS(START_PATH_NAME1 => '/home/MR73/FLR1/aeroporti_certificati.csv',
                         START_PATH_NAME2 => '/home/MR73/FLR2/aeroporti_certificati.csv',
                         COMPARE_ATTRIBUTES => 'YES' 
                        )
            );

Metodo 3: comandi PASE

Un metodo alternativo per confrontare due stream file si basa sull’uso dei comandi PASE bdiff, diff, sdiff o cmp.

bdiff e diff sono simili: bdiff viene usato per confrontare file di grandi dimensioni
sdiff esegue un confronto side-by-side
cmp restituisce un’informazione sintetica sul confronto dei due file

Vediamo qualche esempio

diff -uw /home/MR73/FLR1/aeroporti_certificati.csv /home/MR73/FLR2/aeroporti_certificati.csv

--- /home/MR73/FLR1/aeroporti_ce9rtificati.csv	2025-08-10 22:57:40.000000000 +0000
+++ /home/MR73/FLR2/aeroporti_certificati.csv	2025-08-10 23:07:14.000000000 +0000
@@ -1,4 +1,7 @@
 BGY	BERGAMO ORIO AL SERIO	Via Aeroporto, 13, 24050 Orio al Serio (BG)	ORIO AL SERIO			45.668889	9.700278
-VBS	BRESCIA MONTICHIARI	Via Aeroporto, 34, 25018 Montichiari (BS)	MONTICHIARI			45.425556	10.326944
-LIN	MILANO LINATE	Viale Enrico Forlanini, 20054 Segrate (MI)	SEGRATE			45.449444	9.278333
-MXP	MILANO MALPENSA	Aeroporto Milano Malpensa - 21010 Ferno (VA)	FERNO			45.63	8.723056
+CIA	ROMA CIAMPINO	Via Appia Nuova, 1651, 00040 Ciampino (RM)	CIAMPINO			41.799444	12.597222
+EBA	MARINA DI CAMPO	Via dell'Aeroporto LocalitÇÿ La Pila, 208, 57034 Marina di Campo (LI)	CAMPO NELL'ELBA			42.762778	10.240556
+FCO	ROMA FIUMICINO	Via dell'Aeroporto di Fiumicino 320 - 00054 Fiumicino (RM)	FIUMICINO			41.800278	12.238889
+LIN	MILANO LINATE	Viale Enrico Forlanini, 20090 (MI)	PESCHIERA BORROMEO			45.449444	9.278333
+LMP	LAMPEDUSA	Contrada Cala Francese, 92010 Lampedusa E Linosa (AG)	LAMPEDUSA			35.495833	12.611111
+MXP	MILANO MALPENSA	Aeroporto Berlusconi - 21010 Ferno (VA)	FERNO			45.63	8.723056
diff -C 0 -tw /home/MR73/FLR1/aeroporti_certificati.csv /home/MR73/FLR2/aeroporti_certificati.csv

*** /home/MR73/FLR1/aeroporti_certificati.csv	Sun Aug 10 22:57:40 2025
--- /home/MR73/FLR2/aeroporti_certificati.csv	Sun Aug 10 23:07:14 2025
***************
*** 2,4 ****
! VBS     BRESCIA MONTICHIARI     Via Aeroporto, 34, 25018 Montichiari (BS)       MONTICHIARI                     45.425556       10.326944
! LIN     MILANO LINATE   Viale Enrico Forlanini, 20054 Segrate (MI)      SEGRATE                 45.449444       9.278333
! MXP     MILANO MALPENSA Aeroporto Milano Malpensa - 21010 Ferno (VA)    FERNO                   45.63   8.723056
--- 2,7 ----
! CIA     ROMA CIAMPINO   Via Appia Nuova, 1651, 00040 Ciampino (RM)      CIAMPINO                        41.799444       12.597222
! EBA     MARINA DI CAMPO Via dell'Aeroporto LocalitÇÿ La Pila, 208, 57034 Marina di Campo (LI) CAMPO NELL'ELBA                 42.762778       10.240556
! FCO     ROMA FIUMICINO  Via dell'Aeroporto di Fiumicino 320 - 00054 Fiumicino (RM)      FIUMICINO                       41.800278       12.238889
! LIN     MILANO LINATE   Viale Enrico Forlanini, 20090 (MI)      PESCHIERA BORROMEO                      45.449444       9.278333
! LMP     LAMPEDUSA       Contrada Cala Francese, 92010 Lampedusa E Linosa (AG)   LAMPEDUSA                       35.495833       12.611111
! MXP     MILANO MALPENSA Aeroporto Berlusconi - 21010 Ferno (VA) FERNO                   45.63   8.723056
diff -U 0 -tw /home/MR73/FLR1/aeroporti_certificati.csv /home/MR73/FLR2/aeroporti_certificati.csv

--- /home/MR73/FLR1/aeroporti_certificati.csv	2025-08-10 22:57:40.000000000 +0000
+++ /home/MR73/FLR2/aeroporti_certificati.csv	2025-08-10 23:07:14.000000000 +0000
@@ -2,3 +2,6 @@
-VBS     BRESCIA MONTICHIARI     Via Aeroporto, 34, 25018 Montichiari (BS)       MONTICHIARI                     45.425556       10.326944
-LIN     MILANO LINATE   Viale Enrico Forlanini, 20054 Segrate (MI)      SEGRATE                 45.449444       9.278333
-MXP     MILANO MALPENSA Aeroporto Milano Malpensa - 21010 Ferno (VA)    FERNO                   45.63   8.723056
+CIA     ROMA CIAMPINO   Via Appia Nuova, 1651, 00040 Ciampino (RM)      CIAMPINO                        41.799444       12.597222
+EBA     MARINA DI CAMPO Via dell'Aeroporto LocalitÇÿ La Pila, 208, 57034 Marina di Campo (LI) CAMPO NELL'ELBA                 42.762778       10.240556
+FCO     ROMA FIUMICINO  Via dell'Aeroporto di Fiumicino 320 - 00054 Fiumicino (RM)      FIUMICINO                       41.800278       12.238889
+LIN     MILANO LINATE   Viale Enrico Forlanini, 20090 (MI)      PESCHIERA BORROMEO                      45.449444       9.278333
+LMP     LAMPEDUSA       Contrada Cala Francese, 92010 Lampedusa E Linosa (AG)   LAMPEDUSA                       35.495833       12.611111
+MXP     MILANO MALPENSA Aeroporto Berlusconi - 21010 Ferno (VA) FERNO                   45.63   8.723056
sdiff /home/MR73/FLR1/aeroporti_certificati.csv /home/MR73/FLR2/aeroporti_certificati.csv

BGY	BERGAMO ORIO AL SERIO	Via Aeroporto, 13, 24050 Orio      BGY	BERGAMO ORIO AL SERIO	Via Aeroporto, 13, 24050 Orio 
VBS	BRESCIA MONTICHIARI	Via Aeroporto, 34, 25018 Monti  |  CIA	ROMA CIAMPINO	Via Appia Nuova, 1651, 00040 Ciampino 
LIN	MILANO LINATE	Viale Enrico Forlanini, 20054 Segrate   |  EBA	MARINA DI CAMPO	Via dell'Aeroporto LocalitÇÿ La Pila
MXP	MILANO MALPENSA	Aeroporto Milano Malpensa - 21010 Fern  |  FCO	ROMA FIUMICINO	Via dell'Aeroporto di Fiumicino 320 - 
                                                                >  LIN	MILANO LINATE	Viale Enrico Forlanini, 20090 (MI)
                                                                >  LMP	LAMPEDUSA	Contrada Cala Francese, 92010 Lampedus
                                                                >  MXP	MILANO MALPENSA	Aeroporto Berlusconi - 21010 Ferno (VA
cmp /home/MR73/FLR1/aeroporti_certificati.csv /home/MR73/FLR2/aeroporti_certificati.csv

/home/MR73/FLR1/aeroporti_certificati.csv /home/MR73/FLR2/aeroporti_certificati.csv differ: char 107, line 2

N.B. se non ci sono differenze nei due file il comando cmp non restituisce nulla in output

Per l’utilizzo del comando cmp integrato in una funzione RPG suggerisco la lettura dell’articolo (con allegato esempio RPG) TechTip: Using Qshell from RPG to Compare Two Files di Aaron Bartell.

Confrontare due cartelle di IFS

Metodo 1: funzione IFS_OBJECT_STATISTICS

Utilizzando la funzione IFS_OBJECT_STATISTICS (gemella di OBJECT_STATISTICS) è possibile costruire una query di confronto del contenuto di due cartelle di IFS.

Per esempio con la seguente query è possibile ottenere l’elenco del contenuto della cartella /home/MR73/FLR1 e /home/MR73/FLR2 e trovare eventuali oggetti mancanti in una cartella rispetto all’altra ed anche gli oggetti presenti in entrambe ma con alcune proprietà non congruenti (nell’esempio si confronta data/ora di creazione, data/ora di modifica, dimensione e CCSID)

select SYSTOOLS.IFS_PATH(PATH_NAME => F1.PATH_NAME , SUBSECTION => 'FILE NAME') as "F1 file",
       F1.CREATE_TIMESTAMP as "Data/ora creaz.", F1.DATA_CHANGE_TIMESTAMP as "Data/ora mod.", F1.DATA_SIZE as "Dim.", F1.CCSID as "CCSID",
       SYSTOOLS.IFS_PATH(PATH_NAME => F2.PATH_NAME , SUBSECTION => 'FILE NAME') as "F2 file",
       F2.CREATE_TIMESTAMP as "Data/ora creaz.", F2.DATA_CHANGE_TIMESTAMP as "Data/ora mod.", F2.DATA_SIZE as "Dim.", F2.CCSID as "CCSID" 
  from table(IFS_OBJECT_STATISTICS(START_PATH_NAME => '/home/MR73/FLR1', OBJECT_TYPE_LIST => '*ALLSTMF', IGNORE_ERRORS => 'YES')) as F1
    full outer join table(IFS_OBJECT_STATISTICS(START_PATH_NAME => '/home/MR73/FLR2', OBJECT_TYPE_LIST => '*ALLSTMF', IGNORE_ERRORS => 'YES')) as F2
      on SYSTOOLS.IFS_PATH(PATH_NAME => F1.PATH_NAME , SUBSECTION => 'FILE NAME') =
         SYSTOOLS.IFS_PATH(PATH_NAME => F2.PATH_NAME , SUBSECTION => 'FILE NAME')
  -- specificare gli attributi da confrontare
  where F1.CREATE_TIMESTAMP is distinct from F2.CREATE_TIMESTAMP            -- data/ora creazioe
     or F1.DATA_CHANGE_TIMESTAMP is distinct from F2.DATA_CHANGE_TIMESTAMP  -- data/ora modifica
     or F1.DATA_SIZE is distinct from F2.DATA_SIZE                          -- dimensione
     or F1.CCSID is distinct from F2.CCSID                                  -- CCSID
  order by case when F1.PATH_NAME is not null then lower(SYSTOOLS.IFS_PATH(PATH_NAME => F1.PATH_NAME , SUBSECTION => 'FILE NAME'))
                                              else lower(SYSTOOLS.IFS_PATH(PATH_NAME => F2.PATH_NAME , SUBSECTION => 'FILE NAME')) end;

Metodo 2: funzione COMPARE_IFS

Sempre con la funzione SQL COMPARE_IFS è possibile confrontare non solo due stream file ma due intere cartelle ed eventualmente anche le sottocartelle (parametro SUBTREE_DIRECTORIES).

Confronto specificando il parametro COMPARE_ATTRIBUTES uguale a ‘NAMES’

select *
  from table(COMPARE_IFS(START_PATH_NAME1 => '/home/MR73/FLR1',
                         START_PATH_NAME2 => '/home/MR73/FLR2',
                         COMPARE_ATTRIBUTES => 'NAMES' 
                        )
            );

Per un confronto più dettagliato si può specificare il parametro COMPARE_ATTRIBUTES uguale a ‘YES’

Metodo 3: comandi PASE

Il medesimo comando diff utilizzato per confrontare due stream file può anche essere utilizzato per confrontare il contenuto di tutti i file contenuti in due cartelle di IFS.
Per i file presenti in entrambe le cartelle viene eseguito un comando diff per confrontare il contenuto dei due file. Per i file presenti solo in una delle due cartelle viene evidenziato “Only in…” in quale cartella si trova.

diff /home/MR73/FLR1 /home/MR73/FLR2

Only in /home/MR73/FLR1: aeroporti_certificati.bak
diff /home/MR73/FLR1/aeroporti_certificati.csv /home/MR73/FLR2/aeroporti_certificati.csv
2,4c2,7
< VBS	BRESCIA MONTICHIARI	Via Aeroporto, 34, 25018 Montichiari (BS)	MONTICHIARI			45.425556	10.326944
< LIN	MILANO LINATE	Viale Enrico Forlanini, 20054 Segrate (MI)	SEGRATE			45.449444	9.278333
< MXP	MILANO MALPENSA	Aeroporto Milano Malpensa - 21010 Ferno (VA)	FERNO			45.63	8.723056
---
> CIA	ROMA CIAMPINO	Via Appia Nuova, 1651, 00040 Ciampino (RM)	CIAMPINO			41.799444	12.597222
> EBA	MARINA DI CAMPO	Via dell'Aeroporto LocalitÇÿ La Pila, 208, 57034 Marina di Campo (LI)	CAMPO NELL'ELBA			42.762778	10.240556
> FCO	ROMA FIUMICINO	Via dell'Aeroporto di Fiumicino 320 - 00054 Fiumicino (RM)	FIUMICINO			41.800278	12.238889
> LIN	MILANO LINATE	Viale Enrico Forlanini, 20090 (MI)	PESCHIERA BORROMEO			45.449444	9.278333
> LMP	LAMPEDUSA	Contrada Cala Francese, 92010 Lampedusa E Linosa (AG)	LAMPEDUSA			35.495833	12.611111
> MXP	MILANO MALPENSA	Aeroporto Berlusconi - 21010 Ferno (VA)	FERNO			45.63	8.723056
Only in /home/MR73/FLR2: aeroporti_certificati_v2.bak
Only in /home/MR73/FLR1: esempio.txt
Only in /home/MR73/FLR2: response.json

Per confrontare le due cartelle è possibile utilizzare anche il comando dircmp. E’ equivalente al comando diff -r.
Per poter eseguire il comando dircmp occorre accedere in scrittura alla cartella /usr/tmp.

Lascia un commento

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