Skip to content

Instantly share code, notes, and snippets.

@ryan-moeller21
Last active March 24, 2026 13:48
Show Gist options
  • Select an option

  • Save ryan-moeller21/e376e7ddae173e512cef8fa9f8005bd4 to your computer and use it in GitHub Desktop.

Select an option

Save ryan-moeller21/e376e7ddae173e512cef8fa9f8005bd4 to your computer and use it in GitHub Desktop.
SQL for System and Storage Management
/*************************************************************/
/* Use system limits to find users abusing IFS space */
/*************************************************************/
-- Look at all users who have triggered system limits in the last 24 hours...
SELECT *
FROM QSYS2.SYSLIMITS
WHERE LAST_CHANGE_TIMESTAMP > CURRENT TIMESTAMP - 1 DAY
AND USER_NAME != 'QSYS'
ORDER BY LAST_CHANGE_TIMESTAMP DESC;
-- SQL can help identify who has been creating these files!
SELECT USER_NAME,
COUNT(*) AS NUM_FILES,
MAX(ENTRY_TIMESTAMP) AS LATEST_UPDATE
FROM TABLE (
SYSTOOLS.AUDIT_JOURNAL_CO(STARTING_TIMESTAMP => CURRENT TIMESTAMP - 14 DAY)
)
WHERE USER_NAME != 'QSYS'
GROUP BY USER_NAME
ORDER BY NUM_FILES DESC;
-- If a user profile has triggered a system limit, use audit journal entries to track what they are doing...
SELECT SYS.USER_NAME,
COUNT(*) AS NUM_FILES,
MAX(ENTRY_TIMESTAMP) AS LATEST_UPDATE
FROM QSYS2.SYSLIMTBL SYS
JOIN TABLE (
SYSTOOLS.AUDIT_JOURNAL_CO(STARTING_TIMESTAMP => CURRENT TIMESTAMP - 14 DAY)
) AUDIT
ON SYS.USER_NAME = AUDIT.USER_NAME
WHERE SYS.USER_NAME NOT LIKE 'Q%'
AND LIMIT_CATEGORY = 5 -- Category 5 is "File system", see https://www.ibm.com/docs/en/i/7.5?topic=services-syslimtbl-table
GROUP BY SYS.USER_NAME
ORDER BY NUM_FILES DESC;
/***********************************************************************************/
/* Look at files taking up space in IFS */
/* */
/* Returns similar information to RTVDIRINF, but at the (relative) speed of light! */
/***********************************************************************************/
-- Get raw info about files from IFS_OBJECT_STATISTICS
WITH ALL_OBJS AS (
SELECT PATH_NAME,
OBJECT_TYPE,
DATA_SIZE AS FILESIZE,
OBJECT_OWNER
FROM TABLE (
QSYS2.IFS_OBJECT_STATISTICS(START_PATH_NAME => '/home', -- Set "root" directory for analysis
SUBTREE_DIRECTORIES => 'YES',
OMIT_LIST => '/QSYS.LIB /QFileSvr.400')
)
),
-- Get the total size of all data underneath root
TOTAL_DATA_SIZE AS (
SELECT CAST(SUM(FILESIZE) AS DECFLOAT) AS DATA_SIZE
FROM ALL_OBJS
WHERE OBJECT_TYPE != '*DIR'
),
-- Get path names to files, also calculate percent storage used (relative to total storage under root).
PATHS_AND_FILES AS (
SELECT SUBSTRING(PATH_NAME, 1, LOCATE_IN_STRING(PATH_NAME, '/', -1)) AS PATHNAME,
SUBSTRING(PATH_NAME, LOCATE_IN_STRING(PATH_NAME, '/', -1) + 1) AS FILENAME,
FILESIZE,
OBJECT_TYPE,
(CAST(FILESIZE AS DECFLOAT) / (SELECT DATA_SIZE FROM TOTAL_DATA_SIZE)) * 100 AS PERCENT_STORAGE
FROM ALL_OBJS
WHERE OBJECT_TYPE != '*DIR'
),
-- Sum file size by directory.
FILES_AND_DIRS AS (
SELECT PATHNAME,
COUNT(*) AS NUM_FILES,
CAST(CAST(SUM(FILESIZE) AS DECFLOAT) / 1000000 AS DEC(12, 2)) AS STORAGE_USED_MB,
CAST(SUM(PERCENT_STORAGE) AS DEC(5, 2)) AS PERCENT_STORAGE_USED_OF_RELATIVE_ROOT
FROM PATHS_AND_FILES
GROUP BY PATHNAME)
SELECT * FROM FILES_AND_DIRS
ORDER BY PERCENT_STORAGE_USED_OF_RELATIVE_ROOT DESC;
/**************************************************************************************/
/* Track temporary storage usage with QSYS2.SYSTEM_STATUS and QSYS2.SYSTMPSTG */
/**************************************************************************************/
-- Create schema for function and detail file creation.
CREATE SCHEMA TMPSTGMON;
-- Information as presented by Navigator
SELECT SYSTEM_ASP_STORAGE,
CURRENT_TEMPORARY_STORAGE,
SYSTEM_ASP_USED,
DECIMAL(DECFLOAT(CURRENT_TEMPORARY_STORAGE) / 1000, 10, 2) AS CURRENT_TEMPORARY_STORAGE_GB,
DECIMAL(DECFLOAT(MAXIMUM_TEMPORARY_STORAGE_USED) / 1000, 10, 2) AS MAX_TEMPORARY_STORAGE_USED_GB,
DECIMAL(DECFLOAT(CURRENT_TEMPORARY_STORAGE) * 100 / DECFLOAT(SYSTEM_ASP_STORAGE), 5, 2) AS CURRENT_TEMPORARY_PERCENTAGE_USED
FROM TABLE (QSYS2.SYSTEM_STATUS());
-- Take granular information from SYSTMPSTG and combine it with total temp storage information from SYSTEM_STATUS
WITH BUCKET_INFO AS (
SELECT GLOBAL_BUCKET_NAME,
BUCKET_CURRENT_SIZE / 1000000 AS CURRENT_TEMP_SIZE_MB,
BUCKET_PEAK_SIZE / 1000000 AS PEAK_TEMP_SIZE_MB
FROM QSYS2.SYSTMPSTG
),
TEMP_STORAGE_INFO AS (
SELECT
SYSTEM_ASP_STORAGE,
DECIMAL(DECFLOAT(CURRENT_TEMPORARY_STORAGE), 10, 2) AS CURRENT_TEMP_MB,
DECIMAL(DECFLOAT(MAXIMUM_TEMPORARY_STORAGE_USED), 10, 2) AS PEAK_TEMP_MB
FROM TABLE (QSYS2.SYSTEM_STATUS())
)
SELECT GLOBAL_BUCKET_NAME,
DECIMAL(CURRENT_TEMP_SIZE_MB, 10, 2),
DECIMAL(PEAK_TEMP_SIZE_MB, 10, 2),
DECIMAL(CURRENT_TEMP_SIZE_MB / (SELECT CURRENT_TEMP_MB FROM TEMP_STORAGE_INFO) * 100, 5, 2) AS PERCENT_OF_CURRENT_TEMP,
DECIMAL(PEAK_TEMP_SIZE_MB / (SELECT PEAK_TEMP_MB FROM TEMP_STORAGE_INFO) * 100, 5, 2) AS PERCENT_OF_PEAK_TEMP,
DECIMAL(CURRENT_TEMP_SIZE_MB / (SELECT SYSTEM_ASP_STORAGE FROM TEMP_STORAGE_INFO) * 100, 5, 2) AS PERCENT_OF_TOTAL_ASP
FROM BUCKET_INFO;
-- Create a function to handle automatic reporting!
CREATE OR REPLACE procedure TMPSTGMON.TEMP_STORAGE_NOTIFIER()
LANGUAGE SQL
MODIFIES SQL DATA
BEGIN
DECLARE STMT VARCHAR(1000);
DECLARE FILENAME VARCHAR(100);
DECLARE DB_SEGMENT_WARNING INT DEFAULT 10000;
DECLARE SQE_WARNING INT DEFAULT 1;
DECLARE MTI_WARNING INT DEFAULT 10000;
DECLARE LOB_WARNING INT DEFAULT 1000;
DECLARE JOB_WARNING INT DEFAULT 100;
DECLARE DB_SEGMENT_USAGE INT;
DECLARE SQE_USAGE INT;
DECLARE MTI_USAGE INT;
DECLARE LOB_USAGE INT;
DECLARE ACTIVE_JOB_USAGE INT;
DECLARE HIT_LIMIT INT DEFAULT 0;
-- Get the current temporary storage usage information (in MB) from QSYS2.SYSTMPSTG, and save them for the calculations below.
SELECT BUCKET_CURRENT_SIZE / 1000000 INTO DB_SEGMENT_USAGE FROM QSYS2.SYSTMPSTG WHERE GLOBAL_BUCKET_NAME = '*DATABASE Segment Cache';
SELECT BUCKET_CURRENT_SIZE / 1000000 INTO SQE_USAGE FROM QSYS2.SYSTMPSTG WHERE GLOBAL_BUCKET_NAME = '*DATABASE SQE Heap';
SELECT BUCKET_CURRENT_SIZE / 1000000 INTO MTI_USAGE FROM QSYS2.SYSTMPSTG WHERE GLOBAL_BUCKET_NAME = '*DATABASE DSI SQE MTI';
SELECT SUM(BUCKET_CURRENT_SIZE) / 1000000 INTO LOB_USAGE FROM QSYS2.SYSTMPSTG WHERE GLOBAL_BUCKET_NAME IN ('*DATABASE DS SQE LOB', '*DATABASE DS SQQQ LOB');
SELECT COUNT(*) INTO ACTIVE_JOB_USAGE FROM QSYS2.SYSTMPSTG WHERE (BUCKET_CURRENT_SIZE / 1000000) > 100 AND JOB_NAME IS NOT NULL AND JOB_STATUS = '*ACTIVE';
CALL SYSTOOLS.LPRINTF('Checking for excessive temporary storage thresholds...');
--
-- If any temporary storage bucket exceeds the configured warning level, perform two actions:
-- 1.) Get more information about the temporary storage usage by calling an SQL service to provide
-- more detail about that specific bucket and save that data to a new file.
-- 2.) Build a string to print/email/send
-- Look for SQL Query Engine temp storage usage.
IF SQE_USAGE > SQE_WARNING THEN
SET HIT_LIMIT = 1;
SET FILENAME = 'TMPSTGMON.' CONCAT VARCHAR_FORMAT(CURRENT DATE, 'MONDDYY') CONCAT 'SQE';
CALL SYSTOOLS.LPRINTF('SQE temp storage threshold exceeded! Threshold: ' CONCAT SQE_WARNING CONCAT 'MB. Used: ' CONCAT SQE_USAGE CONCAT 'MB. See ' CONCAT FILENAME CONCAT ' for details.');
-- Create details file
SET STMT = 'CREATE TABLE ' CONCAT FILENAME CONCAT ' AS (SELECT * FROM TABLE(QSYS2.ACTIVE_QUERY_INFO()) ORDER BY AVERAGE_TEMPORARY_STORAGE DESC LIMIT 50) WITH DATA';
PREPARE DETAIL_DATA_STMT FROM STMT;
EXECUTE DETAIL_DATA_STMT;
END IF;
-- Maintained Temporary Index temp storage usage
IF MTI_USAGE > MTI_WARNING THEN
SET HIT_LIMIT = 1;
SET FILENAME = 'TMPSTGMON.' CONCAT VARCHAR_FORMAT(CURRENT DATE, 'MONDDYY') CONCAT 'MTI';
CALL SYSTOOLS.LPRINTF('MTI temp storage threshold exceeded! Threshold: ' CONCAT MTI_WARNING CONCAT 'MB. Used: ' CONCAT MTI_USAGE CONCAT 'MB. See ' CONCAT FILENAME CONCAT 'for details.');
-- Create details file
SET STMT = 'CREATE TABLE ' CONCAT FILENAME CONCAT ' AS (SELECT * FROM TABLE(QSYS2.MTI_INFO()) ORDER BY MTI_SIZE DESC LIMIT 30) WITH DATA';
PREPARE DETAIL_DATA_STMT FROM STMT;
EXECUTE DETAIL_DATA_STMT;
END IF;
-- Individual *ACTIVE job temp storage usage
IF ACTIVE_JOB_USAGE > 0 THEN
SET HIT_LIMIT = 1;
SET FILENAME = 'TMPSTGMON.' CONCAT VARCHAR_FORMAT(CURRENT DATE, 'MONDDYY') CONCAT 'AJ';
CALL SYSTOOLS.LPRINTF('Job temp storage threshold exceeded! Per-job threshold: ' CONCAT ACTIVE_JOB_USAGE CONCAT 'MB. Number of jobs exceeding limit: ' CONCAT ACTIVE_JOB_USAGE CONCAT '. See ' CONCAT FILENAME CONCAT ' for details.');
-- Create details file
SET STMT = 'CREATE TABLE ' CONCAT FILENAME CONCAT ' AS (SELECT * FROM TABLE(QSYS2.ACTIVE_JOB_INFO()) WHERE TEMPORARY_STORAGE > ' CONCAT JOB_WARNING CONCAT ') WITH DATA';
PREPARE DETAIL_DATA_STMT FROM STMT;
EXECUTE DETAIL_DATA_STMT;
END IF;
-- If any limit was hit, get all ended jobs from the previous day
IF HIT_LIMIT = 1 THEN
SET FILENAME = 'TMPSTGMON.' CONCAT VARCHAR_FORMAT(CURRENT DATE, 'MONDDYY') CONCAT 'EJ';
SET STMT = 'CREATE TABLE ' CONCAT FILENAME CONCAT ' AS (
SELECT *
FROM TABLE (
SYSTOOLS.ENDED_JOB_INFO()
) ORDER BY PEAK_TEMPORARY_STORAGE DESC) WITH DATA';
PREPARE DETAIL_DATA_STMT FROM STMT;
EXECUTE DETAIL_DATA_STMT;
END IF;
RETURN;
END;
CALL TMPSTGMON.TEMP_STORAGE_NOTIFIER();
/************************************************************************************************************/
/* Check for defective PTFs, new PTF groups, and firmware updates. Email someone a spreadsheet the results! */
/************************************************************************************************************/
CREATE OR REPLACE FUNCTION SYSTOOLS.SYSTEM_CURRENCY_STATUS (
EMAIL VARCHAR(100)
)
RETURNS INT
SPECIFIC SYSTOOLS.SYSCURSTS
MODIFIES SQL DATA
LANGUAGE SQL
BEGIN
DECLARE SUCCESSFLAG INT;
DECLARE FILEPATH VARCHAR(1000);
DECLARE FILEEXT VARCHAR(5) DEFAULT '.csv';
DECLARE ABSORBFAILURE integer;
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET ABSORBFAILURE = 1;
SET FILEPATH = '/dailyCurrencyReports/currency' CONCAT VARCHAR_FORMAT(CURRENT DATE, 'MONDDYY');
-- Create directory to hold spreadsheets
CALL QSYS2.QCMDEXC('QSH CMD(''mkdir -p /dailyCurrencyReports'')');
-- Use query to generate spreadsheet at specified IFS directory
VALUES (
SYSTOOLS.GENERATE_SPREADSHEET(PATH_NAME => FILEPATH,
SPREADSHEET_QUERY => 'WITH CURRENCY(TYPE, PRODUCT_ID, TITLE, CURRENT_VERSION, AVAILABLE_VERSION) AS (
SELECT ''PTF GROUP'',
PTF_GROUP_ID,
PTF_GROUP_TITLE,
CAST(PTF_GROUP_LEVEL_INSTALLED AS VARCHAR(20)),
CAST(PTF_GROUP_LEVEL_AVAILABLE AS VARCHAR(20))
FROM SYSTOOLS.GROUP_PTF_CURRENCY
WHERE PTF_GROUP_LEVEL_INSTALLED != PTF_GROUP_LEVEL_AVAILABLE
UNION ALL
SELECT ''PTF'',
PRODUCT_ID,
''Defective PTF'',
DEFECTIVE_PTF,
FIXING_PTF
FROM SYSTOOLS.DEFECTIVE_PTF_CURRENCY
UNION ALL
SELECT ''FIRMWARE'',
FW_PRODUCT_ID,
''Firmware'',
FW_CURRENTFIXPACK,
FW_RECOMMENDED_UPDATE
FROM SYSTOOLS.FIRMWARE_CURRENCY)
SELECT * FROM CURRENCY',
SPREADSHEET_TYPE => 'csv', COLUMN_HEADINGS => 'COLUMN')) INTO SUCCESSFLAG;
IF SUCCESSFLAG = 1 THEN
VALUES (SYSTOOLS.SEND_EMAIL(
TO_EMAIL => EMAIL,
SUBJECT => 'PTF and Firmware Currency Report for ' CONCAT CURRENT SERVER,
BODY => 'See attachment!',
ATTACHMENT => FILEPATH CONCAT FILEEXT)) INTO SUCCESSFLAG;
END IF;
RETURN SUCCESSFLAG;
END;
VALUES SYSTOOLS.SYSTEM_CURRENCY_STATUS(:email);
/******************************************************************************/
/* Find security-related changes made by potentially vulnerable user profiles */
/******************************************************************************/
-- Navigator's Users Tab
SELECT
CASE GROUP_ID_NUMBER
WHEN 0 THEN 'USER'
ELSE 'GROUP'
END AS PROFILE_TYPE,
A.*
FROM QSYS2.USER_INFO A
LIMIT 100 OFFSET 0;
-- Find users who haven't changed their password in a while...
SELECT AUTHORIZATION_NAME,
PREVIOUS_SIGNON,
PASSWORD_CHANGE_DATE,
TEXT_DESCRIPTION
FROM QSYS2.USER_INFO
WHERE GROUP_ID_NUMBER = 0
AND PREVIOUS_SIGNON IS NOT NULL
AND PASSWORD_CHANGE_DATE < CURRENT_TIMESTAMP - 3 YEAR
AND STATUS = '*ENABLED'
ORDER BY PASSWORD_CHANGE_DATE ASC;
-- Find delete operations performed by potentially-vulnerable user profiles
WITH PASSWORD_NEGLECTORS AS (
SELECT AUTHORIZATION_NAME,
PREVIOUS_SIGNON,
PASSWORD_CHANGE_DATE,
TEXT_DESCRIPTION
FROM QSYS2.USER_INFO A
WHERE GROUP_ID_NUMBER = 0
AND PREVIOUS_SIGNON IS NOT NULL
AND PASSWORD_CHANGE_DATE < CURRENT_TIMESTAMP - 3 YEAR
AND STATUS = '*ENABLED'
)
SELECT *
FROM PASSWORD_NEGLECTORS PG
JOIN TABLE (
SYSTOOLS.AUDIT_JOURNAL_DO(STARTING_TIMESTAMP => CURRENT TIMESTAMP - 1 MONTHS, USER_NAME => PG.AUTHORIZATION_NAME) -- DO finds Deleted Object
) AUDIT_INFO
ON PG.AUTHORIZATION_NAME = AUDIT_INFO.USER_NAME
WHERE AUTHORIZATION_NAME NOT LIKE 'Q%'; -- Don't include system profiles
-- Take the results of the previous query, but now disable the profiles.
WITH PASSWORD_NEGLECTORS AS (
SELECT AUTHORIZATION_NAME,
PREVIOUS_SIGNON,
PASSWORD_CHANGE_DATE,
TEXT_DESCRIPTION
FROM QSYS2.USER_INFO A
WHERE GROUP_ID_NUMBER = 0
AND PREVIOUS_SIGNON IS NOT NULL
AND PASSWORD_CHANGE_DATE < CURRENT_TIMESTAMP - 3 YEAR
AND STATUS = '*ENABLED'
)
SELECT *
FROM PASSWORD_NEGLECTORS PG,
TABLE (
SYSTOOLS.AUDIT_JOURNAL_DO(STARTING_TIMESTAMP => CURRENT TIMESTAMP - 1 MONTHS,
USER_NAME => PG.AUTHORIZATION_NAME) -- DO finds Deleted Object
) AUDIT_INFO,
TABLE (
SYSTOOLS.CHANGE_USER_PROFILE(P_USER_NAME => PG.AUTHORIZATION_NAME,
P_STATUS => '*DISABLED',
PREVIEW => 'YES')
)
WHERE PG.AUTHORIZATION_NAME = AUDIT_INFO.USER_NAME
AND AUTHORIZATION_NAME NOT LIKE 'Q%'; -- Don't include system profiles
/*************************************************************************************************/
/* Manage consumption of storage by journal receivers, only keep last N days of journals */
/*************************************************************************************************/
SELECT *
FROM QSYS2.JOURNAL_RECEIVER_INFO
WHERE DETACH_TIMESTAMP < CURRENT DATE - 3 DAYS;
-- NOTE: *IGNINQMSG is not specified for DLTJRNRCV. Any unsaved journals receivers will not be deleted.
CREATE OR REPLACE FUNCTION COOLSTUFF.DELETE_OLD_RECEIVERS (
DAYS_OLD INT,
RECEIVER_LIBRARY VARCHAR(1000) DEFAULT '*ALL',
RECEIVER_NAME VARCHAR(10) DEFAULT '*ALL',
DELETE_UNSAVED VARCHAR(3) DEFAULT 'NO',
PREVIEW VARCHAR(3) DEFAULT 'YES'
)
RETURNS TABLE (
ACTION_TAKEN VARCHAR(3),
JOURNAL_RECEIVER_LIBRARY VARCHAR(10),
JOURNAL_RECEIVER_NAME VARCHAR(10),
DESCRIPTIVE_TEXT VARCHAR(50),
JOURNAL_LIBRARY VARCHAR(10),
JOURNAL_NAME VARCHAR(10),
SIZE INTEGER,
ATTACH_TIMESTAMP TIMESTAMP,
DETACH_TIMESTAMP TIMESTAMP,
SAVE_TIMESTAMP TIMESTAMP
) SPECIFIC COOLSTUFF.DLTOLDJRCV
MODIFIES SQL DATA
LANGUAGE SQL
BEGIN
FOR JRN_CURSOR CURSOR FOR SELECT X.*
FROM QSYS2.JOURNAL_RECEIVER_INFO X, TABLE(SYSTOOLS.SPLIT(RECEIVER_LIBRARY, ',')) Y
WHERE DETACH_TIMESTAMP < CURRENT TIMESTAMP - DAYS_OLD DAYS
AND (JOURNAL_RECEIVER_LIBRARY LIKE REPLACE(RECEIVER_LIBRARY, '*ALL', '%') OR X.JOURNAL_RECEIVER_LIBRARY = TRIM(Y.ELEMENT)) -- Handle both *ALL and comma-delimited formats
AND (JOURNAL_RECEIVER_NAME LIKE REPLACE(RECEIVER_NAME, '*ALL', '%'))
AND (UPPER(DELETE_UNSAVED) = 'YES' OR (SAVE_TIMESTAMP IS NOT NULL AND UPPER(DELETE_UNSAVED) = 'NO'))
ORDER BY DETACH_TIMESTAMP ASC, JOURNAL_RECEIVER_NAME ASC
DO
IF PREVIEW = 'NO' THEN
CALL QSYS2.QCMDEXC('DLTJRNRCV JRNRCV(' CONCAT JOURNAL_RECEIVER_LIBRARY CONCAT '/' CONCAT JOURNAL_RECEIVER_NAME CONCAT ')');
PIPE ('YES', JOURNAL_RECEIVER_LIBRARY, JOURNAL_RECEIVER_NAME, DESCRIPTIVE_TEXT, JOURNAL_LIBRARY, JOURNAL_NAME, SIZE, ATTACH_TIMESTAMP, DETACH_TIMESTAMP, SAVE_TIMESTAMP);
ELSE
PIPE ('NO', JOURNAL_RECEIVER_LIBRARY, JOURNAL_RECEIVER_NAME, DESCRIPTIVE_TEXT, JOURNAL_LIBRARY, JOURNAL_NAME, SIZE, ATTACH_TIMESTAMP, DETACH_TIMESTAMP, SAVE_TIMESTAMP);
END IF;
END FOR;
RETURN;
END;
-- PREVIEW => 'YES' will NOT delete receivers (default)
-- 'NO' will delete receivers
-- RECEIVER_LIBRARY => '*ALL' will search all libraries on system (default)
-- Otherwise, a comma-delimited list of library names can be provided
-- RECEIVER_NAME => '*ALL' will search for all receiver names
-- Otherwise, this is handled as a LIKE predicate, meaning wildcarding can be used. See example below.
-- DELETE_UNSAVED => 'NO' will prevent unsaved receivers from being deleted (default)
-- 'YES' will delete all receivers, including unsaved ones (typically not recommended)
-- DAYS_OLD => Journals with a detach timestamp older than DAYS_OLD days ago will be deleted
SELECT * FROM TABLE (COOLSTUFF.DELETE_OLD_RECEIVERS(DAYS_OLD => 3,
RECEIVER_LIBRARY => 'QSYS',
RECEIVER_NAME => 'QSQ%', -- Find all journal names starting with QSQ
DELETE_UNSAVED => 'NO',
PREVIEW => 'YES'));
WITH DELETED_ROWS_DATA AS (
SELECT TABLE_SCHEMA,
TABLE_NAME,
NUMBER_DELETED_ROWS,
NUMBER_ROWS,
DECIMAL(NUMBER_DELETED_ROWS, 18, 3) * 100 / (NUMBER_DELETED_ROWS + NUMBER_ROWS) AS PERCENT_DELETED,
DECIMAL(NUMBER_DELETED_ROWS, 18, 3) * (SELECT ROW_LENGTH FROM QSYS2.SYSTABLES Y WHERE Y.TABLE_NAME = X.TABLE_NAME AND Y.TABLE_SCHEMA = X.TABLE_SCHEMA)/1000000 AS UNUSED_SPACE_MB
FROM QSYS2.SYSTABLESTAT X
WHERE NUMBER_ROWS > 0 AND
NUMBER_DELETED_ROWS > 0 AND
TABLE_SCHEMA NOT LIKE 'Q%')
SELECT 'RGZPFM FILE(' CONCAT TABLE_SCHEMA CONCAT '/' CONCAT TABLE_NAME CONCAT ')', DELROWS.* FROM DELETED_ROWS_DATA DELROWS
WHERE UNUSED_SP
SELECT *
FROM QSYS2.JOURNAL_RECEIVER_INFO
WHERE DETACH_TIMESTAMP < CURRENT DATE - 3 DAYS;
-- NOTE: *IGNINQMSG is not specified for DLTJRNRCV. Any unsaved journals receivers will not be deleted.
CREATE OR REPLACE FUNCTION COOLSTUFF.DELETE_OLD_RECEIVERS (
DAYS_OLD INT,
RECEIVER_LIBRARY VARCHAR(1000) DEFAULT '*ALL',
RECEIVER_NAME VARCHAR(10) DEFAULT '*ALL',
DELETE_UNSAVED VARCHAR(3) DEFAULT 'NO',
PREVIEW VARCHAR(3) DEFAULT 'YES'
)
RETURNS TABLE (
ACTION_TAKEN VARCHAR(3),
JOURNAL_RECEIVER_LIBRARY VARCHAR(10),
JOURNAL_RECEIVER_NAME VARCHAR(10),
DESCRIPTIVE_TEXT VARCHAR(50),
JOURNAL_LIBRARY VARCHAR(10),
JOURNAL_NAME VARCHAR(10),
SIZE INTEGER,
ATTACH_TIMESTAMP TIMESTAMP,
DETACH_TIMESTAMP TIMESTAMP,
SAVE_TIMESTAMP TIMESTAMP
) SPECIFIC COOLSTUFF.DLTOLDJRCV
MODIFIES SQL DATA
LANGUAGE SQL
BEGIN
FOR JRN_CURSOR CURSOR FOR SELECT X.*
FROM QSYS2.JOURNAL_RECEIVER_INFO X, TABLE(SYSTOOLS.SPLIT(RECEIVER_LIBRARY, ',')) Y
WHERE DETACH_TIMESTAMP < CURRENT TIMESTAMP - DAYS_OLD DAYS
AND (JOURNAL_RECEIVER_LIBRARY LIKE REPLACE(RECEIVER_LIBRARY, '*ALL', '%') OR X.JOURNAL_RECEIVER_LIBRARY = TRIM(Y.ELEMENT)) -- Handle both *ALL and comma-delimited formats
AND (JOURNAL_RECEIVER_NAME LIKE REPLACE(RECEIVER_NAME, '*ALL', '%'))
AND (UPPER(DELETE_UNSAVED) = 'YES' OR (SAVE_TIMESTAMP IS NOT NULL AND UPPER(DELETE_UNSAVED) = 'NO'))
ORDER BY DETACH_TIMESTAMP ASC, JOURNAL_RECEIVER_NAME ASC
DO
IF PREVIEW = 'NO' THEN
CALL QSYS2.QCMDEXC('DLTJRNRCV JRNRCV(' CONCAT JOURNAL_RECEIVER_LIBRARY CONCAT '/' CONCAT JOURNAL_RECEIVER_NAME CONCAT ')');
PIPE ('YES', JOURNAL_RECEIVER_LIBRARY, JOURNAL_RECEIVER_NAME, DESCRIPTIVE_TEXT, JOURNAL_LIBRARY, JOURNAL_NAME, SIZE, ATTACH_TIMESTAMP, DETACH_TIMESTAMP, SAVE_TIMESTAMP);
ELSE
PIPE ('NO', JOURNAL_RECEIVER_LIBRARY, JOURNAL_RECEIVER_NAME, DESCRIPTIVE_TEXT, JOURNAL_LIBRARY, JOURNAL_NAME, SIZE, ATTACH_TIMESTAMP, DETACH_TIMESTAMP, SAVE_TIMESTAMP);
END IF;
END FOR;
RETURN;
END;
-- PREVIEW => 'YES' will NOT delete receivers (default)
-- 'NO' will delete receivers
-- RECEIVER_LIBRARY => '*ALL' will search all libraries on system (default)
-- Otherwise, a comma-delimited list of library names can be provided
-- RECEIVER_NAME => '*ALL' will search for all receiver names
-- Otherwise, this is handled as a LIKE predicate, meaning wildcarding can be used. See example below.
-- DELETE_UNSAVED => 'NO' will prevent unsaved receivers from being deleted (default)
-- 'YES' will delete all receivers, including unsaved ones (typically not recommended)
-- DAYS_OLD => Journals with a detach timestamp older than DAYS_OLD days ago will be deleted
SELECT * FROM TABLE (COOLSTUFF.DELETE_OLD_RECEIVERS(DAYS_OLD => 3,
RECEIVER_LIBRARY => 'QSYS',
RECEIVER_NAME => 'QSQ%', -- Find all journal names starting with QSQ
DELETE_UNSAVED => 'NO',
PREVIEW => 'YES'));
/************************************************************************************/
/* Clean up storage that is being consumed by tables with many deleted rows */
/************************************************************************************/
WITH DELETED_ROWS_DATA AS (
SELECT TABLE_SCHEMA,
TABLE_NAME,
NUMBER_DELETED_ROWS,
NUMBER_ROWS,
DECIMAL(NUMBER_DELETED_ROWS, 18, 3) * 100 / (NUMBER_DELETED_ROWS + NUMBER_ROWS) AS PERCENT_DELETED,
DECIMAL(NUMBER_DELETED_ROWS, 18, 3) * (SELECT ROW_LENGTH FROM QSYS2.SYSTABLES Y WHERE Y.TABLE_NAME = X.TABLE_NAME AND Y.TABLE_SCHEMA = X.TABLE_SCHEMA)/1000000 AS UNUSED_SPACE_MB
FROM QSYS2.SYSTABLESTAT X
WHERE NUMBER_ROWS > 0 AND
NUMBER_DELETED_ROWS > 0 AND
TABLE_SCHEMA NOT LIKE 'Q%')
SELECT 'RGZPFM FILE(' CONCAT TABLE_SCHEMA CONCAT '/' CONCAT TABLE_NAME CONCAT ')', DELROWS.* FROM DELETED_ROWS_DATA DELROWS
WHERE UNUSED_SPACE_MB > 1000
ORDER BY UNUSED_SPACE_MB DESC;
CREATE OR REPLACE FUNCTION COOLSTUFF.DELETED_ROW_CLEANUP (MINIMUM_EMPTY_SPACE_MB INT, PREVIEW VARCHAR(3))
RETURNS TABLE (
ACTION_TAKEN CHAR(3),
TABLE_SCHEMA VARCHAR(128),
TABLE_NAME VARCHAR(128),
NUMBER_DELETED_ROWS BIGINT,
NUMBER_ROWS BIGINT,
PERCENT_DELETED DECIMAL(31, 16),
UNUSED_SPACE_MB DECIMAL(31, 5)
)
MODIFIES SQL DATA
LANGUAGE SQL
BEGIN
FOR TAB_CURSOR CURSOR FOR WITH DELETED_ROWS_DATA AS (
SELECT TABLE_SCHEMA,
TABLE_NAME,
NUMBER_DELETED_ROWS,
NUMBER_ROWS,
DECIMAL(NUMBER_DELETED_ROWS, 30, 3) / (NUMBER_DELETED_ROWS + NUMBER_ROWS) AS PERCENT_DELETED,
DECIMAL(NUMBER_DELETED_ROWS, 30, 3) * (SELECT ROW_LENGTH FROM QSYS2.SYSTABLES Y WHERE Y.TABLE_NAME = X.TABLE_NAME AND Y.TABLE_SCHEMA = X.TABLE_SCHEMA)/1000000 AS UNUSED_SPACE_MB
FROM QSYS2.SYSTABLESTAT X
WHERE NUMBER_ROWS > 0 AND
NUMBER_DELETED_ROWS > 0 AND
TABLE_SCHEMA NOT LIKE 'Q%')
SELECT *
FROM DELETED_ROWS_DATA DELROWS
WHERE UNUSED_SPACE_MB > 1000
ORDER BY UNUSED_SPACE_MB DESC
DO
IF PREVIEW = 'NO' THEN
-- 'RGZPFM FILE(' CONCAT TABLE_SCHEMA CONCAT '/' CONCAT TABLE_NAME CONCAT ')', DELROWS.
--CALL QSYS2.QCMDEXC('DLTJRNRCV JRNRCV(' CONCAT JOURNAL_RECEIVER_LIBRARY CONCAT '/' CONCAT JOURNAL_RECEIVER_NAME CONCAT ')');
PIPE ('YES', TABLE_SCHEMA, TABLE_NAME, NUMBER_DELETED_ROWS, NUMBER_ROWS, PERCENT_DELETED, UNUSED_SPACE_MB);
ELSE
PIPE ('NO', TABLE_SCHEMA, TABLE_NAME, NUMBER_DELETED_ROWS, NUMBER_ROWS, PERCENT_DELETED, UNUSED_SPACE_MB);
END IF;
END FOR;
RETURN;
RETURN;
END;
SELECT * FROM TABLE (COOLSTUFF.DELETED_ROW_CLEANUP(MINIMUM_EMPTY_SPACE_MB => 1000,
PREVIEW => 'YES'));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment