Sunday, October 19, 2025

test

SET SERVEROUTPUT ON SIZE UNLIMITED
WHENEVER SQLERROR EXIT FAILURE

DECLARE
    -- Configuration variables
    c_batch_size        CONSTANT NUMBER := 40;  
    c_job_prefix        CONSTANT VARCHAR2(15) := 'BATCH_COUNT_';

    -- Runtime Variables
    v_total_source_count NUMBER;
    v_max_batches_needed NUMBER; -- 🌟 FIX: Holds the actual highest partition ID
    v_job_name           VARCHAR2(128);
    v_ddl_statement      VARCHAR2(4000);
    v_total_submitted    NUMBER := 0;
    v_pending_in_batch   NUMBER; 

    -- Helper procedure to execute DDL safely (defined to avoid compile errors on missing objects)
    PROCEDURE execute_ddl(p_sql IN VARCHAR2) IS
    BEGIN
        EXECUTE IMMEDIATE p_sql;
    EXCEPTION
        WHEN OTHERS THEN
            -- Ignore common errors (including ORA-27475: unknown job)
            IF SQLCODE NOT IN (-942, -4043, -27475) THEN
                RAISE; 
            END IF;
    END;
    
    -- Helper function to check for object existence
    FUNCTION object_exists(p_object_name IN VARCHAR2) RETURN BOOLEAN IS
        v_exists NUMBER;
    BEGIN
        SELECT COUNT(*) INTO v_exists FROM user_objects WHERE object_name = UPPER(p_object_name);
        RETURN v_exists > 0;
    END;
    
    -- P1. Initializes DDL and Structural Integrity
    PROCEDURE initialize_system IS
    BEGIN
        DBMS_OUTPUT.PUT_LINE('--- PHASES 0 & 1: PERFORMING DESTRUCTIVE FIRST-TIME SETUP ---');
        
        -- Cleanup Phase
        execute_ddl('DROP VIEW row_count_summary');
        execute_ddl('DROP TABLE row_count_control CASCADE CONSTRAINTS');
        execute_ddl('DROP TABLE row_counts_scheduler_log CASCADE CONSTRAINTS'); 
        execute_ddl('DROP TABLE job_staging_gtt');

        -- Creation Phase (Omitting DDL body for brevity, assume correct)
        EXECUTE IMMEDIATE '
            CREATE TABLE row_count_control (
                schema_name VARCHAR2(128) NOT NULL, table_name VARCHAR2(128) NOT NULL,
                job_partition_id NUMBER NOT NULL, status VARCHAR2(10) DEFAULT ''PENDING'' NOT NULL,
                start_time TIMESTAMP, end_time TIMESTAMP, error_message VARCHAR2(4000),
                CONSTRAINT pk_control PRIMARY KEY (schema_name, table_name)
            )';

        EXECUTE IMMEDIATE '
            CREATE TABLE row_counts_scheduler_log (
                schema_name VARCHAR2(128) NOT NULL, table_name VARCHAR2(128) NOT NULL,
                row_count NUMBER, count_timestamp TIMESTAMP, job_name VARCHAR2(128),
                batch_id NUMBER, error_message VARCHAR2(4000),
                CONSTRAINT pk_final_log PRIMARY KEY (schema_name, table_name)
            )';

        EXECUTE IMMEDIATE '
            CREATE GLOBAL TEMPORARY TABLE job_staging_gtt (
                owner VARCHAR2(128), table_name VARCHAR2(128)
            ) ON COMMIT PRESERVE ROWS';
            
        -- Create Summary View
        v_ddl_statement := '
            CREATE OR REPLACE VIEW row_count_summary AS
            SELECT 
                COUNT(*) AS total_tasks, SUM(CASE WHEN status = ''COMPLETE'' THEN 1 ELSE 0 END) AS success_count,
                SUM(CASE WHEN status = ''FAILED'' THEN 1 ELSE 0 END) AS failed_count, SUM(CASE WHEN status = ''RUNNING'' THEN 1 ELSE 0 END) AS running_count,
                ROUND(SUM(CASE WHEN status = ''COMPLETE'' THEN 1 ELSE 0 END) / GREATEST(COUNT(*),1) * 100, 1) AS pct_complete,
                MAX(end_time) AS last_completion 
            FROM row_count_control';
        EXECUTE IMMEDIATE v_ddl_statement;
        
        COMMIT;
        DBMS_OUTPUT.PUT_LINE(' -> SYSTEM DDL SETUP COMPLETE.');
    END initialize_system;

    -- P2. Populates the Master Control Table using NTILE
    PROCEDURE prepare_workload IS
        v_total_count NUMBER;
        v_ddl_insert VARCHAR2(4000);
    BEGIN
        SELECT COUNT(*) INTO v_total_count 
        FROM all_tables WHERE owner IN ('HR','SALES','FINANCE') AND table_name NOT LIKE 'BIN$%';

        v_batches_needed := CEIL(v_total_count / c_batch_size);

        -- CRITICAL: MERGE is used to add only new tables, preserving existing statuses
        v_ddl_insert := '
            MERGE INTO row_count_control c
            USING (
                SELECT owner AS schema_name, table_name,
                        NTILE(:num_batches) OVER (ORDER BY owner, table_name) AS job_partition_id
                FROM all_tables
                WHERE owner IN (''HR'',''SALES'',''FINANCE'') AND table_name NOT LIKE ''BIN$%''
            ) s
            ON (c.schema_name = s.schema_name AND c.table_name = s.table_name)
            WHEN NOT MATCHED THEN
                INSERT (schema_name, table_name, job_partition_id, status)
                VALUES (s.schema_name, s.table_name, s.job_partition_id, ''PENDING'')';
                
        EXECUTE IMMEDIATE v_ddl_insert USING v_batches_needed;
        COMMIT;
        DBMS_OUTPUT.PUT_LINE(' -> WORKLOAD PREPARED. ' || SQL%ROWCOUNT || ' new tasks added/checked. Total partitions: ' || v_batches_needed);
    END prepare_workload;
    
    -- P3. Clears abandoned 'RUNNING' tasks
    PROCEDURE reset_stalled_tasks IS
        v_stale_threshold_minutes CONSTANT NUMBER := 60; 
    BEGIN
        UPDATE row_count_control
        SET status = 'PENDING', start_time = NULL, end_time = NULL, error_message = 'RESET: Task was forcefully recycled from RUNNING state.'
        WHERE status = 'RUNNING'
          AND start_time < SYSTIMESTAMP - INTERVAL '1' MINUTE * v_stale_threshold_minutes;
        COMMIT;
    END reset_stalled_tasks;
    
    -- P4. Creates the core counting procedure
    PROCEDURE create_counting_procedure IS
    BEGIN
        v_ddl_statement := '
            CREATE OR REPLACE PROCEDURE count_table_batch(p_batch_id NUMBER) AS
                v_sql_staging_filter VARCHAR2(4000); v_sql_count VARCHAR2(200); v_cnt NUMBER;
                v_job_name_log CONSTANT VARCHAR2(30) := ''BATCH_'' || p_batch_id; v_error_msg VARCHAR2(4000); 
                CURSOR c_staged_tables IS SELECT owner, table_name FROM job_staging_gtt;
            BEGIN
                -- Phase 1: STAGE WORKLOAD (Isolation and Resumption)
                EXECUTE IMMEDIATE ''TRUNCATE TABLE job_staging_gtt'';
                v_sql_staging_filter := ''
                    INSERT INTO job_staging_gtt (owner, table_name)
                    SELECT schema_name, table_name
                    FROM row_count_control
                    WHERE job_partition_id = :p_batch_id AND status IN (''''PENDING'''', ''''FAILED'''')
                    '';
                EXECUTE IMMEDIATE v_sql_staging_filter USING p_batch_id;
                COMMIT; 

                -- Phase 2: PROCESS STAGED WORKLOAD
                FOR r IN c_staged_tables LOOP
                    BEGIN 
                        -- Update status to RUNNING immediately (for stall detection)
                        UPDATE row_count_control SET status = ''RUNNING'', start_time = SYSTIMESTAMP
                        WHERE schema_name = r.owner AND table_name = r.table_name;
                        COMMIT; 

                        v_sql_count := ''SELECT COUNT(*) FROM "''||r.owner||''"."''||r.table_name||''"'';
                        EXECUTE IMMEDIATE v_sql_count INTO v_cnt;
                        
                        -- LOG SUCCESS and Mark COMPLETE
                        DELETE FROM row_counts_scheduler_log WHERE schema_name = r.owner AND table_name = r.table_name;
                        INSERT INTO row_counts_scheduler_log (schema_name, table_name, row_count, count_timestamp, job_name, batch_id, error_message)
                        VALUES (r.owner, r.table_name, v_cnt, SYSTIMESTAMP, v_job_name_log, p_batch_id, NULL);

                        UPDATE row_count_control SET status = ''COMPLETE'', end_time = SYSTIMESTAMP, error_message = NULL
                        WHERE schema_name = r.owner AND table_name = r.table_name;
                        COMMIT; 

                    EXCEPTION 
                        WHEN OTHERS THEN 
                            v_error_msg := SUBSTR(SQLERRM, 1, 4000);
                            -- Mark task as FAILED (prevents automatic retries until DBA intervenes)
                            UPDATE row_count_control SET status = ''FAILED'', end_time = SYSTIMESTAMP, error_message = v_error_msg
                            WHERE schema_name = r.owner AND table_name = r.table_name;
                            INSERT INTO row_counts_scheduler_log (schema_name, table_name, row_count, count_timestamp, job_name, batch_id, error_message)
                            VALUES (r.owner, r.table_name, -1, SYSTIMESTAMP, v_job_name_log, p_batch_id, v_error_msg);
                            COMMIT; 
                    END;
                END LOOP;
            END;
            ';
        EXECUTE IMMEDIATE v_ddl_statement;
        DBMS_OUTPUT.PUT_LINE(' -> Counting Procedure created.');
    END create_counting_procedure;


BEGIN
    --------------------------------------------------------------------------------------------------
    -- 0. INITIAL LAUNCH OR RESUME CHECK
    --------------------------------------------------------------------------------------------------
    IF NOT object_exists('ROW_COUNT_CONTROL') THEN
        DBMS_OUTPUT.PUT_LINE('--- PHASE 1: FULL SYSTEM INITIALIZATION (FIRST RUN) ---');
        initialize_system; 
        prepare_workload; -- Populates the queue for the very first time
    ELSE
        DBMS_OUTPUT.PUT_LINE('--- PHASE 1: SYSTEM IS LIVE. INITIATING RESTART/RESUME PROTOCOL ---');
        prepare_workload; -- Check for new schemas/tables added since last run
    END IF;
    
    create_counting_procedure; -- Ensure procedure is compiled with latest logic

    --------------------------------------------------------------------------------------------------
    -- 1. EXECUTION AND RESTART PROTOCOL
    --------------------------------------------------------------------------------------------------
    
    DBMS_OUTPUT.PUT_LINE('PHASE 2: EXECUTING RESTART PROTOCOL');
    
    -- Step A: Clear abandoned locks and recycle tasks
    reset_stalled_tasks; 

    -- 🌟 CRITICAL FIX: Get final batch count from the actual MAX job partition ID 🌟
    SELECT MAX(job_partition_id) INTO v_batches_needed
    FROM row_count_control;
    
    -- Safety check for the case where the control table is empty
    IF v_batches_needed IS NULL THEN
        DBMS_OUTPUT.PUT_LINE('ERROR: No tables found in the control queue. Cannot proceed.');
        RETURN;
    END IF;

    -- Step B: Launch/Relaunch Execution
    DBMS_OUTPUT.PUT_LINE('Launching up to ' || v_batches_needed || ' parallel jobs...');

    FOR i IN 1..v_batches_needed LOOP
        v_job_name := c_job_prefix || LPAD(i, 2, '0');
        
        -- Check if this batch has any PENDING or FAILED work before launching (Optimization)
        SELECT COUNT(*) INTO v_pending_in_batch
        FROM row_count_control
        WHERE job_partition_id = i AND status IN ('PENDING', 'FAILED');

        IF v_pending_in_batch > 0 THEN
            -- Cleanup old job definition (Avoids ORA-27475 crash)
            execute_ddl('BEGIN DBMS_SCHEDULER.DROP_JOB(''' || v_job_name || ''', TRUE); END;'); 

            -- Create, Set Argument, and Enable NEW JOB
            DBMS_SCHEDULER.CREATE_JOB (
                job_name => v_job_name, job_type => 'STORED_PROCEDURE', job_action => 'COUNT_TABLE_BATCH',
                number_of_arguments => 1, start_date => SYSTIMESTAMP, repeat_interval => NULL, enabled => FALSE 
            );
            
            -- Set argument (using TO_CHAR to avoid PLS-00307 ambiguity)
            DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE(v_job_name, 1, TO_CHAR(i));
            DBMS_SCHEDULER.ENABLE(v_job_name);
            
            v_total_submitted := v_total_submitted + 1;
            DBMS_OUTPUT.PUT_LINE('  -> Submitted Job ' || v_job_name || ' (' || v_pending_in_batch || ' tasks pending/failed)');
        END IF;
    END LOOP;
    
    COMMIT;
    DBMS_OUTPUT.PUT_LINE('--- FINAL SUCCESS: ALL ' || v_total_submitted || ' jobs successfully launched/resumed. ---');

EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('!!! FATAL ERROR IN MASTER BLOCK !!! SQLERRM: ' || SQLERRM);
        ROLLBACK;
        RAISE;
END;
/

====================================================================

UPDATE row_count_control
SET 
    status = 'PENDING',
    start_time = NULL,
    end_time = NULL,
    error_message = 'RETRY FORCED by DBA. Originally: ' || NVL(error_message, 'Unknown.')
WHERE 
    -- Target tasks that were abandoned (stuck in RUNNING for > 1 hour)
    (status = 'RUNNING' AND start_time < SYSTIMESTAMP - INTERVAL '60' MINUTE)
    -- OR Target tasks that previously failed but are now ready for retry
    OR status = 'FAILED';

COMMIT;

Final Production Row Count System (Single Executable)

/*

This script is structured as an anonymous block that calls internal procedures to manage the entire lifecycle.

Usage Instructions

  1. Replace Schemas: Update the owner IN ('HR', 'SALES', 'FINANCE') list with your actual schema names in the DDL procedure definitions.

  2. Execute the Entire Script: Run the entire code block once.

    • The first time, it performs the full setup.

    • For subsequent runs, it skips the initial setup and goes straight to the Submission/Restart phase.

      */

 



SET SERVEROUTPUT ON SIZE UNLIMITED
WHENEVER SQLERROR EXIT FAILURE

DECLARE
    -- Configuration variables
    c_batch_size        CONSTANT NUMBER := 40;
    c_job_prefix        CONSTANT VARCHAR2(15) := 'BATCH_COUNT_';

    -- Runtime Variables
    v_total_source_count NUMBER;
    v_batches_needed     NUMBER;
    v_job_name           VARCHAR2(128);
    v_ddl_statement      VARCHAR2(4000);
    v_total_submitted    NUMBER := 0;

    -- Helper procedure to safely attempt DDL destruction (IGNORES 'DOES NOT EXIST')
    PROCEDURE safe_execute_drop(p_sql IN VARCHAR2) IS
    BEGIN
        EXECUTE IMMEDIATE p_sql;
    EXCEPTION
        WHEN OTHERS THEN
            -- ORA-00942 (table/view does not exist)
            -- ORA-04043 (object does not exist)
            IF SQLCODE NOT IN (-942, -4043) THEN 
                RAISE; 
            END IF;
    END safe_execute_drop;

    -- P1. Initializes DDL and Structural Integrity (Indestructible Setup)
    PROCEDURE initialize_system IS
    BEGIN
        DBMS_OUTPUT.PUT_LINE('--- PHASES 0 & 1: FULL SYSTEM INITIALIZATION ---');
        
        -- 1. INDESTRUCTIBLE CLEANUP PHASE: Attempt to drop everything, ignore errors
        safe_execute_drop('DROP VIEW row_count_summary');
        safe_execute_drop('DROP TABLE row_count_control CASCADE CONSTRAINTS');
        safe_execute_drop('DROP TABLE row_counts_scheduler_log CASCADE CONSTRAINTS'); 
        safe_execute_drop('DROP TABLE job_staging_gtt');

        -- 2. Creation Phase (Must succeed now that dependencies are gone)
        EXECUTE IMMEDIATE '
            CREATE TABLE row_count_control (
                schema_name VARCHAR2(128) NOT NULL, table_name VARCHAR2(128) NOT NULL,
                job_partition_id NUMBER NOT NULL, status VARCHAR2(10) DEFAULT ''PENDING'' NOT NULL,
                start_time TIMESTAMP, end_time TIMESTAMP, error_message VARCHAR2(4000),
                CONSTRAINT pk_control PRIMARY KEY (schema_name, table_name)
            )';

        EXECUTE IMMEDIATE '
            CREATE TABLE row_counts_scheduler_log (
                schema_name VARCHAR2(128) NOT NULL, table_name VARCHAR2(128) NOT NULL,
                row_count NUMBER, count_timestamp TIMESTAMP, job_name VARCHAR2(128),
                batch_id NUMBER, error_message VARCHAR2(4000),
                CONSTRAINT pk_final_log PRIMARY KEY (schema_name, table_name)
            )';

        EXECUTE IMMEDIATE '
            CREATE GLOBAL TEMPORARY TABLE job_staging_gtt (
                owner VARCHAR2(128), table_name VARCHAR2(128)
            ) ON COMMIT PRESERVE ROWS';
            
        -- Create Summary View
        v_ddl_statement := '
            CREATE OR REPLACE VIEW row_count_summary AS
            SELECT 
                COUNT(*) AS total_tasks, SUM(CASE WHEN status = ''COMPLETE'' THEN 1 ELSE 0 END) AS success_count,
                SUM(CASE WHEN status = ''FAILED'' THEN 1 ELSE 0 END) AS failed_count, SUM(CASE WHEN status = ''RUNNING'' THEN 1 ELSE 0 END) AS running_count,
                ROUND(SUM(CASE WHEN status = ''COMPLETE'' THEN 1 ELSE 0 END) / GREATEST(COUNT(*),1) * 100, 1) AS pct_complete,
                MAX(end_time) AS last_completion 
            FROM row_count_control';
        EXECUTE IMMEDIATE v_ddl_statement;
        
        COMMIT;
        DBMS_OUTPUT.PUT_LINE('  -> SYSTEM DDL SETUP COMPLETE.');
    END initialize_system;

    -- P2. Populates the Master Control Table using NTILE
    PROCEDURE prepare_workload IS
        v_total_count NUMBER;
        v_ddl_insert VARCHAR2(4000);
    BEGIN
        SELECT COUNT(*) INTO v_total_count 
        FROM all_tables WHERE owner IN ('HR','SALES','FINANCE') AND table_name NOT LIKE 'BIN$%';

        v_batches_needed := CEIL(v_total_count / c_batch_size);

        DELETE FROM row_count_control; -- Clear old work data
        
        v_ddl_insert := '
            INSERT INTO row_count_control (schema_name, table_name, job_partition_id)
            SELECT
                owner, table_name,
                NTILE(:num_batches) OVER (ORDER BY owner, table_name) AS job_partition_id
            FROM
                all_tables
            WHERE
                owner IN (''HR'',''SALES'',''FINANCE'') AND table_name NOT LIKE ''BIN$%''';
                
        EXECUTE IMMEDIATE v_ddl_insert USING v_batches_needed;
        COMMIT;
        DBMS_OUTPUT.PUT_LINE('  -> WORKLOAD PREPARED. ' || v_total_count || ' tasks partitioned into ' || v_batches_needed || ' batches.');
    END prepare_workload;
    
    -- P3. Clears abandoned 'RUNNING' tasks
    PROCEDURE reset_stalled_tasks IS
        v_stale_threshold_minutes CONSTANT NUMBER := 60; 
    BEGIN
        -- Resets stalled tasks to PENDING for recycling
        UPDATE row_count_control
        SET status = 'PENDING', start_time = NULL, end_time = NULL, error_message = 'RESET: Task was forcefully recycled from RUNNING state.'
        WHERE status = 'RUNNING'
          AND start_time < SYSTIMESTAMP - INTERVAL '1' MINUTE * v_stale_threshold_minutes;
        COMMIT;
    END reset_stalled_tasks;
    
    -- P4. Creates the core counting procedure (The Engine)
    PROCEDURE create_counting_procedure IS
    BEGIN
        v_ddl_statement := '
            CREATE OR REPLACE PROCEDURE count_table_batch(p_batch_id NUMBER) AS
                v_sql_staging_filter VARCHAR2(4000); v_sql_count VARCHAR2(200); v_cnt NUMBER;
                v_job_name_log CONSTANT VARCHAR2(30) := ''BATCH_'' || p_batch_id; v_error_msg VARCHAR2(4000); 
                CURSOR c_staged_tables IS SELECT owner, table_name FROM job_staging_gtt;
            BEGIN
                -- Phase 1: STAGE WORKLOAD 
                EXECUTE IMMEDIATE ''TRUNCATE TABLE job_staging_gtt'';
                v_sql_staging_filter := ''
                    INSERT INTO job_staging_gtt (owner, table_name)
                    SELECT schema_name, table_name
                    FROM row_count_control
                    WHERE job_partition_id = :p_batch_id AND status IN (''''PENDING'''', ''''FAILED'''')
                    '';
                EXECUTE IMMEDIATE v_sql_staging_filter USING p_batch_id; -- Includes PENDING and FAILED for retry
                COMMIT; 

                -- Phase 2: PROCESS STAGED WORKLOAD
                FOR r IN c_staged_tables LOOP
                    BEGIN 
                        -- Update status to RUNNING immediately 
                        UPDATE row_count_control SET status = ''RUNNING'', start_time = SYSTIMESTAMP
                        WHERE schema_name = r.owner AND table_name = r.table_name;
                        COMMIT; 

                        v_sql_count := ''SELECT COUNT(*) FROM "''||r.owner||''"."''||r.table_name||''"'';
                        EXECUTE IMMEDIATE v_sql_count INTO v_cnt;
                        
                        -- LOG SUCCESS and Mark COMPLETE
                        DELETE FROM row_counts_scheduler_log WHERE schema_name = r.owner AND table_name = r.table_name;
                        INSERT INTO row_counts_scheduler_log (schema_name, table_name, row_count, count_timestamp, job_name, batch_id, error_message)
                        VALUES (r.owner, r.table_name, v_cnt, SYSTIMESTAMP, v_job_name_log, p_batch_id, NULL);

                        UPDATE row_count_control SET status = ''COMPLETE'', end_time = SYSTIMESTAMP, error_message = NULL
                        WHERE schema_name = r.owner AND table_name = r.table_name;
                        COMMIT; 

                    EXCEPTION 
                        WHEN OTHERS THEN 
                            v_error_msg := SUBSTR(SQLERRM, 1, 4000);
                            -- Mark task as FAILED (prevents automatic retries until DBA intervenes)
                            UPDATE row_count_control SET status = ''FAILED'', end_time = SYSTIMESTAMP, error_message = v_error_msg
                            WHERE schema_name = r.owner AND table_name = r.table_name;
                            INSERT INTO row_counts_scheduler_log (schema_name, table_name, row_count, count_timestamp, job_name, batch_id, error_message)
                            VALUES (r.owner, r.table_name, -1, SYSTIMESTAMP, v_job_name_log, p_batch_id, v_error_msg);
                            COMMIT; 
                    END;
                END LOOP;
            END;
            ';
        EXECUTE IMMEDIATE v_ddl_statement;
    END create_counting_procedure;


BEGIN
    --------------------------------------------------------------------------------------------------
    -- 0. INITIAL LAUNCH OR RESUME CHECK
    --------------------------------------------------------------------------------------------------
    IF NOT object_exists('ROW_COUNT_CONTROL') THEN
        initialize_system; 
        prepare_workload; -- Populates the queue for the very first time
    END IF;
    
    create_counting_procedure; -- Ensure procedure is compiled with latest logic

    --------------------------------------------------------------------------------------------------
    -- 1. EXECUTION AND RESTART PROTOCOL
    --------------------------------------------------------------------------------------------------
    
    DBMS_OUTPUT.PUT_LINE('PHASE 2: EXECUTING RESTART PROTOCOL');
    
    -- Step A: Clear abandoned locks and recycle tasks
    reset_stalled_tasks; 

    -- Get final batch count (always based on the total control table size)
    SELECT CEIL(COUNT(*) / c_batch_size) INTO v_batches_needed
    FROM row_count_control;
    
    -- Step B: Launch/Relaunch Execution
    DBMS_OUTPUT.PUT_LINE('Launching ' || v_batches_needed || ' parallel jobs...');

    FOR i IN 1..v_batches_needed LOOP
        v_job_name := c_job_prefix || LPAD(i, 2, '0');
        
        -- Cleanup old job definition (Always drop before recreating for safety)
        execute_ddl('BEGIN DBMS_SCHEDULER.DROP_JOB(''' || v_job_name || ''', TRUE); END;'); 

        -- Create, Set Argument, and Enable NEW JOB
        DBMS_SCHEDULER.CREATE_JOB (
            job_name => v_job_name, job_type => 'STORED_PROCEDURE', job_action => 'COUNT_TABLE_BATCH',
            number_of_arguments => 1, start_date => SYSTIMESTAMP, repeat_interval => NULL, enabled => FALSE 
        );
        
        DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE(v_job_name, 1, TO_CHAR(i));
        DBMS_SCHEDULER.ENABLE(v_job_name);
        
        v_total_submitted := v_total_submitted + 1;
    END LOOP;
    
    COMMIT;
    DBMS_OUTPUT.PUT_LINE('--- ALL ' || v_total_submitted || ' jobs successfully launched/resumed. ---');

EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('!!! FATAL ERROR IN MASTER BLOCK !!! SQLERRM: ' || SQLERRM);
        ROLLBACK;
        RAISE;
END;
/

A. Query to Identify Targets

First, use a query to view all current failures:

SQL
SELECT
    schema_name,
    table_name,
    error_message
FROM
    row_count_control
WHERE
    status = 'FAILED'
ORDER BY
    error_message;

B. The Update Command (Retry Action)

You have two options for the update: retry all failed tasks or retry a specific task.

Option 1: Retry ALL Failed Tasks (Recommended after a global fix)

SQL
UPDATE row_count_control
SET 
    status = 'PENDING',
    error_message = 'RETRY PENDING - Root cause fixed.'
WHERE 
    status = 'FAILED';

COMMIT;
DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT || ' FAILED tasks have been reset to PENDING.');

Option 2: Retry a Specific Table (Recommended for testing the fix)

SQL
UPDATE row_count_control
SET 
    status = 'PENDING',
    error_message = 'RETRY PENDING - Permission issue resolved.'
WHERE 
    status = 'FAILED'
    AND schema_name = 'FINANCE'
    AND table_name = 'LEDGER_BALANCE_BIG';

COMMIT;
DBMS_OUTPUT.PUT_LINE('Specific table set to PENDING.');
====================

This step clears any tasks abandoned by jobs that crashed but left their status as 'RUNNING'.

ActionCommand/QueryPurpose
Recycle Stalled TasksEXEC reset_stalled_tasks;Forces any job stuck in the RUNNING state for over 60 minutes back to the PENDING queue for recycling.

RESET_STALLED_TASKS Procedure


CREATE OR REPLACE PROCEDURE reset_stalled_tasks AS
    -- Defines the threshold for marking a task as "stalled" (e.g., inactive for 60 minutes).
    v_stale_threshold_minutes CONSTANT NUMBER := 60; 
    v_rows_reset NUMBER;
BEGIN
    DBMS_OUTPUT.PUT_LINE('--- Initiating Stalled Task Cleanup ---');
    
    UPDATE row_count_control
    SET status = 'PENDING', -- Recycles the abandoned task back into the work queue
        start_time = NULL,
        end_time = NULL,
        error_message = 'RESET: Task was forcefully recycled from RUNNING state.'
    WHERE status = 'RUNNING'
      -- Identify tasks where the start_time is older than the threshold
      AND start_time < SYSTIMESTAMP - INTERVAL '1' MINUTE * v_stale_threshold_minutes;
      
    v_rows_reset := SQL%ROWCOUNT;
    
    IF v_rows_reset > 0 THEN
        DBMS_OUTPUT.PUT_LINE('!!! WARNING: ' || v_rows_reset || ' stalled tasks were reset to PENDING and are ready for retry.');
    ELSE
        DBMS_OUTPUT.PUT_LINE('No stalled tasks found.');
    END IF;
    
    COMMIT;
    
EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('FATAL ERROR during stalled task reset: ' || SQLERRM);
        -- Rollback should only affect this small transaction, but we include it for safety
        ROLLBACK; 
        RAISE;
END;
/

==========

CREATE OR REPLACE VIEW row_count_summary AS
SELECT
    -- Total tasks submitted to the work queue
    COUNT(*) AS total_tasks,
    
    -- Status Counts
    SUM(CASE WHEN status = 'COMPLETE' THEN 1 ELSE 0 END) AS successful_count,
    SUM(CASE WHEN status = 'FAILED' THEN 1 ELSE 0 END) AS failed_count,
    SUM(CASE WHEN status = 'RUNNING' THEN 1 ELSE 0 END) AS running_count,
    SUM(CASE WHEN status = 'PENDING' THEN 1 ELSE 0 END) AS pending_count,
    
    -- Progress Metrics
    -- Calculate percentage complete based on successful tasks vs. total tasks
    ROUND(
        SUM(CASE WHEN status = 'COMPLETE' THEN 1 ELSE 0 END) / GREATEST(COUNT(*), 1) * 100, 
        1
    ) AS pct_complete,
    
    -- Timestamp of the last successful activity
    MAX(end_time) AS last_completion_time 
FROM
    row_count_control;


SELECT
    'TOTAL WORKLOAD STATUS' AS metric_group,
    total_tasks,
    successful_count,
    failed_count,
    running_count,
    pending_count,
    pct_complete,
    TO_CHAR(last_completion_time, 'YYYY-MM-DD HH24:MI:SS') AS last_update
FROM
    row_count_summary;


SELECT
    schema_name,
    COUNT(*) AS total_tables,
    SUM(CASE WHEN status = 'COMPLETE' THEN 1 ELSE 0 END) AS done_count,
    SUM(CASE WHEN status = 'FAILED' THEN 1 ELSE 0 END) AS failed_count,
    SUM(CASE WHEN status = 'PENDING' THEN 1 ELSE 0 END) AS pending_count,
    ROUND(SUM(CASE WHEN status = 'COMPLETE' THEN 1 ELSE 0 END) / COUNT(*) * 100, 1) AS pct_done
FROM
    row_count_control
GROUP BY
    schema_name
ORDER BY
    pct_done, schema_name;



SELECT
    c.schema_name,
    c.table_name,
    c.status AS control_status,
    c.job_partition_id AS current_batch,
    TO_CHAR(l.row_count, '999,999,999,999') AS final_row_count,
    TO_CHAR(l.count_timestamp, 'YYYY-MM-DD HH24:MI:SS') AS count_time
FROM
    row_count_control c
LEFT JOIN
    row_counts_scheduler_log l ON c.schema_name = l.schema_name AND c.table_name = l.table_name
WHERE
    -- Example: Check a specific table, or leave commented for full list
    c.table_name = 'YOUR_SPECIFIC_TABLE_NAME' 
    -- OR
    -- c.status IN ('RUNNING', 'PENDING')
ORDER BY
    c.status DESC, c.schema_name, c.table_name;


To query the row count for a single table using the data you've collected, you must query the row_counts_scheduler_logtable directly.

Here is the most efficient and accurate query:

SQL
SELECT
    schema_name,
    table_name,
    TO_CHAR(row_count, '999,999,999,999') AS final_row_count,
    TO_CHAR(count_timestamp, 'YYYY-MM-DD HH24:MI:SS') AS count_time
FROM
    row_counts_scheduler_log
WHERE
    schema_name = 'YOUR_SCHEMA_NAME'  -- 👈 Replace with the target schema (e.g., 'HR')
    AND table_name = 'YOUR_TABLE_NAME' -- 👈 Replace with the target table name
    AND row_count >= 0 -- Ensure you only get the successful count
ORDER BY
    count_timestamp DESC
FETCH NEXT 1 ROW ONLY;

using ole

SET SERVEROUTPUT ON SIZE UNLIMITED

DECLARE
    -- Configuration Constants
    c_batch_size        CONSTANT NUMBER := 50;  -- Final safe batch size
    c_job_prefix        CONSTANT VARCHAR2(15) := 'BATCH_COUNT_';

    -- Runtime Variables
    v_total_source_count NUMBER;
    v_batches_needed     NUMBER;
    v_job_name           VARCHAR2(128);
    v_ddl_statement      VARCHAR2(4000);
    
    -- Helper procedure to execute DDL safely
    PROCEDURE execute_ddl(p_sql IN VARCHAR2) IS
    BEGIN
        EXECUTE IMMEDIATE p_sql;
    EXCEPTION
        WHEN OTHERS THEN
            -- Ignore "object does not exist" errors during cleanup
            IF SQLCODE NOT IN (-942, -2443, -1418, -2289, -1432, -27475) THEN
                RAISE;
            END IF;
    END;

BEGIN
    DBMS_OUTPUT.PUT_LINE('===================================================================');
    DBMS_OUTPUT.PUT_LINE('PHASE 1: SYSTEM SETUP AND WORK QUEUE INITIALIZATION');
    DBMS_OUTPUT.PUT_LINE('===================================================================');

    --------------------------------------------------------------------------------------
    -- A. CLEANUP AND DDL SETUP
    --------------------------------------------------------------------------------------
    execute_ddl('DROP VIEW row_count_summary');
    execute_ddl('DROP TABLE row_count_control CASCADE CONSTRAINTS');
    execute_ddl('DROP TABLE row_counts_scheduler_log CASCADE CONSTRAINTS'); 
    execute_ddl('DROP TABLE job_staging_gtt');

    -- Create Master Control Table (Queue)
    EXECUTE IMMEDIATE '
        CREATE TABLE row_count_control (
            schema_name VARCHAR2(128) NOT NULL,
            table_name VARCHAR2(128) NOT NULL,
            job_partition_id NUMBER NOT NULL, 
            status VARCHAR2(10) DEFAULT ''PENDING'' NOT NULL, 
            start_time TIMESTAMP, end_time TIMESTAMP, error_message VARCHAR2(4000),
            CONSTRAINT pk_control PRIMARY KEY (schema_name, table_name)
        )';
    -- Create Permanent Log Table (Result History)
    EXECUTE IMMEDIATE '
        CREATE TABLE row_counts_scheduler_log (
            schema_name VARCHAR2(128) NOT NULL,
            table_name VARCHAR2(128) NOT NULL,
            row_count NUMBER, count_timestamp TIMESTAMP, job_name VARCHAR2(128),
            batch_id NUMBER, error_message VARCHAR2(4000),
            CONSTRAINT pk_final_log PRIMARY KEY (schema_name, table_name)
        )';
    -- Create Global Temporary Table (Private Session Staging)
    EXECUTE IMMEDIATE '
        CREATE GLOBAL TEMPORARY TABLE job_staging_gtt (
            owner VARCHAR2(128), table_name VARCHAR2(128)
        ) ON COMMIT PRESERVE ROWS';
    DBMS_OUTPUT.PUT_LINE('Base tables and GTT created.');

    --------------------------------------------------------------------------------------
    -- B. WORKLOAD POPULATION (NTILE - The Load Balancing Fix)
    --------------------------------------------------------------------------------------
    -- 1. Calculate total tables and batches needed
    SELECT COUNT(*) INTO v_total_source_count 
    FROM all_tables WHERE owner IN ('HR','SALES','FINANCE') AND table_name NOT LIKE 'BIN$%';
    
    v_batches_needed := CEIL(v_total_source_count / c_batch_size);

    -- 2. Insert all tables using NTILE for partitioning
    v_ddl_statement := '
        INSERT INTO row_count_control (schema_name, table_name, job_partition_id)
        SELECT
            owner,
            table_name,
            -- NTILE divides the tables into V_BATCHES_NEEDED groups (1 to N)
            NTILE(:num_batches) OVER (ORDER BY owner, table_name) AS job_partition_id
        FROM
            all_tables
        WHERE
            owner IN (''HR'',''SALES'',''FINANCE'') AND table_name NOT LIKE ''BIN$%''';
            
    EXECUTE IMMEDIATE v_ddl_statement USING v_batches_needed;
    COMMIT;
    DBMS_OUTPUT.PUT_LINE('Workload partitioned into ' || v_batches_needed || ' batches using NTILE.');

    --------------------------------------------------------------------------------------
    -- C. CREATE THE CORE PROCEDURE (Job Engine)
    --------------------------------------------------------------------------------------
    v_ddl_statement := '
        CREATE OR REPLACE PROCEDURE count_table_batch(p_batch_id NUMBER) AS
            v_sql_staging_filter VARCHAR2(4000); v_sql_count VARCHAR2(200); v_cnt NUMBER;
            v_job_name_log CONSTANT VARCHAR2(30) := ''BATCH_'' || p_batch_id; v_error_msg VARCHAR2(4000); 
            CURSOR c_staged_tables IS SELECT owner, table_name FROM job_staging_gtt;
        BEGIN
            -- 1. STAGE WORKLOAD: Identify and insert PENDING work into GTT
            EXECUTE IMMEDIATE ''TRUNCATE TABLE job_staging_gtt'';
            v_sql_staging_filter := ''
                INSERT INTO job_staging_gtt (owner, table_name)
                SELECT schema_name, table_name
                FROM row_count_control
                WHERE job_partition_id = :p_batch_id AND status = ''''PENDING''''
                '';
            EXECUTE IMMEDIATE v_sql_staging_filter USING p_batch_id;
            COMMIT;

            -- 2. PROCESS STAGED WORKLOAD
            FOR r IN c_staged_tables LOOP
                BEGIN 
                    -- Update status to RUNNING immediately (for monitoring and stall detection)
                    UPDATE row_count_control SET status = ''RUNNING'', start_time = SYSTIMESTAMP
                    WHERE schema_name = r.owner AND table_name = r.table_name;
                    COMMIT;

                    -- COUNT (The heavy sequential operation)
                    v_sql_count := ''SELECT COUNT(*) FROM "''||r.owner||''"."''||r.table_name||''"'';
                    EXECUTE IMMEDIATE v_sql_count INTO v_cnt;
                    
                    -- LOG SUCCESS and Mark COMPLETE
                    DELETE FROM row_counts_scheduler_log WHERE schema_name = r.owner AND table_name = r.table_name;
                    INSERT INTO row_counts_scheduler_log (schema_name, table_name, row_count, count_timestamp, job_name, batch_id, error_message)
                    VALUES (r.owner, r.table_name, v_cnt, SYSTIMESTAMP, v_job_name_log, p_batch_id, NULL);

                    UPDATE row_count_control SET status = ''COMPLETE'', end_time = SYSTIMESTAMP, error_message = NULL
                    WHERE schema_name = r.owner AND table_name = r.table_name;
                    COMMIT;

                EXCEPTION 
                    WHEN OTHERS THEN 
                        v_error_msg := SUBSTR(SQLERRM, 1, 4000);
                        UPDATE row_count_control SET status = ''FAILED'', end_time = SYSTIMESTAMP, error_message = v_error_msg
                        WHERE schema_name = r.owner AND table_name = r.table_name;
                        INSERT INTO row_counts_scheduler_log (schema_name, table_name, row_count, count_timestamp, job_name, batch_id, error_message)
                        VALUES (r.owner, r.table_name, -1, SYSTIMESTAMP, v_job_name_log, p_batch_id, v_error_msg);
                        COMMIT; 
                END;
            END LOOP;
        END;
        ';
    EXECUTE IMMEDIATE v_ddl_statement;
    DBMS_OUTPUT.PUT_LINE('Procedure count_table_batch created.');

    --------------------------------------------------------------------------------------
    -- D. JOB SUBMISSION AND EXECUTION
    --------------------------------------------------------------------------------------
    DBMS_OUTPUT.PUT_LINE('===================================================================');
    DBMS_OUTPUT.PUT_LINE('PHASE 2: JOB SUBMISSION (Launching ' || v_batches_needed || ' Parallel Jobs)');
    DBMS_OUTPUT.PUT_LINE('===================================================================');

    -- Reset stalled tasks before submission to recycle any abandoned jobs
    execute_ddl('BEGIN reset_stalled_tasks; END;');

    FOR i IN 1..v_batches_needed LOOP
        v_job_name := c_job_prefix || LPAD(i, 2, '0');
        
        -- Cleanup old job definitions
        execute_ddl('BEGIN DBMS_SCHEDULER.DROP_JOB(''' || v_job_name || ''', TRUE); END;'); 

        -- Create, Set Argument, and Enable (The robust sequence)
        DBMS_SCHEDULER.CREATE_JOB (
            job_name => v_job_name, job_type => 'STORED_PROCEDURE', job_action => 'COUNT_TABLE_BATCH',
            number_of_arguments => 1, start_date => SYSTIMESTAMP, repeat_interval => NULL, enabled => FALSE 
        );
        
        -- Use TO_CHAR(i) to avoid PLS-00307 ambiguity
        DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE(v_job_name, 1, TO_CHAR(i));
        DBMS_SCHEDULER.ENABLE(v_job_name);
        
        -- DBMS_OUTPUT.PUT_LINE('  -> Submitted Job: ' || v_job_name);
    END LOOP;
    
    COMMIT;
    DBMS_OUTPUT.PUT_LINE('All ' || v_batches_needed || ' jobs submitted and running in parallel!');

EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('!!! FATAL ERROR DURING EXECUTION !!!');
        DBMS_OUTPUT.PUT_LINE('SQLERRM: ' || SQLERRM);
        ROLLBACK;
        RAISE;
END;
/

live monitoring

 
WITH BatchActivity AS (
    -- 1. Get all successful logs from the last 10 minutes
    SELECT
        batch_id,
        job_name,
        count_timestamp AS log_time
    FROM
        row_counts_scheduler_log
    WHERE
        row_count >= 0
        AND count_timestamp >= SYSTIMESTAMP - INTERVAL '10' MINUTE
),
BatchTotals AS (
    -- 2. Get the total cumulative successful count for each batch, ever
    SELECT
        batch_id,
        COUNT(*) AS total_processed_ever
    FROM
        row_counts_scheduler_log
    WHERE
        row_count >= 0
    GROUP BY
        batch_id
)
SELECT
    ba.batch_id,
    ba.job_name,
    -- Calculate new tables processed in the last 5 minutes
    SUM(CASE WHEN ba.log_time >= SYSTIMESTAMP - INTERVAL '5' MINUTE THEN 1 ELSE 0 END) AS tables_processed_last_5_min,
    
    -- Calculate total tables processed in the previous 5 minutes (5 to 10 min ago)
    SUM(CASE WHEN ba.log_time >= SYSTIMESTAMP - INTERVAL '10' MINUTE 
             AND ba.log_time < SYSTIMESTAMP - INTERVAL '5' MINUTE THEN 1 ELSE 0 END) AS tables_processed_prev_5_min,
    -- Retrieve the cumulative total processed by this job
    bt.total_processed_ever
    
FROM
    BatchActivity ba
JOIN
    BatchTotals bt ON ba.batch_id = bt.batch_id
GROUP BY
    ba.batch_id,
    ba.job_name,
    bt.total_processed_ever
ORDER BY
    tables_processed_last_5_min DESC, -- Show currently active jobs first
    ba.batch_id


=======
WITH BatchActivity AS (
    -- 1. Get all successful logs from the last 15 minutes
    SELECT batch_id, job_name, count_timestamp AS log_time
    FROM row_counts_scheduler_log
    WHERE row_count >= 0
    AND count_timestamp >= SYSTIMESTAMP - INTERVAL '15' MINUTE
),
BatchTotals AS (
    -- 2. Get the total cumulative successful count and last activity time for each batch, ever
    SELECT batch_id, COUNT(*) AS total_processed_ever,
           MAX(count_timestamp) AS last_activity
    FROM row_counts_scheduler_log WHERE row_count >= 0 
    GROUP BY batch_id
),
ExpectedWorkload AS ( 
    -- 3. CRITICAL FIX: Calculate the unique, expected total table count for each partition (batch_id)
    SELECT MOD(ORA_HASH(owner||table_name),100)+1 AS batch_id,
           COUNT(*) AS expected
    FROM all_tables 
    WHERE owner IN ('HR','SALES','FINANCE') AND table_name NOT LIKE 'BIN$%'
    GROUP BY MOD(ORA_HASH(owner||table_name),100)+1
)
SELECT
    -- LIVE STATUS: Flags jobs that have not logged anything in the last 5 minutes
    CASE WHEN SUM(CASE WHEN ba.log_time >= SYSTIMESTAMP - INTERVAL '5' MINUTE THEN 1 ELSE 0 END) = 0 
         THEN ' STALLED' ELSE ' ACTIVE' END AS "STATUS",
         
    ba.batch_id AS "BATCH",
    
    -- Activity Comparison
    SUM(CASE WHEN ba.log_time >= SYSTIMESTAMP - INTERVAL '5' MINUTE THEN 1 ELSE 0 END) AS "NOW (tpm)",
    SUM(CASE WHEN ba.log_time >= SYSTIMESTAMP - INTERVAL '10' MINUTE
             AND ba.log_time < SYSTIMESTAMP - INTERVAL '5' MINUTE THEN 1 ELSE 0 END) AS "PREV (tpm)",
    
    -- Progress Metrics
    ew.expected AS "EXPECTED", 
    bt.total_processed_ever AS "DONE", 
    ROUND((bt.total_processed_ever / ew.expected) * 100, 1) AS "%",
    
    -- ETA Calculation: (Remaining Work / Current Rate) * Time Window
    -- Only calculate ETA if there was activity in the last 5 minutes to avoid division by zero
    CASE WHEN SUM(CASE WHEN ba.log_time >= SYSTIMESTAMP - INTERVAL '5' MINUTE THEN 1 ELSE 0 END) > 0 
         THEN ROUND(
                 (ew.expected - bt.total_processed_ever) / 
                 SUM(CASE WHEN ba.log_time >= SYSTIMESTAMP - INTERVAL '5' MINUTE THEN 1 ELSE 0 END) * 5, 1
              )
         ELSE NULL 
    END AS "ETA (min)",
    
    TO_CHAR(bt.last_activity, 'HH24:MI:SS') AS "LAST TOUCH"
    
FROM BatchActivity ba
JOIN BatchTotals bt ON ba.batch_id = bt.batch_id
JOIN ExpectedWorkload ew ON ba.batch_id = ew.batch_id 
GROUP BY ba.batch_id, ba.job_name, bt.total_processed_ever, bt.last_activity, ew.expected
ORDER BY
    -- Prioritize currently active batches at the top
    SUM(CASE WHEN ba.log_time >= SYSTIMESTAMP - INTERVAL '5' MINUTE THEN 1 ELSE 0 END) DESC, 
    ba.batch_id;

===

SET SERVEROUTPUT ON SIZE UNLIMITED

DECLARE
    -- Add the specific IDs returned by the query in Step 1
    TYPE t_batch_list IS TABLE OF NUMBER;
    v_stalled_batches t_batch_list := t_batch_list(32, 33, 45, 61, 62, 70); -- 👈 UPDATE THIS LIST

BEGIN
    DBMS_OUTPUT.PUT_LINE('--- INITIATING MANUAL SEQUENTIAL RESUME ---');
    
    FOR i IN 1..v_stalled_batches.COUNT LOOP
        DBMS_OUTPUT.PUT_LINE(RPAD('-', 60, '-'));
        DBMS_OUTPUT.PUT_LINE('Processing Batch ID: ' || v_stalled_batches(i));
        
        -- EXECUTE THE PROCEDURE
        count_table_batch(v_stalled_batches(i));
        
        -- The procedure contains the COMMIT, so this ensures the log is immediately updated
        
        DBMS_OUTPUT.PUT_LINE('Batch ID ' || v_stalled_batches(i) || ' finished. Checking log...');
        
        -- Optional wait to reduce immediate I/O strain between batches
        DBMS_LOCK.SLEEP(5); 
        
    END LOOP;
    
    DBMS_OUTPUT.PUT_LINE('--- ALL STALLED BATCHES HAVE BEEN EXECUTED ---');
EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('!!! FATAL ERROR DURING SEQUENTIAL EXECUTION: ' || SQLERRM);
        -- Note: Since the procedure commits per table, a final rollback isn't necessary.
        RAISE;
END;
/

===

CREATE OR REPLACE PROCEDURE count_table_batch(p_batch_id NUMBER) AS
    v_sql_staging VARCHAR2(4000);
    v_sql_count VARCHAR2(200);
    v_cnt NUMBER;
    v_job_name_log CONSTANT VARCHAR2(30) := 'BATCH_' || p_batch_id;
    v_error_msg VARCHAR2(4000);
    
    -- Cursor to iterate over the tables staged in the GTT
    CURSOR c_staged_tables IS
        SELECT owner, table_name FROM job_staging_gtt;

BEGIN
    -- PHASE 1: STAGE WORKLOAD (Quickly identify and stage ONLY the missing tables)
    v_sql_staging := '
        INSERT INTO job_staging_gtt (owner, table_name)
        SELECT 
            t.owner, 
            t.table_name
        FROM 
            all_tables t
        LEFT JOIN
            row_counts_scheduler_log l 
            -- Join only successful counts
            ON (t.owner = l.schema_name AND t.table_name = l.table_name AND l.row_count >= 0)
        WHERE 
            t.owner IN (''HR'',''SALES'',''FINANCE'') AND t.table_name NOT LIKE ''BIN$%''
            -- Apply the batch partitioning logic
            AND MOD(ORA_HASH(t.owner||t.table_name),100)+1 = :p_batch_id
            -- CRITICAL: Only include tables where a successful log entry does NOT exist
            AND l.table_name IS NULL';
            
    -- Execute staging insert. This happens fast and only affects the session.
    EXECUTE IMMEDIATE v_sql_staging USING p_batch_id;
    COMMIT; -- Commits the DML outside the main counting transaction

    -- PHASE 2: PROCESS STAGED WORKLOAD (Fast, isolated processing loop)
    FOR r IN c_staged_tables LOOP
        BEGIN 
            -- COUNT (The expensive, risky operation)
            v_sql_count := 'SELECT COUNT(*) FROM "'||r.owner||'"."'||r.table_name||'"';
            EXECUTE IMMEDIATE v_sql_count INTO v_cnt;
            
            -- DELETE/INSERT (The logging transaction)
            DELETE FROM row_counts_scheduler_log WHERE schema_name = r.owner AND table_name = r.table_name;
            INSERT INTO row_counts_scheduler_log (schema_name, table_name, row_count, count_timestamp, job_name, batch_id, error_message)
            VALUES (r.owner, r.table_name, v_cnt, SYSTIMESTAMP, v_job_name_log, p_batch_id, NULL);
            
            COMMIT;
        EXCEPTION 
            WHEN OTHERS THEN 
                -- Log failure
                v_error_msg := SUBSTR(SQLERRM, 1, 4000);
                INSERT INTO row_counts_scheduler_log (schema_name, table_name, row_count, count_timestamp, job_name, batch_id, error_message)
                VALUES (r.owner, r.table_name, -1, SYSTIMESTAMP, v_job_name_log, p_batch_id, v_error_msg);
                COMMIT;
        END;
    END LOOP;
    
    -- The GTT is automatically cleared when the session ends (ON COMMIT PRESERVE ROWS allows us to commit inside the loop).

EXCEPTION
    -- If staging failed entirely (e.g., severe privilege issue), log it.
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('FATAL ERROR IN BATCH ' || p_batch_id || ' SETUP: ' || SQLERRM);
        RAISE;
END;
/

count -v1

CREATE OR REPLACE PROCEDURE count_table_batch(p_batch_id NUMBER) AS
    v_sql_count VARCHAR2(200);
    v_cnt NUMBER;
    v_job_name_log CONSTANT VARCHAR2(30) := 'BATCH_' || p_batch_id;
    
    -- Declare the error variable (CRITICAL FIX)
    v_error_msg VARCHAR2(4000); 
    
    -- Variables for status output
    v_resuming_count NUMBER;
    v_status_message VARCHAR2(100);
    
BEGIN
    -- Determine initial status for output
    SELECT COUNT(*) INTO v_resuming_count
    FROM row_counts_scheduler_log
    WHERE batch_id = p_batch_id AND row_count >= 0;
    
    IF v_resuming_count > 0 THEN
        v_status_message := 'RESUMING (Skipping ' || v_resuming_count || ' successful tables in this batch)';
    ELSE
        v_status_message := 'STARTING FRESH';
    END IF;
    
    DBMS_OUTPUT.PUT_LINE('--- BATCH ' || p_batch_id || ' Status: ' || v_status_message || ' ---');

    -- Cursor: Filters out successfully completed tables (The Resume Logic)
    FOR r IN (
        SELECT 
            t.owner, 
            t.table_name 
        FROM 
            all_tables t
        LEFT JOIN
            row_counts_scheduler_log l 
            ON (t.owner = l.schema_name AND t.table_name = l.table_name AND l.row_count >= 0)
        WHERE 
            t.owner IN ('HR','SALES','FINANCE') AND t.table_name NOT LIKE 'BIN$%'
            AND MOD(ORA_HASH(t.owner||t.table_name),100)+1=p_batch_id
            AND l.table_name IS NULL -- Only include tables that haven't been successfully logged
    ) LOOP
        BEGIN 
            -- 1. COUNT
            v_sql_count := 'SELECT /*+ PARALLEL(8) */ COUNT(*) FROM "'||r.owner||'"."'||r.table_name||'"';
            EXECUTE IMMEDIATE v_sql_count INTO v_cnt;
            
            -- 2. SAFE DELETE then INSERT (Atomic update logic)
            DELETE FROM row_counts_scheduler_log 
            WHERE schema_name = r.owner AND table_name = r.table_name;
            
            INSERT INTO row_counts_scheduler_log 
            (schema_name, table_name, row_count, count_timestamp, job_name, batch_id, error_message)
            VALUES (r.owner, r.table_name, v_cnt, SYSTIMESTAMP, v_job_name_log, p_batch_id, NULL);
            
            COMMIT;
        EXCEPTION 
            WHEN OTHERS THEN 
                -- CRITICAL FIX: Capture and truncate SQLERRM safely
                v_error_msg := SUBSTR(SQLERRM, 1, 4000);
                
                -- Log failure with explicit column names
                INSERT INTO row_counts_scheduler_log 
                (schema_name, table_name, row_count, count_timestamp, job_name, batch_id, error_message)
                VALUES (r.owner, r.table_name, -1, SYSTIMESTAMP, v_job_name_log, p_batch_id, v_error_msg);
                
                COMMIT; 
        END;
    END LOOP;
END;
/

========

2. Final DBMS_SCHEDULER Control Script (The Submission)

This script calculates the required batches and uses the robust create/set/enable sequence to launch all jobs in parallel. It automatically uses the live logged count to provide an accurate status message.


SET SERVEROUTPUT ON SIZE UNLIMITED

DECLARE
    c_batch_size        CONSTANT NUMBER := 100;
    c_job_prefix        CONSTANT VARCHAR2(15) := 'BATCH_COUNT_';

    v_total_source_count NUMBER;
    v_total_logged_count NUMBER; -- Stores the actual current logged count
    v_batches_needed     NUMBER;
    v_job_name           VARCHAR2(128);
    v_total_submitted    NUMBER := 0;

    -- Helper procedure for safe DDL cleanup (omitted for brevity, assume defined)
    PROCEDURE execute_ddl(p_sql IN VARCHAR2) IS
    BEGIN
        EXECUTE IMMEDIATE p_sql;
    EXCEPTION
        WHEN OTHERS THEN
            IF SQLCODE NOT IN (-27475, -2443) THEN RAISE; END IF;
    END;

BEGIN
    -- 1. Calculate the definitive source count and current logged count
    SELECT COUNT(*) INTO v_total_source_count 
    FROM all_tables WHERE owner IN ('HR','SALES','FINANCE') AND table_name NOT LIKE 'BIN$%';
    
    SELECT COUNT(DISTINCT schema_name || '.' || table_name)
    INTO v_total_logged_count
    FROM row_counts_scheduler_log 
    WHERE row_count >= 0;
    
    v_batches_needed := CEIL(v_total_source_count / c_batch_size);

    DBMS_OUTPUT.PUT_LINE('===================================================================');
    DBMS_OUTPUT.PUT_LINE('MASTER JOB SUBMISSION: ROW COUNT INITIATION');
    DBMS_OUTPUT.PUT_LINE('===================================================================');
    DBMS_OUTPUT.PUT_LINE('Source Tables Found: ' || v_total_source_count);
    DBMS_OUTPUT.PUT_LINE('Currently Logged:    ' || v_total_logged_count);
    DBMS_OUTPUT.PUT_LINE('Jobs to Submit:      ' || v_batches_needed);
    DBMS_OUTPUT.PUT_LINE('Status: Submitting ALL jobs for parallel analysis.');

    FOR i IN 1..v_batches_needed LOOP
        v_job_name := c_job_prefix || LPAD(i, 2, '0');
        
        -- 1. Cleanup old job definitions (Ensures no old job definition conflicts)
        execute_ddl('BEGIN DBMS_SCHEDULER.DROP_JOB(''' || v_job_name || ''', TRUE); END;'); 

        -- 2. Create the job DISABLED (CRUCIAL for argument setting safety)
        DBMS_SCHEDULER.CREATE_JOB (
            job_name => v_job_name, job_type => 'STORED_PROCEDURE', job_action => 'COUNT_TABLE_BATCH',
            number_of_arguments => 1, start_date => SYSTIMESTAMP, repeat_interval => NULL, enabled => FALSE 
        );
        
        -- 3. Set argument and ENABLE (The robust sequence)
        DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE(v_job_name, 1, i);
        DBMS_SCHEDULER.ENABLE(v_job_name);
        
        v_total_submitted := v_total_submitted + 1;
    END LOOP;
    
    COMMIT;
    
    DBMS_OUTPUT.PUT_LINE('-------------------------------------------------------------------');
    DBMS_OUTPUT.PUT_LINE('Result: ' || v_total_submitted || ' parallel jobs successfully launched.');
    DBMS_OUTPUT.PUT_LINE('Monitoring: Use the status script to track live progress.');
    DBMS_OUTPUT.PUT_LINE('===================================================================');

EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('!!! FATAL ERROR DURING JOB SUBMISSION !!!');
        DBMS_OUTPUT.PUT_LINE('SQLERRM: ' || SQLERRM);
        ROLLBACK;
END;
/

=================

step 3 - clean up

SET SERVEROUTPUT ON

DECLARE
    -- The job prefix used in your submission script
    c_job_prefix CONSTANT VARCHAR2(15) := 'BATCH_COUNT_';

    v_job_name  VARCHAR2(128);
    v_jobs_dropped NUMBER := 0;

    -- Cursor selects all jobs matching the prefix that belong to the current user
    CURSOR job_cur IS
        SELECT job_name
        FROM dba_scheduler_jobs
        WHERE job_name LIKE c_job_prefix || '%'
        AND owner = USER; 
BEGIN
    DBMS_OUTPUT.PUT_LINE('--- INITIATING SYSTEM CLEANUP: DROPPING ALL BATCH JOBS ---');
    
    FOR rec IN job_cur LOOP
        v_job_name := rec.job_name;
        
        -- 1. OPTIONAL STOP ATTEMPT (Best effort to terminate running sessions cleanly)
        -- If the job is running, we try to stop it first. If it fails, we ignore the error.
        BEGIN
            DBMS_SCHEDULER.STOP_JOB(job_name => v_job_name, force => TRUE);
        EXCEPTION
            -- Ignore errors if the job already finished or is not running/stuck
            WHEN OTHERS THEN
                NULL; 
        END;
        
        -- 2. DROP the job permanently (The mandatory cleanup action)
        BEGIN
            -- FORCE => TRUE ensures the drop occurs even if the job was running
            DBMS_SCHEDULER.DROP_JOB(job_name => v_job_name, force => TRUE);
            v_jobs_dropped := v_jobs_dropped + 1;
            DBMS_OUTPUT.PUT_LINE('Dropped job definition: ' || v_job_name);
        EXCEPTION
            WHEN OTHERS THEN
                DBMS_OUTPUT.PUT_LINE('CRITICAL ERROR: Failed to drop job ' || v_job_name || '. SQLERRM: ' || SQLERRM);
        END;
        
    END LOOP;
    
    COMMIT;
    
    DBMS_OUTPUT.PUT_LINE('--------------------------------------------------');
    DBMS_OUTPUT.PUT_LINE('Total Job Definitions Removed: ' || v_jobs_dropped);
    DBMS_OUTPUT.PUT_LINE('The job queue is now clear of batch tasks.');
    
EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('!!! FATAL ERROR DURING CLEANUP: ' || SQLERRM);
        ROLLBACK;
END;
/