Last Updated on 11 Settembre 2025 by Roberto De Pedrini
Nell’ultimo mio ultimo articolo “Rimuovere i vecchi spool file per tenere sotto controllo il numero di lavori nel sistema” ho pubblicato il sorgente di un programma CLLE che rimuove gli spooled file selezionati tramite JOBNAME, USERNAME, DAYS (Retention days), FILENAME, USRDTA e OUTQ in uso sui sistemi che amministro. Due lettori mi hanno fatto notare che lo stesso scopo può essere ottenuto utilizzano la procedura IBM DELETE_OLD_SPOOLED_FILES ma solo dopo averla personalizzata.
In questo articolo vediamo come modificare questa procedura SQL di IBM contenuta nella libreria SYSTOOLS aggiungendo i filtri mancanti e cioè JOBNAME, FILENAME e USRDTA come suggerito da IBM stessa:
Il sorgente SQL degli strumenti di Db2 for i forniti all’interno di SYSTOOLS può essere estratto e utilizzato come modello per costruire funzioni di aiuto simili o per creare una versione personalizzata all’interno di uno schema specificato dall’utente.
Prima di tutto dobbiamo recuperare il sorgente della procedura DELETE_OLD_SPOOLED_FILES e lo facciamo utilizzando un’altra procedura IBM chiamata GENERATE_SQL che si trova nella libreria QSYS2.
La procedura GENERATE_SQL genera le istruzioni del linguaggio di definizione dei dati SQL necessarie per ricreare un oggetto del database. I risultati vengono restituiti nel membro del file di origine del database specificato, nel file del flusso di origine o come set di risultati.
Da Run SQL scripts di Access Client Solutions generiamo lo stream file DELETE_OLD_SPOOLED_FILES.sql nella dir /tmp in IFS con questo statement SQL:
CALL
QSYS2.GENERATE_SQL(
DATABASE_OBJECT_NAME => 'DELETE_OLD_SPOOLED_FILES',
DATABASE_OBJECT_LIBRARY_NAME => 'SYSTOOLS',
DATABASE_OBJECT_TYPE => 'PROCEDURE',
DATABASE_SOURCE_FILE_NAME => '*STMF',
SOURCE_STREAM_FILE => '/tmp/DELETE_OLD_SPOOLED_FILES.sql',
SOURCE_STREAM_FILE_CCSID => 1208,
CREATE_OR_REPLACE_OPTION => 1,
REPLACE_OPTION => '1'
)
;
Avendo estratto il sorgente su di un file con CCSID 1208 (o 1252) possiamo editarlo da PC con Run SQL Scripts di Access Client Solutions o, se la dir dove si trova il file è stata condivisa con NetServer, con un qualunque editor di testo Windows.
Modifichiamo il sorgente come evidenziato sotto facendo attenzione a modificare la libreria di destinazione in modo che la procedura venga creata in una libreria utente.
In breve, le modifiche da apportare sono le seguenti:
- modifica della libreria di destinazione da SYSTOOLS ad una vostra libreria utente;
- definizione dei nuovi parametri P_JOBNAME, P_SPOOLED_FILE_NAME e P_USER_DATA;
- dichiarazione della nuova variabile V_USER_DATA;
- modifica dello statement WHERE aggiungendo il vincolo sui nuovi parametri;
- inserimento della nuova variabile V_USER_DATA negli statement di FETCH;
- modifica del commento e del commento sui parametri.
Se preferite, potete anche personalizzare il nome per far evidenziare ulteriormente la differenza con la procedura ufficiale.
-- Generazione SQL
-- Versione: V7R4M0 190621
-- Generata su: 05/09/25 15:47:40
-- Database relazionale: TREBIRDB
-- Opzioni standard: Db2 for i
SET PATH "QSYS","QSYS2","SYSPROC","SYSIBMADM" ;
CREATE OR REPLACE PROCEDURE MYLIB.DELETE_OLD_SPOOLED_FILES (
IN DELETE_OLDER_THAN TIMESTAMP DEFAULT ( CURRENT_TIMESTAMP - 3 MONTHS ) ,
IN P_OUTPUT_QUEUE_LIBRARY_NAME VARCHAR(10) FOR SBCS DATA DEFAULT '*ALL' ,
IN P_OUTPUT_QUEUE_NAME VARCHAR(10) FOR SBCS DATA DEFAULT '*ALL' ,
IN P_USER_NAME VARCHAR(10) FOR SBCS DATA DEFAULT '*ALL' ,
IN P_JOB_NAME VARCHAR(10) FOR SBCS DATA DEFAULT '*ALL' ,
IN P_SPOOLED_FILE_NAME VARCHAR(10) FOR SBCS DATA DEFAULT '*ALL' ,
IN P_USER_DATA VARCHAR(10) FOR SBCS DATA DEFAULT '*ALL' ,
IN PREVIEW VARCHAR(3) FOR SBCS DATA DEFAULT 'NO' )
DYNAMIC RESULT SETS 1
LANGUAGE SQL
SPECIFIC MYLIB.DLTOLDSPL
NOT DETERMINISTIC
MODIFIES SQL DATA
CALLED ON NULL INPUT
SYSTEM_TIME SENSITIVE NO
SET OPTION ALWBLK = *ALLREAD ,
ALWCPYDTA = *OPTIMIZE ,
COMMIT = *NONE ,
DECRESULT = (31, 31, 00) ,
DFTRDBCOL = QSYS2 ,
DLYPRP = *NO ,
DYNDFTCOL = *NO ,
DYNUSRPRF = *USER ,
SRTSEQ = *HEX ,
USRPRF = *USER
BEGIN
-- Example: PROCEDURE SYSTOOLS.DELETE_OLD_SPOOLED_FILES
--
-- Disclaimer:
-- This example is being provided by IBM to allow IBM i users to understand how to use
-- SQL to manage spooled files.
--
-- While efforts were made to verify the completeness and accuracy of this sample procedure,
-- this sample is provided 'as is' without any warranty whatsoever and to the maximum extent permitted,
-- IBM disclaims all implied warranties.
--
-- Parameters:
-- 1) DELETE_OLDER_THAN - A timestamp indicating to delete spooled files that were created BEFORE this point
-- 5) PREVIEW - When set to YES, the list of spooled files that would be deleted are returned, but not deleted.
-- If the value is not YES, all identified spooled files are deleted
--
DECLARE NOT_FOUND CONDITION FOR '02000' ;
DECLARE AT_END INTEGER DEFAULT 0 ;
DECLARE V_SPOOLED_FILE_NAME VARCHAR ( 10 ) ;
DECLARE V_JOB_NAME VARCHAR ( 28 ) ;
DECLARE V_FILE_NUMBER BIGINT ;
DECLARE V_USER_NAME VARCHAR ( 10 ) ;
DECLARE V_USER_DATA VARCHAR ( 10 ) ;
DECLARE V_CMDSTMT VARCHAR ( 200 ) ;
DECLARE V_CRT_DATETIME TIMESTAMP ;
DECLARE V_CRTDATE CHAR ( 15 ) ;
DECLARE DATE_FMT CHAR ( 4 ) ;
--
-- Find spooled files to delete
--
DECLARE V_SPOOLED_FILE_STMT_TEXT VARCHAR ( 3000 ) DEFAULT
'SELECT SPOOLED_FILE_NAME, JOB_NAME, FILE_NUMBER, USER_NAME, CREATE_TIMESTAMP, USER_DATA
FROM QSYS2.OUTPUT_QUEUE_ENTRIES_BASIC
WHERE CREATE_TIMESTAMP < ?' ;
DECLARE SPOOLED_FILE_CURSOR CURSOR WITH RETURN TO CALLER FOR V_SPOOLED_FILE_STMT ;
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET AT_END = 1 ;
DECLARE CONTINUE HANDLER FOR NOT_FOUND SET AT_END = 1 ;
IF ( PREVIEW = 'YES' ) THEN
SET V_SPOOLED_FILE_STMT_TEXT =
'SELECT SPOOLED_FILE_NAME, JOB_NAME, FILE_NUMBER, USER_NAME,
SIZE, OUTPUT_QUEUE_NAME, OUTPUT_QUEUE_LIBRARY_NAME, CREATE_TIMESTAMP,
USER_DATA, STATUS, TOTAL_PAGES, COPIES, FORM_TYPE,
DEVICE_TYPE, OUTPUT_PRIORITY, SYSTEM
FROM QSYS2.OUTPUT_QUEUE_ENTRIES_BASIC
WHERE CREATE_TIMESTAMP < ?' ;
END IF ;
IF ( P_OUTPUT_QUEUE_LIBRARY_NAME <> '*ALL' ) THEN
SET V_SPOOLED_FILE_STMT_TEXT = V_SPOOLED_FILE_STMT_TEXT CONCAT
' AND OUTPUT_QUEUE_LIBRARY_NAME =''' CONCAT P_OUTPUT_QUEUE_LIBRARY_NAME CONCAT '''' ;
END IF ;
IF ( P_OUTPUT_QUEUE_NAME <> '*ALL' ) THEN
SET V_SPOOLED_FILE_STMT_TEXT = V_SPOOLED_FILE_STMT_TEXT CONCAT
' AND OUTPUT_QUEUE_NAME =''' CONCAT P_OUTPUT_QUEUE_NAME CONCAT '''' ;
END IF ;
IF ( P_USER_NAME <> '*ALL' ) THEN
SET V_SPOOLED_FILE_STMT_TEXT = V_SPOOLED_FILE_STMT_TEXT CONCAT
' AND USER_NAME =''' CONCAT P_USER_NAME CONCAT '''' ;
END IF ;
IF ( P_JOB_NAME <> '*ALL' ) THEN
SET V_SPOOLED_FILE_STMT_TEXT = V_SPOOLED_FILE_STMT_TEXT CONCAT
' AND REGEXP_SUBSTR(JOB_NAME, ''[^/]+$'') =''' CONCAT P_JOB_NAME CONCAT '''' ;
END IF ;
IF ( P_SPOOLED_FILE_NAME <> '*ALL' ) THEN
SET V_SPOOLED_FILE_STMT_TEXT = V_SPOOLED_FILE_STMT_TEXT CONCAT
' AND SPOOLED_FILE_NAME =''' CONCAT P_SPOOLED_FILE_NAME CONCAT '''' ;
END IF ;
IF ( P_USER_DATA <> '*ALL' ) THEN
SET V_SPOOLED_FILE_STMT_TEXT = V_SPOOLED_FILE_STMT_TEXT CONCAT
' AND USER_DATA =''' CONCAT P_USER_DATA CONCAT '''' ;
END IF ;
PREPARE V_SPOOLED_FILE_STMT FROM V_SPOOLED_FILE_STMT_TEXT ;
OPEN SPOOLED_FILE_CURSOR USING DELETE_OLDER_THAN ;
IF ( PREVIEW = 'YES' ) THEN
RETURN ;
END IF ;
SELECT DATE_FORMAT INTO DATE_FMT
FROM TABLE ( QSYS2 . ACTIVE_JOB_INFO ( JOB_NAME_FILTER => '*' , DETAILED_INFO => 'ALL' ) ) ;
FETCH FROM SPOOLED_FILE_CURSOR INTO V_SPOOLED_FILE_NAME , V_JOB_NAME , V_FILE_NUMBER , V_USER_NAME , V_CRT_DATETIME , V_USER_DATA ;
WHILE ( AT_END = 0 ) DO
--
-- Add 500 microseconds to keep the identical date and time value with spool file's.
--
SET V_CRT_DATETIME = V_CRT_DATETIME + 500 MICROSECONDS ;
--
-- Convert date and time ISO format to current job's date and time format
--
IF DATE_FMT = '*DMY' THEN
SET V_CRTDATE = VARCHAR_FORMAT ( V_CRT_DATETIME , 'DDMMYYYY HH24MISS' ) ;
ELSEIF DATE_FMT = '*JUL' THEN
SET V_CRTDATE = VARCHAR_FORMAT ( V_CRT_DATETIME , 'YYYYDDD HH24MISS' ) ;
ELSEIF DATE_FMT = '*MDY' THEN
SET V_CRTDATE = VARCHAR_FORMAT ( V_CRT_DATETIME , 'MMDDYYYY HH24MISS' ) ;
ELSEIF DATE_FMT = '*YMD' THEN
SET V_CRTDATE = VARCHAR_FORMAT ( V_CRT_DATETIME , 'YYYYMMDD HH24MISS' ) ;
END IF ;
SET V_CMDSTMT = 'QSYS/DLTSPLF FILE(' CONCAT V_SPOOLED_FILE_NAME CONCAT
') JOB(' CONCAT V_JOB_NAME CONCAT
') SPLNBR(' CONCAT V_FILE_NUMBER CONCAT
') CRTDATE(' CONCAT V_CRTDATE CONCAT
') JOBSYSNAME(*ANY) SELECT(' CONCAT V_USER_NAME CONCAT ')' ;
CALL QSYS2 . QCMDEXC ( V_CMDSTMT ) ;
FETCH FROM SPOOLED_FILE_CURSOR INTO V_SPOOLED_FILE_NAME , V_JOB_NAME , V_FILE_NUMBER , V_USER_NAME , V_CRT_DATETIME , V_USER_DATA ;
END WHILE ;
CLOSE SPOOLED_FILE_CURSOR ;
END ;
COMMENT ON SPECIFIC PROCEDURE MYLIB.DLTOLDSPL
IS 'USER VERSION CREATED FROM DB2 FOR IBM i SUPPLIED OBJECT VERSION 07400110003' ;
COMMENT ON PARAMETER SPECIFIC PROCEDURE MYLIB.DLTOLDSPL
( DELETE_OLDER_THAN IS 'timestamp - Default: CURRENT TIMESTAMP - 3 MONTHS' ,
P_OUTPUT_QUEUE_LIBRARY_NAME IS 'name, *ALL - Default: *ALL' ,
P_OUTPUT_QUEUE_NAME IS 'name, *ALL - Default: *ALL' ,
P_USER_NAME IS 'name, *ALL - Default: *ALL' ,
P_JOB_NAME IS 'name, *ALL - Default: *ALL' ,
P_SPOOLED_FILE_NAME IS 'name, *ALL - Default: *ALL' ,
P_USER_DATA IS 'name, *ALL - Default: *ALL' ,
PREVIEW IS 'NO, YES - Default: NO' ) ;
GRANT EXECUTE
ON SPECIFIC PROCEDURE MYLIB.DLTOLDSPL
TO PUBLIC ;
GRANT ALTER , EXECUTE
ON SPECIFIC PROCEDURE MYLIB.DLTOLDSPL
TO QSYS WITH GRANT OPTION ;
A questo punto siamo pronti a creare la nostra procedura SQL.
Possiamo farlo direttamente da QShell o da PASE con il comando:
db2 -tvf /tmp/DELETE_OLD_SPOOLED_FILES.sql
Oppure, richiamando lo stesso comando da riga comandi:
QSH CMD('db2 -tvf /tmp/DELETE_OLD_SPOOLED_FILES.sql')
Dove:
- -t = usa “;” come terminatore dello statement
- -v = verbose (stampa gli statement prima di eseguirli)
- -f = file di input
Oppure possiamo più comodamente aprire il file ed eseguirlo direttamente da Run SQL Scripts di Access Client Solutions.
Una volta che la procedura SQL è disponibile possiamo provarla. Di seguito trovate un esempio di chiamata che non cancella nulla ma mostra solo il result set (parametro PREVIEW => ‘YES’):
CALL
MYLIB.DELETE_OLD_SPOOLED_FILES(
DELETE_OLDER_THAN => CURRENT DATE - 60 DAYS,
P_OUTPUT_QUEUE_LIBRARY_NAME => 'QUSRSYS', -- '*ALL'
P_OUTPUT_QUEUE_NAME => 'QEZJOBLOG', -- '*ALL'
P_USER_NAME => 'QUSER', -- '*ALL'
P_JOB_NAME => 'QRWTSRVR', -- '*ALL'
P_SPOOLED_FILE_NAME => 'QPJOBLOG', -- '*ALL'
P_USER_DATA => 'QRWTSRVR', -- '*ALL'
PREVIEW => 'YES')
;
Per rimuovere la procedura basta eseguire questa istruzione SQL:
DROP PROCEDURE
MYLIB.DELETE_OLD_SPOOLED_FILES
;
A questo punto, il programma CLLE di esempio fornito nel precedente articolo diventa:
/* Before compiling: +
CREATE TABLE QTEMP.SQLOUT AS ( +
SELECT TOTAL_JOBS_IN_SYSTEM, MAXIMUM_JOBS_IN_SYSTEM +
FROM QSYS2.SYSTEM_STATUS_INFO_BASIC) +
WITH NO DATA */
PGM
DCLF FILE(SQLOUT) OPNID(SQLOUT) ALWVARLEN(*YES)
DCL VAR(&E) TYPE(*LGL) VALUE('0')
DCL VAR(&ERROR) TYPE(*LGL) VALUE('0')
/* RUNSQL */
DCL VAR(&SQL) TYPE(*CHAR) LEN(5000)
DCL VAR(&OUTQ) TYPE(*CHAR) LEN(21)
DCL VAR(&SQLLIB) TYPE(*CHAR) LEN(10) VALUE('QTEMP')
DCL VAR(&SQLFIL) TYPE(*CHAR) LEN(10) VALUE('SQLOUT')
DCL VAR(&SQLMBR) TYPE(*CHAR) LEN(10) VALUE('SQLOUT')
MONMSG MSGID(CPF0000) EXEC(GOTO CMDLBL(ERROR))
CALLSUBR SUBR(RTVTOTJOBS)
RUNSQL SQL('CALL MYLIB.DELETE_OLD_SPOOLED_FILES(+
DELETE_OLDER_THAN => CURRENT DATE - 7 DAYS, +
P_USER_NAME => ''*ALL'', +
P_JOB_NAME => ''QP0ZSPWT'')') +
COMMIT(*NONE)
MONMSG MSGID(SQL0000) EXEC(CHGVAR VAR(&E) VALUE('1'))
RUNSQL SQL('CALL MYLIB.DELETE_OLD_SPOOLED_FILES(+
DELETE_OLDER_THAN => CURRENT DATE - 7 DAYS, +
P_USER_NAME => ''*ALL'', +
P_JOB_NAME => ''QP0ZSPWP'')') +
COMMIT(*NONE)
MONMSG MSGID(SQL0000) EXEC(CHGVAR VAR(&E) VALUE('1'))
RUNSQL SQL('CALL MYLIB.DELETE_OLD_SPOOLED_FILES(+
DELETE_OLDER_THAN => CURRENT DATE - 7 DAYS, +
P_USER_NAME => ''*ALL'', +
P_JOB_NAME => ''QJVACMDSRV'')') +
COMMIT(*NONE)
MONMSG MSGID(SQL0000) EXEC(CHGVAR VAR(&E) VALUE('1'))
RUNSQL SQL('CALL MYLIB.DELETE_OLD_SPOOLED_FILES(+
DELETE_OLDER_THAN => CURRENT DATE - 7 DAYS, +
P_USER_NAME => ''*ALL'', +
P_JOB_NAME => ''QZSHSH'')') +
COMMIT(*NONE)
MONMSG MSGID(SQL0000) EXEC(CHGVAR VAR(&E) VALUE('1'))
RUNSQL SQL('CALL MYLIB.DELETE_OLD_SPOOLED_FILES(+
DELETE_OLDER_THAN => CURRENT DATE - 7 DAYS, +
P_USER_NAME => ''QUSER'', +
P_JOB_NAME => ''QRWTSRVR'')') +
COMMIT(*NONE)
MONMSG MSGID(SQL0000) EXEC(CHGVAR VAR(&E) VALUE('1'))
RUNSQL SQL('CALL MYLIB.DELETE_OLD_SPOOLED_FILES(+
DELETE_OLDER_THAN => CURRENT DATE - 7 DAYS, +
P_USER_NAME => ''*ALL'', +
P_JOB_NAME => ''QPRTJOB'', +
P_USER_DATA => ''QZRCSRVS'')') +
COMMIT(*NONE)
MONMSG MSGID(SQL0000) EXEC(CHGVAR VAR(&E) VALUE('1'))
CALLSUBR SUBR(RTVTOTJOBS)
GOTO CMDLBL(ENDCLP)
ERROR:
CHGVAR VAR(&ERROR) VALUE('1')
MONMSG MSGID(CPF0000)
GOTO CMDLBL(ENDCLP)
ENDCLP:
IF COND(&ERROR) THEN(DO)
SNDPGMMSG MSGID(CPF9898) MSGF(QCPFMSG) +
MSGDTA('Rilevato errore durante +
l''esecuzione della procedura, vedere +
messaggi precedenti') MSGTYPE(*ESCAPE)
MONMSG MSGID(CPF0000)
ENDDO
ELSE CMD(IF COND(&E) THEN(DO))
SNDPGMMSG MSGID(CPF9898) MSGF(QCPFMSG) +
MSGDTA('Spooled files rimossi +
parzialmente, vedere messaggi +
precedenti') MSGTYPE(*ESCAPE)
MONMSG MSGID(CPF0000)
ENDDO
RETURN
RTVTOTJOBS: SUBR SUBR(RTVTOTJOBS)
DLTF FILE(&SQLLIB/&SQLFIL)
MONMSG MSGID(CPF2105)
CHGVAR VAR(&SQL) VALUE('SELECT TOTAL_JOBS_IN_SYSTEM, +
MAXIMUM_JOBS_IN_SYSTEM FROM +
QSYS2.SYSTEM_STATUS_INFO_BASIC')
CHGVAR VAR(&SQL) VALUE('CREATE TABLE' *BCAT &SQLLIB +
*TCAT '/' *CAT &SQLFIL *BCAT 'AS (' *CAT &SQL +
*TCAT ') WITH DATA')
RUNSQL SQL(&SQL) COMMIT(*NONE) NAMING(*SYS)
MONMSG MSGID(SQL0000) EXEC(DO)
ENDDO
OVRDBF FILE(SQLOUT) TOFILE(&SQLLIB/&SQLFIL) +
MBR(&SQLMBR) LVLCHK(*NO) OVRSCOPE(*CALLLVL)
RCVF OPNID(SQLOUT)
CLOSE OPNID(SQLOUT) /* RVCF */
DLTOVR FILE(SQLOUT) LVL(*) /* OVRDBF */
SNDPGMMSG MSGID(CPI8859) MSGF(QCPFMSG) MSGDTA('Jobs in +
system:' *BCAT %CHAR(&SQLOUT_TOTAL_JOBS) +
*BCAT '/' *BCAT %CHAR(&SQLOUT_MAX_JOBS)) +
TOPGMQ(*PRV) TOMSGQ(*TOPGMQ) MSGTYPE(*INFO)
DLTF FILE(&SQLLIB/&SQLFIL)
MONMSG MSGID(CPF0000)
ENDSUBR RTNVAL(0)
ENDPGM:
ENDPGM
Riferimenti


