-- ========================================================= -- Database Health Check Report (HTML) -- Author : IFSTechServices.com -- Purpose : Generate HTML health check report similar to DB Info -- Note : Uses only non-AWR/ASH views (no Diagnostic Pack needed) -- Connect using sys USER -- Test on Lower Environment before on production -- For Any support. Please mail to ifs.services@IFSTechServices.com -- ========================================================= -- ===== Thresholds (tune these as per your standards) ===== DEFINE TBS_WARN_PCT = 15 -- Tablespace free % warning threshold DEFINE FRA_WARN_PCT = 80 -- FRA usage warn (%) DEFINE FRA_FAIL_PCT = 90 -- FRA usage fail (%) DEFINE UPTIME_WARN_HOURS = 24 -- Warn if instance up time < hours DEFINE RMAN_WARN_HOURS = 24 -- Warn if last successful RMAN > hours SET LINESIZE 2000 SET LONG 200000 SET LONGCHUNKSIZE 200000 SET TRIMSPOOL ON SET WRAP OFF SET TERMOUT OFF SET FEEDBACK OFF SET VERIFY OFF SET HEADING ON SET PAGESIZE 50000 --SET LINESIZE 220 SET TRIMSPOOL ON -- HTML ON SET MARKUP HTML ON SPOOL ON TABLE ON PREFORMAT OFF ENTMAP OFF SPOOL Database_Health_Check_Report.html -- ===== Page Header / Style ===== set head off SELECT '

' || 'Oracle Database Health Check

' || '
Generated On: ' || TO_CHAR(SYSDATE, 'DD-MON-YYYY HH24:MI:SS') || '
' || '' FROM dual; set head on -- Use a consistent table style SET MARKUP HTML ON TABLE "border='1' cellpadding='5' cellspacing='0'" PROMPT

Environment

SELECT banner_full AS "Database Edition / Version" FROM v$version WHERE banner_full LIKE 'Oracle Database%'; SELECT d.dbid, d.name AS "Database Name", d.platform_name AS "Platform", d.log_mode AS "Archive Mode", CASE d.cdb WHEN 'YES' THEN 'CONTAINER DATABASE' ELSE 'NON-CDB' END AS "Container" FROM v$database d; SELECT host_name AS "Host", instance_name AS "Instance", version AS "RDBMS Version", status AS "Instance Status", to_char(startup_time,'DD-MON-YYYY HH24:MI:SS') AS "Startup Time", ROUND((SYSDATE - startup_time)*24,2) AS "Hours Up" FROM v$instance; PROMPT

Health Summary (Pass/Warn/Fail)

-- Robust version without WITH/CTE to avoid SQL*Plus parsing issues SELECT 'Instance Uptime' AS "Check", CASE WHEN hours_up < &UPTIME_WARN_HOURS THEN 'WARN' ELSE 'OK' END AS "Status", TO_CHAR(ROUND(hours_up,2)) || ' hours' AS "Details" FROM ( SELECT (SYSDATE - startup_time)*24 AS hours_up FROM v$instance ) UNION ALL SELECT 'FRA Usage', CASE WHEN fra_pct >= &FRA_FAIL_PCT THEN 'FAIL' WHEN fra_pct >= &FRA_WARN_PCT THEN 'WARN' ELSE 'OK' END, TO_CHAR(NVL(fra_pct,0)) || '%' AS details FROM ( SELECT CASE WHEN space_limit > 0 THEN ROUND(space_used*100/space_limit,1) ELSE 0 END AS fra_pct FROM v$recovery_file_dest ) UNION ALL SELECT 'Low Free Tablespaces (< '||TO_CHAR(&TBS_WARN_PCT)||'%)', CASE WHEN cnt > 0 THEN 'WARN' ELSE 'OK' END, TO_CHAR(cnt) || ' tablespace(s)' FROM ( SELECT COUNT(*) AS cnt FROM ( SELECT df.tablespace_name, ROUND(NVL(fs.bytes,0) * 100 / df.bytes, 1) AS pct_free FROM (SELECT tablespace_name, SUM(bytes) bytes FROM dba_data_files GROUP BY tablespace_name) df LEFT JOIN (SELECT tablespace_name, SUM(bytes) bytes FROM dba_free_space GROUP BY tablespace_name) fs ON fs.tablespace_name = df.tablespace_name WHERE df.tablespace_name NOT IN ( SELECT tablespace_name FROM dba_tablespaces WHERE contents = 'TEMPORARY' ) ) WHERE pct_free < &TBS_WARN_PCT ) UNION ALL SELECT 'Datafiles Autoextend OFF', CASE WHEN cnt > 0 THEN 'WARN' ELSE 'OK' END, TO_CHAR(cnt) || ' file(s)' FROM (SELECT COUNT(*) AS cnt FROM dba_data_files WHERE autoextensible = 'NO') UNION ALL SELECT 'Invalid Objects (non-SYS)', CASE WHEN cnt > 0 THEN 'WARN' ELSE 'OK' END, TO_CHAR(cnt) || ' object(s)' FROM (SELECT COUNT(*) AS cnt FROM dba_objects WHERE status = 'INVALID' AND owner NOT IN ('SYS','SYSTEM')) UNION ALL SELECT 'Scheduler Jobs in BROKEN/FAILED state', CASE WHEN cnt > 0 THEN 'WARN' ELSE 'OK' END, TO_CHAR(cnt) || ' job(s)' FROM (SELECT COUNT(*) AS cnt FROM dba_scheduler_jobs WHERE state IN ('BROKEN','FAILED')) UNION ALL SELECT 'Legacy DBMS_JOB (BROKEN=Y)', CASE WHEN cnt > 0 THEN 'WARN' ELSE 'OK' END, TO_CHAR(cnt) || ' job(s)' FROM (SELECT COUNT(*) AS cnt FROM dba_jobs WHERE broken = 'Y') UNION ALL SELECT 'Last RMAN Success', CASE WHEN NVL(hours_since_last_ok, 99999) > &RMAN_WARN_HOURS THEN 'WARN' ELSE 'OK' END, NVL(last_ok_txt, 'No successful backup found') AS details FROM ( SELECT MAX(end_time) AS last_ok, TO_CHAR(MAX(end_time),'DD-MON-YYYY HH24:MI:SS') || ' (' || ROUND( (SYSDATE - MAX(end_time))*24, 2) || 'h ago)' AS last_ok_txt, ROUND( (SYSDATE - MAX(end_time))*24, 2) AS hours_since_last_ok FROM v$rman_backup_job_details WHERE status IN ('COMPLETED','COMPLETED WITH WARNINGS') ) UNION ALL SELECT 'Blocking Sessions Present', CASE WHEN cnt > 0 THEN 'WARN' ELSE 'OK' END, TO_CHAR(cnt) || ' blocked session(s)' FROM (SELECT COUNT(*) AS cnt FROM gv$session WHERE blocking_session IS NOT NULL) ORDER BY 1; PROMPT

PDB Open Modes (if CDB)

SELECT name AS "PDB Name", open_mode AS "Open Mode" FROM v$pdbs ORDER BY name; PROMPT

Tablespaces Capacity And Pressure

SELECT /* + RULE */ df.tablespace_name AS "Tablespace", ROUND(df.bytes/1024/1024) AS "Size (MB)", ROUND(NVL(fs.free,0)/1024/1024) AS "Free (MB)", NVL(ROUND((NVL(fs.free,0)/df.bytes)*100,1),0) AS "% Free", ROUND((1 - NVL(fs.free,0)/df.bytes)*100,1) AS "% Used" FROM ( SELECT tablespace_name, SUM(bytes) bytes FROM dba_data_files GROUP BY tablespace_name ) df LEFT JOIN ( SELECT tablespace_name, SUM(bytes) free FROM dba_free_space GROUP BY tablespace_name ) fs ON df.tablespace_name = fs.tablespace_name UNION ALL SELECT /* + RULE */ df.tablespace_name, ROUND(df.bytes/1024/1024), ROUND(df.bytes_free/1024/1024), NVL(ROUND(df.bytes_free*100/df.bytes,1),0), ROUND((df.bytes - df.bytes_free)*100/df.bytes,1) FROM ( SELECT tsh.tablespace_name, SUM(tsh.bytes_used + tsh.bytes_free) AS bytes, SUM(tsh.bytes_free) AS bytes_free FROM v$temp_space_header tsh GROUP BY tsh.tablespace_name ) df ORDER BY 1; PROMPT

Tablespaces Below &TBS_WARN_PCT.% Free

SELECT * FROM ( SELECT df.tablespace_name AS "Tablespace", ROUND((NVL(fs.free,0)/df.bytes)*100,1) AS "% Free", ROUND(df.bytes/1024/1024) AS "Size (MB)", ROUND(NVL(fs.free,0)/1024/1024) AS "Free (MB)" FROM ( SELECT tablespace_name, SUM(bytes) bytes FROM dba_data_files GROUP BY tablespace_name ) df LEFT JOIN ( SELECT tablespace_name, SUM(bytes) free FROM dba_free_space GROUP BY tablespace_name ) fs ON df.tablespace_name = fs.tablespace_name ) WHERE "% Free" < &TBS_WARN_PCT ORDER BY 2; PROMPT

Datafiles (Autoextend)

SELECT tablespace_name AS "Tablespace", SUBSTR(file_name,1,80) AS "File Name", bytes/1024/1024 AS "Size (MB)", autoextensible AS "Autoextend", increment_by AS "Next Extent (blocks)", maxbytes/1024/1024 AS "Max Size (MB)" FROM dba_data_files ORDER BY 1,2; PROMPT

FRA / Archivelogs / Backup

SELECT name AS "FRA Name", space_limit/1024/1024/1024 AS "Limit (GB)", space_used/1024/1024/1024 AS "Used (GB)", ROUND(CASE WHEN space_limit>0 THEN (space_used/space_limit)*100 ELSE 0 END,1) AS "Used %" FROM v$recovery_file_dest; SELECT log_mode AS "Archive Log Mode" FROM v$database; SELECT TO_CHAR(first_time,'DD-MON-YYYY HH24:MI:SS') AS "Last Archived Log Time" FROM (SELECT first_time FROM v$log_history ORDER BY first_time DESC) WHERE ROWNUM = 1; SELECT sequence#, applied, completion_time FROM v$archived_log WHERE ROWNUM <= 10 ORDER BY sequence# DESC; PROMPT

RMAN Backup Jobs (Last 7 Days)

SELECT TO_CHAR(start_time,'DD-MON HH24:MI') AS "Start", TO_CHAR(end_time,'DD-MON HH24:MI') AS "End", input_type, status, ROUND(elapsed_seconds/60,1) AS "Elapsed (min)" FROM v$rman_backup_job_details WHERE start_time > SYSDATE - 7 ORDER BY start_time DESC; PROMPT

Sessions / Blocking / Long Operations

SELECT username, status, COUNT(*) AS "Session Count" FROM v$session WHERE username IS NOT NULL GROUP BY username, status ORDER BY 1,2; PROMPT

Blocking Session Details

SELECT s.sid, s.serial#, s.username, s.status, s.event, s.machine, s.program, s.sql_id, s.blocking_session AS "Blocking SID" FROM gv$session s WHERE s.blocking_session IS NOT NULL ORDER BY s.sid; PROMPT

Long Operations In Progress

SELECT sid, serial#, opname, SOFAR, TOTALWORK, ROUND( (sofar/NULLIF(totalwork,0))*100,1 ) AS "Pct Done", TIME_REMAINING AS "Sec Remaining" FROM v$session_longops WHERE TIME_REMAINING > 0 ORDER BY TIME_REMAINING DESC; PROMPT

Wait Events And Wait Classes

SELECT * FROM ( SELECT event, total_waits, time_waited/100 AS "Time Waited (s)" FROM v$system_event WHERE wait_class <> 'Idle' ORDER BY time_waited DESC ) WHERE ROWNUM <= 10; SELECT wait_class AS "Wait Class", total_waits, time_waited/100 AS "Time Waited (s)" FROM v$system_wait_class WHERE wait_class <> 'Idle' ORDER BY time_waited DESC; PROMPT

Memory (SGA/PGA)

SELECT name, ROUND(value/1024/1024,2) AS "Size (MB)" FROM v$sga ORDER BY name; SELECT name, ROUND(value/1024/1024,2) AS "Size (MB)" FROM v$pgastat WHERE name IN ('total PGA inuse','total PGA allocated'); PROMPT

Temp And Undo Usage

SELECT tablespace_name AS "Temp TS", SUM(bytes_used/1024/1024) AS "Used (MB)", SUM(bytes_free/1024/1024) AS "Free (MB)" FROM V$temp_space_header GROUP BY tablespace_name; SELECT b.tablespace AS "Temp TS", ROUND(b.blocks * p.value / 1024 / 1024, 2) AS "Temp Used (MB)", a.sid || ',' || a.serial# AS "SID,Serial", a.username, a.program FROM v$sort_usage b JOIN v$session a ON a.saddr = b.session_addr JOIN v$parameter p ON p.name = 'db_block_size' ORDER BY 2 DESC; SELECT d.tablespace_name AS "UNDO TS", ROUND(SUM(CASE WHEN status='ACTIVE' THEN bytes ELSE 0 END)/1024/1024,1) AS "Active (MB)", ROUND(SUM(CASE WHEN status='UNEXPIRED' THEN bytes ELSE 0 END)/1024/1024,1) AS "Unexpired (MB)", ROUND(SUM(CASE WHEN status='EXPIRED' THEN bytes ELSE 0 END)/1024/1024,1) AS "Expired (MB)" FROM dba_undo_extents d GROUP BY d.tablespace_name; PROMPT

Objects, Stats And Jobs

SELECT owner, object_type, COUNT(*) AS "Invalid Count" FROM dba_objects WHERE status='INVALID' AND owner NOT IN ('SYS','SYSTEM') GROUP BY owner, object_type ORDER BY 3 DESC; -- Stale stats (12c+; if column exists) -- If this errors on older versions, comment out BEGIN NULL; END; / SELECT owner, table_name, num_rows, last_analyzed FROM dba_tab_statistics WHERE stale_stats = 'YES' AND owner NOT IN ('SYS','SYSTEM') AND ROWNUM <= 100 ORDER BY owner, table_name; PROMPT

Security And Accounts

SELECT username, account_status, lock_date, expiry_date, profile FROM dba_users WHERE account_status <> 'OPEN' ORDER BY username; PROMPT

Parameters Changed From Defaults

SELECT name, value, issys_modifiable, isdefault FROM v$parameter WHERE isdefault = 'FALSE' ORDER BY name; PROMPT

I/O Hot Datafiles And Redo Switches

SELECT * FROM ( SELECT b.name AS "Datafile", a.phyrds, a.phywrts, a.readtim, a.writetim FROM v$filestat a JOIN v$datafile b ON a.file# = b.file# ORDER BY a.readtim + a.writetim DESC ) WHERE ROWNUM <= 10; SELECT TO_CHAR(first_time,'YYYY-MM-DD HH24') AS "Hour", COUNT(*) AS "Log Switches" FROM v$log_history WHERE first_time > SYSDATE - 1 GROUP BY TO_CHAR(first_time,'YYYY-MM-DD HH24') ORDER BY 1 DESC; PROMPT

Data Guard Destinations (if configured)

-- Primary, version-agnostic view: use TYPE (not TARGET) from V$ARCHIVE_DEST_STATUS SELECT s.dest_id, s.type, -- LOCAL / PHYSICAL / LOGICAL s.status, -- VALID / INACTIVE / ERROR s.error, s.destination, s.archived_thread#, s.archived_seq# FROM v$archive_dest_status s WHERE s.status IS NOT NULL ORDER BY s.dest_id; PROMPT SPOOL OFF SET MARKUP HTML OFF SET TERMOUT ON