Saturday, December 6, 2025

tbs size

 
-- ==== COPY-PASTE THIS ENTIRE BLOCK ====
SET LINESIZE 200
SET PAGESIZE 100
COLUMN table_name        FORMAT A30
COLUMN partition_name     FORMAT A30
COLUMN current_total_gb   FORMAT 9,999,999.99
COLUMN one_partition_gb   FORMAT 9,999,999.99
COLUMN projected_12m_gb   FORMAT 9,999,999.99
COLUMN recommended_rds_gb FORMAT 9,999,999
SELECT
    'YOUR_TABLE_NAME'                                      AS table_name,
    ROUND(SUM(bytes)/1024/1024/1024, 2)                             AS current_total_gb,
    ROUND(SUM(CASE WHEN partition_name = 'THE_PARTITION_YOU_JUST_LOADED' 
                   THEN bytes ELSE 0 END)/1024/1024/1024, 2)       AS one_partition_gb,
    ROUND(SUM(CASE WHEN partition_name = 'THE_PARTITION_YOU_JUST_LOADED' 
                   THEN bytes ELSE 0 END)/1024/1024/1024, 2) * 12   AS projected_12_months_gb,
    ROUND(SUM(CASE WHEN partition_name = 'THE_PARTITION_YOU_JUST_LOADED' 
                   THEN bytes ELSE 0 END)/1024/1024/1024, 2) * 12 * 1.4 AS recommended_rds_storage_gb,
    'Safe buffer = 40% (growth, temp, undo, redo, statistics, future partitions)' AS note
FROM dba_segments
WHERE owner = 'YOUR_SCHEMA_NAME'
  AND segment_name = 'YOUR_TABLE_NAME'
  AND segment_type LIKE '%PARTITION';
-- Optional: also show the breakdown by partition so they see only one is big
SELECT 
    partition_name,
    ROUND(SUM(bytes)/1024/1024/1024, 2) AS size_per_partition_gb
FROM dba_segments
WHERE owner = 'YOUR_SCHEMA_NAME'
  AND segment_name = 'YOUR_TABLE_NAME'
  AND segment_type LIKE '%PARTITION'
GROUP BY partition_name
ORDER BY size_per_partition_gb DESC;

Thursday, December 4, 2025

parition index disable

 


-- CLEAN & SIMPLE – RUN THIS IN THE TARGET DATABASE
SELECT 'FACT_SALES'           AS table_name, COUNT(*) AS rows_june_2025 FROM FACT_SALES           PARTITION (ARCHV_DT_202506) UNION ALL
SELECT 'FACT_PURCHASES'      ,                COUNT(*)        FROM FACT_PURCHASES      PARTITION (ARCHV_DT_202506) UNION ALL
SELECT 'FACT_INVENTORY'      ,                COUNT(*)        FROM FACT_INVENTORY      PARTITION (ARCHV_DT_202506) UNION ALL
SELECT 'FACT_SHIPMENTS'      ,                COUNT(*)        FROM FACT_SHIPMENTS      PARTITION (ARCHV_DT_202506) UNION ALL
SELECT 'FACT_RETURNS'        ,                COUNT(*)        FROM FACT_RETURNS        PARTITION (ARCHV_DT_202506) UNION ALL
SELECT 'FACT_CLAIMS'         ,                COUNT(*)        FROM FACT_CLAIMS         PARTITION (ARCHV_DT_202506) UNION ALL
SELECT 'FACT_ADJUSTMENTS'    ,                COUNT(*)        FROM FACT_ADJUSTMENTS    PARTITION (ARCHV_DT_202506)
ORDER BY table_name;

===
SET SERVEROUTPUT ON SIZE UNLIMITED
DECLARE
   v_table_owner  VARCHAR2(128) := 'YOUR_SCHEMA_NAME';   -- ← CHANGE THIS
   v_table_name   VARCHAR2(128) := 'YOUR_TABLE_NAME';    -- ← CHANGE THIS

   v_sql          VARCHAR2(4000);
   v_count        INTEGER := 0;
BEGIN
   DBMS_OUTPUT.PUT_LINE('=== STARTING DISABLE OF FOREIGN KEYS ===');
   DBMS_OUTPUT.PUT_LINE('Target table: ' || v_table_owner || '.' || v_table_name);
   DBMS_OUTPUT.PUT_LINE('');

   FOR rec IN (
       SELECT owner, table_name, constraint_name
       FROM dba_constraints
       WHERE constraint_type = 'R'
         AND ( (r_owner, r_constraint_name) IN   -- FKs that point TO our table (child tables)
               (SELECT owner, constraint_name
                FROM dba_constraints
                WHERE owner = v_table_owner
                  AND table_name = v_table_name
                  AND constraint_type IN ('P','U')
               )
            OR                                 -- FKs that our table points FROM (parent references)
               (owner = v_table_owner AND table_name = v_table_name)
             )
         AND delete_rule = 'NO ACTION'   -- optional filter, remove if you want ALL
   )
   LOOP
      BEGIN
         v_sql := 'ALTER TABLE '||rec.owner||'.'||rec.table_name||
                  ' DISABLE CONSTRAINT '||rec.constraint_name;

         EXECUTE IMMEDIATE v_sql;

         DBMS_OUTPUT.PUT_LINE('DISABLED  → '||rec.owner||'.'||rec.table_name||'.'||rec.constraint_name);

         v_count := v_count + 1;

      EXCEPTION
         WHEN OTHERS THEN
            DBMS_OUTPUT.PUT_LINE('FAILED to disable → '||
                                 rec.owner||'.'||rec.table_name||'.'||rec.constraint_name||
                                 ' | Error: '||SQLERRM);
      END;
   END LOOP;

   DBMS_OUTPUT.PUT_LINE('');
   DBMS_OUTPUT.PUT_LINE('=== DISABLE PHASE FINISHED – '||v_count||' constraints disabled ===');
   DBMS_OUTPUT.PUT_LINE('You can now safely run the TOAD copy with:');
   DBMS_OUTPUT.PUT_LINE('PARTITION (ARCHV_DT_202506) AND ROWNUM <= 500');
END;
/



enable

SET SERVEROUTPUT ON SIZE UNLIMITED
DECLARE
   v_table_owner  VARCHAR2(128) := 'YOUR_SCHEMA_NAME';   -- ← SAME AS ABOVE
   v_table_name   VARCHAR2(128) := 'YOUR_TABLE_NAME';    -- ← SAME AS ABOVE

   v_sql          VARCHAR2(4000);
   v_count        INTEGER := 0;
BEGIN
   DBMS_OUTPUT.PUT_LINE('=== STARTING ENABLE NOVALIDATE OF FOREIGN KEYS ===');

   FOR rec IN (
       SELECT owner, table_name, constraint_name
       FROM dba_constraints
       WHERE constraint_type = 'R'
         AND ( (r_owner, r_constraint_name) IN
               (SELECT owner, constraint_name
                FROM dba_constraints
                WHERE owner = v_table_owner
                  AND table_name = v_table_name
                  AND constraint_type IN ('P','U')
               )
            OR (owner = v_table_owner AND table_name = v_table_name)
             )
   )
   LOOP
      BEGIN
         v_sql := 'ALTER TABLE '||rec.owner||'.'||rec.table_name||
                  ' ENABLE NOVALIDATE CONSTRAINT '||rec.constraint_name;

         EXECUTE IMMEDIATE v_sql;

         DBMS_OUTPUT.PUT_LINE('ENABLED NOVALIDATE → '||rec.owner||'.'||rec.table_name||'.'||rec.constraint_name);

         v_count := v_count + 1;

      EXCEPTION
         WHEN OTHERS THEN
            DBMS_OUTPUT.PUT_LINE('FAILED to enable → '||
                                 rec.owner||'.'||rec.table_name||'.'||rec.constraint_name||
                                 ' | Error: '||SQLERRM);
      END;
   END LOOP;

   DBMS_OUTPUT.PUT_LINE('');
   DBMS_OUTPUT.PUT_LINE('=== ALL DONE – '||v_count||' constraints re-enabled with NOVALIDATE ===');
END;
/






======

Step 1 – Disable all foreign keys that can block your insert (run in the TARGET database)

SQL
-- Disable every FK that references your table OR that your table references
BEGIN
   FOR rec IN (
       SELECT owner, table_name, constraint_name
       FROM dba_constraints
       WHERE constraint_type = 'R'
         AND (r_owner, r_constraint_name) IN (
               SELECT owner, constraint_name
               FROM dba_constraints
               WHERE owner = 'YOUR_TARGET_SCHEMA'     -- ← change to your schema
                 AND table_name = 'YOUR_TABLE_NAME'   -- ← change to your actual table name
                 AND constraint_type IN ('P','U')
             )
          OR (owner = 'YOUR_TARGET_SCHEMA' 
              AND table_name = 'YOUR_TABLE_NAME')
   ) LOOP
       EXECUTE IMMEDIATE 
           'ALTER TABLE '||rec.owner||'.'||rec.table_name||
           ' DISABLE CONSTRAINT '||rec.constraint_name;
   END LOOP;
END;
/

Step 2 – Now run the copy in TOAD with this WHERE clause (100% works)

SQL
PARTITION (ARCHV_DT_202506) AND ROWNUM <= 500

Step 3 – After the copy finishes successfully, re-enable all the FKs (with NOVALIDATE so it doesn’t scan millions of rows)

SQL
BEGIN
   FOR rec IN (
       SELECT owner, table_name, constraint_name
       FROM dba_constraints
       WHERE constraint_type = 'R'
         AND (r_owner, r_constraint_name) IN (
               SELECT owner, constraint_name
               FROM dba_constraints
               WHERE owner = 'YOUR_TARGET_SCHEMA'
                 AND table_name = 'YOUR_TABLE_NAME'
                 AND constraint_type IN ('P','U')
             )
          OR (owner = 'YOUR_TARGET_SCHEMA' 
              AND table_name = 'YOUR_TABLE_NAME')
   ) LOOP
       EXECUTE IMMEDIATE 
           'ALTER TABLE '||rec.owner||'.'||rec.table_name||
           ' ENABLE NOVALIDATE CONSTRAINT '||rec.constraint_name;
   END LOOP;
END;
/

Just replace these two placeholders in both scripts:

Wednesday, December 3, 2025

drop

 
-- ========================================================
-- FINAL WORKING SCRIPT – DROP COLUMN + PARALLEL 8 (NO PLS-00103)
-- ========================================================
-- >>> EDIT ONLY THESE THREE LINES <<<
DEFINE owner       = 'HR'
DEFINE table_name  = 'BIG_TABLE'
DEFINE column_name = 'OLD_COLUMN'
-- ========================================================
PROMPT === 1. Mark column as UNUSED (instant) ===
ALTER TABLE &&owner..&&table_name SET UNUSED COLUMN &&column_name;
COMMIT;
PROMPT Column &&column_name is now UNUSED and invisible to all new transactions.
-- ========================================================
PROMPT === 2. Submit background job with PARALLEL 8 ===
DECLARE
    v_job_action  CLOB;
BEGIN
    -- First remove any old job with the same name
    BEGIN
        DBMS_SCHEDULER.DROP_JOB(job_name => 'DROP_UNUSED_&&table_name._P8', force => TRUE);
    EXCEPTION WHEN OTHERS THEN NULL;
    END;
    -- Build the job action as CLOB (this avoids all quoting nightmares)
    v_job_action := q'[
BEGIN
    EXECUTE IMMEDIATE 'ALTER SESSION ENABLE PARALLEL DML';
    EXECUTE IMMEDIATE 'ALTER SESSION FORCE PARALLEL DDL PARALLEL 8';
    -- Do the heavy physical drop with checkpointing
    EXECUTE IMMEDIATE 
        'ALTER TABLE ]' || '&&owner..&&table_name' || q'[ DROP UNUSED COLUMNS CHECKPOINT 10000';
    DBMS_OUTPUT.PUT_LINE('Physical drop with PARALLEL 8 completed successfully');
END;]';
    DBMS_SCHEDULER.CREATE_JOB (
        job_name     => 'DROP_UNUSED_&&table_name._P8',
        job_type     => 'PLSQL_BLOCK',
        job_action   => v_job_action,
        start_date   => SYSTIMESTAMP,
        enabled      => TRUE,
        auto_drop    => TRUE,
        comments     => 'Physical drop of unused columns with PARALLEL 8'
    );
    DBMS_OUTPUT.PUT_LINE('Background job submitted successfully with PARALLEL 8');
END;
/
PROMPT 
PROMPT Job is now running in the background with 8 parallel processes.
PROMPT You can close TOAD/SQL Developer immediately — everything continues on the server.
-- ========================================================
PROMPT === MONITORING COMMANDS (run any time) ===
PROMPT 
PROMPT -- Job status
PROMPT SELECT job_name, status, actual_start_date, run_duration, error#
PROMPT FROM user_scheduler_job_run_details
PROMPT WHERE job_name = 'DROP_UNUSED_&&table_name._P8';
PROMPT -- Real-time progress (you will see ~8 rows, one per parallel slave)
PROMPT SELECT sid, opname, sofar, totalwork,
PROMPT        ROUND(sofar/totalwork*100,2)||'%' pct, elapsed_seconds/60 mins
PROMPT FROM v$session_longops
PROMPT WHERE opname = 'Drop Column' AND target LIKE '%&&table_name%';
PROMPT Done! Column already disappeared from queries, physical cleanup running safely in background.



====

-- ========================================================
-- SAFE DROP COLUMN + PARALLEL 8 - FULL SCRIPT (Dec 2025)
-- ========================================================
-- >>>>>>>> EDIT THESE 4 LINES ONLY <<<<<<<<
DEFINE owner       = 'YOUR_SCHEMA_NAME'
DEFINE table_name  = 'YOUR_TABLE_NAME'
DEFINE column_name = 'YOUR_COLUMN_NAME'

-- Set to 'Y' ONLY if you really want to kill the stuck session (rarely needed now)
DEFINE KILL_CURRENT_SESSION = 'N'   -- Keep 'N' in almost all cases

-- ========================================================
PROMPT 
PROMPT === STEP 1: Mark column as UNUSED (instant, zero downtime) ===
ALTER TABLE &&owner..&&table_name SET UNUSED COLUMN &&column_name;
COMMIT;

PROMPT Column &&column_name is now invisible to all new queries and DESC!

-- ========================================================
PROMPT 
PROMPT === STEP 2: Schedule PHYSICAL DROP with PARALLEL 8 in background ===
BEGIN
    -- Clean any previous job with same name
    BEGIN
        DBMS_SCHEDULER.DROP_JOB('DROP_UNUSED_&&table_name||'_PAR8', FORCE => TRUE);
    EXCEPTION WHEN OTHERS THEN NULL;
    END;

    DBMS_SCHEDULER.CREATE_JOB (
        job_name        => 'DROP_UNUSED_&&table_name||'_PAR8',
        job_type        => 'PLSQL_BLOCK',
        job_action      => q'[
            BEGIN
                -- Enable parallel DML inside the job
                EXECUTE IMMEDIATE 'ALTER SESSION ENABLE PARALLEL DML';
                EXECUTE IMMEDIATE 'ALTER SESSION FORCE PARALLEL DDL PARALLEL 8';

                DBMS_OUTPUT.PUT_LINE('Starting physical drop with PARALLEL 8...');

                EXECUTE IMMEDIATE 
                    'ALTER TABLE &&owner..&&table_name DROP UNUSED COLUMNS CHECKPOINT 10000';

                DBMS_OUTPUT.PUT_LINE('Physical drop completed successfully with PARALLEL 8');
            EXCEPTION
                WHEN OTHERS THEN
                    DBMS_OUTPUT.PUT_LINE('ERROR: '||SQLERRM);
                    RAISE;
            END;
        ]',
        start_date      => SYSTIMESTAMP + 5/1440,   -- start 5 seconds from now
        enabled         => TRUE,
        auto_drop       => TRUE,
        comments        => 'Physical drop of unused column with PARALLEL 8'
    );

    DBMS_OUTPUT.PUT_LINE('Background job with PARALLEL 8 submitted successfully!');
END;
/

PROMPT 
PROMPT Job is now running in the background with 8 parallel processes.
PROMPT You can safely close TOAD/SQL Developer now — everything continues on the server.

-- ========================================================
PROMPT 
PROMPT === MONITORING QUERIES (copy-paste & run anytime) ===
PROMPT 
PROMPT -- 1. Job status
PROMPT SELECT job_name, status, actual_start_date, run_duration, error#
PROMPT FROM   user_scheduler_job_run_details
PROMPT WHERE  job_name = 'DROP_UNUSED_&&table_name||'_PAR8'
PROMPT ORDER BY log_date DESC;

PROMPT 
PROMPT -- 2. Real-time progress with PARALLEL 8 slaves
PROMPT SELECT sid, username, opname, target, sofar, totalwork,
PROMPT        ROUND(sofar/totalwork*100,2) || '%' pct_done,
PROMPT        elapsed_seconds/60 mins_running, message
PROMPT FROM   v$session_longops
PROMPT WHERE  opname = 'Drop Column'
PROMPT   AND  target LIKE '%&&table_name%'
PROMPT   AND  sofar <> totalwork;

PROMPT 
PROMPT -- 3. See the 8 parallel execution servers in action
PROMPT SELECT sid, px_server#, px_server_group, degree
PROMPT FROM   v$session
PROMPT WHERE  program LIKE '%(P0%'
PROMPT   AND    sql_id = (SELECT sql_id FROM v$session WHERE program LIKE '%scheduler%');

PROMPT 
PROMPT -- 4. Confirm column is gone after job finishes
PROMPT SELECT column_name, hidden_column, segment_column_id
PROMPT FROM   user_tab_cols
PROMPT WHERE  table_name = UPPER('&&table_name')
PROMPT AND    column_name = UPPER('&&column_name');

PROMPT 
PROMPT Script finished. Your table stays 100% available the whole time.
PROMPT Typical speed gain with PARALLEL 8: 5–7× faster than serial drop.

-- End of script

Monday, December 1, 2025

ORA-14097

-- ONE SINGLE QUERY → generates perfect plain-SQL commands
-- Uses the REAL SYS_C... names from PROD.SALES
SELECT 
    'ALTER TABLE sales_staging ADD "' || column_name || '" VARCHAR2(1);' || CHR(10) ||
    'ALTER TABLE sales_staging SET UNUSED COLUMN "' || column_name || '";'
    AS plain_sql_commands
FROM dba_tab_cols
WHERE owner       = 'PROD'
  AND table_name  = 'SALES'
  AND column_name LIKE 'SYS_C%' 
  AND hidden_column = 'YES'
ORDER BY internal_column_id;

==========

STEP 2


-- Compare physical column order (INTERNAL_COLUMN_ID) between source and staging
WITH source_cols AS (
   SELECT internal_column_id, column_name, data_type, hidden_column
   FROM   dba_tab_cols
   WHERE  owner = 'PROD' AND table_name = 'SALES'
   ORDER BY internal_column_id
),
staging_cols AS (
   SELECT internal_column_id, column_name, data_type, hidden_column
   FROM   dba_tab_cols
   WHERE  owner = 'PROD' AND table_name = 'SALES_STAGING'
   ORDER BY internal_column_id
)
SELECT 
   s.internal_column_id AS source_pos,
   s.column_name AS source_col,
   t.internal_column_id AS staging_pos,
   t.column_name AS staging_col,
   CASE WHEN s.internal_column_id = t.internal_column_id THEN 'MATCH' ELSE 'MISMATCH' END AS status
FROM   source_cols s
FULL OUTER JOIN staging_cols t
   ON s.internal_column_id = t.internal_column_id
   AND NVL(s.column_name, '?') = NVL(t.column_name, '?')
ORDER BY NVL(s.internal_column_id, t.internal_column_id);


++++++++++

-- This works 100% identically to the NUMBER version
-- (Oracle officially ignores datatype for UNUSED columns during exchange)
BEGIN
   FOR i IN 1..40 LOOP
      EXECUTE IMMEDIATE 
         'ALTER TABLE sales_staging ADD dgh_' || LPAD(i,3,'0') || ' VARCHAR2(1)';
      EXECUTE IMMEDIATE 
         'ALTER TABLE sales_staging SET UNUSED COLUMN dgh_' || LPAD(i,3,'0');
   END LOOP;
END;
/
===========
-- CORRECTED VERSION – works in all tools
SELECT source,
       internal_column_id,
       column_name,
       data_type,
       data_length,
       data_precision,
       data_scale
FROM (
    -- STAGING minus PARTITIONED
    SELECT 'STAGING'    AS source,
           internal_column_id,
           column_name,
           data_type,
           data_length,
           data_precision,
           data_scale
    FROM   dba_tab_cols
    WHERE  owner       = 'PROD'            -- <<< change
    AND    table_name  = 'SALES_STAGING'   -- <<< change

    MINUS

    SELECT 'PARTITIONED' AS source,
           internal_column_id,
           column_name,
           data_type,
           data_length,
           data_precision,
           data_scale
    FROM   dba_tab_cols
    WHERE  owner       = 'PROD'            --<<< change
    AND    table_name  = 'SALES'           --<<< change

    UNION ALL

    -- PARTITIONED minus STAGING
    SELECT 'PARTITIONED' AS source,
           internal_column_id,
           column_name,
           data_type,
           data_length,
           data_precision,
           data_scale
    FROM   dba_tab_cols
    WHERE  owner       = 'PROD'
    AND    table_name  = 'SALES'

    MINUS

    SELECT 'STAGING' AS source,
           internal_column_id,
           column_name,
           data_type,
           data_length,
           data_precision,
           data_scale
    FROM   dba_tab_cols
    WHERE  owner       = 'PROD'
    AND    table_name  = 'SALES_STAGING'
)
ORDER BY internal_column_id, source;   -- only ONE ORDER BY at the very end

-----

-- PERFECT ONE-SHOT QUERY – shows exactly what causes ORA-14097
WITH staging AS (
    SELECT 
        column_id,
        column_name,
        data_type,
        data_type_mod,
        data_type_owner,
        data_length,
        data_precision,
        data_scale,
        char_length,          -- correct column in DBA_TAB_COLUMNS
        char_used,            -- B or C – correct column
        nullable,
        data_default          -- LONG! but we handle it safely below
    FROM dba_tab_columns
    WHERE owner       = 'YOUR_SCHEMA'
      AND table_name  = 'SALES_STAGING'
),
partition_def AS (
    SELECT 
        column_id,
        column_name,
        data_type,
        data_type_mod,
        data_type_owner,
        data_length,
        data_precision,
        data_scale,
        char_col_decl_length  AS char_length,   -- correct name in DBA_PART_COL_STATISTICS
        char_used,
        nullable,
        NULL                  AS data_default   -- this column does NOT exist here → always NULL
    FROM dba_part_col_statistics
    WHERE owner          = 'YOUR_SCHEMA'
      AND table_name     = 'SALES'
      AND partition_name = 'P_DEC2025'
)
SELECT 
    src,
    column_id,
    column_name,
    data_type,
    char_used,
    nullable,
    data_precision,
    data_scale,
    char_length,
    CASE WHEN data_default IS NULL THEN 'NULL'
         WHEN LENGTH(data_default) > 50 THEN SUBSTR(data_default,1,50)||'...(truncated)'
         ELSE TO_CHAR(data_default)
    END AS data_default_snippet
FROM (
    SELECT 'STAGING ≠ PARTITION' AS src, s.*,
           'S' as which
    FROM staging s
    FULL OUTER JOIN partition_def p
      ON s.column_id = p.column_id
     AND NVL(s.column_name,'?') = NVL(p.column_name,'?')
    WHERE p.column_id IS NULL
       OR s.column_id IS NULL
       OR s.data_type          <> p.data_type
       OR NVL(s.data_type_mod,'X')   <> NVL(p.data_type_mod,'X')
       OR NVL(s.data_type_owner,'X') <> NVL(p.data_type_owner,'X')
       OR s.data_length        <> p.data_length
       OR NVL(s.data_precision,-999) <> NVL(p.data_precision,-999)
       OR NVL(s.data_scale,-999)     <> NVL(p.data_scale,-999)
       OR NVL(s.char_length,-999)    <> NVL(p.char_length,-999)
       OR NVL(s.char_used,'X')       <> NVL(p.char_used,'X')
       OR NVL(s.nullable,'X')        <> NVL(p.nullable,'X')
       OR (s.data_default IS NOT NULL AND p.data_default IS NULL)
       OR (s.data_default IS NULL     AND p.data_default IS NOT NULL)

    UNION ALL

    SELECT 'PARTITION ≠ STAGING', p.*,
           'P'
    FROM staging s
    FULL OUTER JOIN partition_def p
      ON s.column_id = p.column_id
     AND NVL(s.column_name,'?') = NVL(p.column_name,'?')
    WHERE p.column_id IS NULL
       OR s.column_id IS NULL
       OR s.data_type          <> p.data_type
       OR NVL(s.data_type_mod,'X')   <> NVL(p.data_type_mod,'X')
       OR NVL(s.data_type_owner,'X') <> NVL(p.data_type_owner,'X')
       OR s.data_length        <> p.data_length
       OR NVL(s.data_precision,-999) <> NVL(p.data_precision,-999)
       OR NVL(s.data_scale,-999)     <> NVL(p.data_scale,-999)
       OR NVL(s.char_length,-999)    <> NVL(p.char_length,-999)
       OR NVL(s.char_used,'X')       <> NVL(p.char_used,'X')
       OR NVL(s.nullable,'X')        <> NVL(p.nullable,'X')
       OR (s.data_default IS NOT NULL AND p.data_default IS NULL)
       OR (s.data_default IS NULL     AND p.data_default IS NOT NULL)
)
ORDER BY column_id, src;

-- 1. Structure of your staging table
SELECT 
    column_id,
    column_name,
    data_type,
    data_type_mod,
    data_type_owner,
    data_length,
    data_precision,
    data_scale,
    char_length,
    char_used,        -- B or C  (this is a VERY common hidden mismatch)
    nullable,
    data_default
FROM   user_tab_columns
WHERE  table_name = 'SALES_STAGING'
ORDER BY column_id;
-- 2. Structure of the exact partition you want to exchange with
SELECT 
    column_id,
    column_name,
    data_type,
    data_type_mod,
    data_type_owner,
    data_length,
    data_precision,
    data_scale,
    char_length,
    char_used,        -- B or C
    nullable,
    data_default
FROM   user_part_col_statistics   -- this view shows the real partition definition
WHERE  table_name = 'SALES'        -- your partitioned table
AND    partition_name = 'P_DEC2025'   -- <<< change to your real partition name
ORDER BY column_id;

--------

-- One-liner that shows you the differences immediately
SELECT * FROM (
    SELECT column_id, column_name, data_type, char_used, nullable, data_default, 'STAGING' src
    FROM user_tab_columns WHERE table_name = 'SALES_STAGING'
  MINUS
    SELECT column_id, column_name, data_type, char_used, nullable, data_default, 'PARTITION' 
    FROM user_part_col_statistics 
    WHERE table_name = 'SALES' AND partition_name = 'P_DEC2025'
)
UNION ALL
SELECT * FROM (
    SELECT column_id, column_name, data_type, char_used, nullable, data_default, 'PARTITION'
    FROM user_part_col_statistics 
    WHERE table_name = 'SALES' AND partition_name = 'P_DEC2025'
  MINUS
    SELECT column_id, column_name, data_type, char_used, nullable, data_default, 'STAGING'
    FROM user_tab_columns WHERE table_name = 'SALES_STAGING'
)
ORDER BY column_id, src;

Wednesday, November 19, 2025

POPULATE TABLE DATA

 

-- CLEAN FINAL REPORT: One clean row per table, no duplicates, perfect for managers
-- Top 50 most active tables across the entire database

SELECT
    "Rank",
    "Schema",
    "Table Name",
    "Rows Inserted",
    "Rows Updated",
    "Rows Deleted",
    "Total DML",
    "Avg Inserts/Day",
    "DML Activity Since",
    "Stats Last Collected"
FROM (
    SELECT
        RANK() OVER (ORDER BY 
            dtm.inserts DESC, 
            dtm.updates DESC, 
            dtm.deletes DESC
        ) AS "Rank",

        dtm.table_owner                                              AS "Schema",
        dtm.table_name                                               AS "Table Name",

        TO_CHAR(dtm.inserts, 'FM999,999,999,990')                    AS "Rows Inserted",
        TO_CHAR(dtm.updates, 'FM999,999,999,990')                     AS "Rows Updated",
        TO_CHAR(dtm.deletes, 'FM999,999,999,990')                    AS "Rows Deleted",
        TO_CHAR(dtm.inserts + dtm.updates + dtm.deletes, 'FM999,999,999,990') AS "Total DML",

        -- Safe average: avoids crazy numbers when stats are fresh
        TO_CHAR(
            ROUND(dtm.inserts / GREATEST(SYSDATE - dtm.timestamp + 1, 1)),
            'FM999,999,990'
        )                                                             AS "Avg Inserts/Day",

        TO_CHAR(dtm.timestamp, 'DD-MON-YYYY HH24:MI')                AS "DML Activity Since",
        TO_CHAR(MAX(st.last_analyzed), 'DD-MON-YYYY HH24:MI')        AS "Stats Last Collected"

    FROM dba_tab_modifications dtm
    LEFT JOIN dba_tab_statistics st
           ON st.owner       = dtm.table_owner
          AND st.table_name  = dtm.table_name
          AND st.object_type = 'TABLE'

    WHERE (dtm.inserts + dtm.updates + dtm.deletes) > 0
      AND dtm.table_owner NOT IN ('SYS','SYSTEM','OUTLN','DBSNMP','GSMADMIN_INTERNAL','XDB','APPQOSSYS','ORACLE_OCM')

    GROUP BY 
        dtm.table_owner, dtm.table_name, dtm.inserts, dtm.updates, dtm.deletes, dtm.timestamp

    ORDER BY "Rank"
)
WHERE ROWNUM <= 50;   -- Top 50 only — clean and readable

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

SELECT
    owner AS "Schema",
    object_name AS "Table Name",
    TO_CHAR(created, 'DD-MON-YYYY HH24:MI') AS "Date Created"
FROM
    dba_objects
WHERE
    owner = 'YOUR_SCHEMA_NAME'  -- <<< REPLACE with the schema name
    AND object_type = 'TABLE'
ORDER BY
    created DESC;

==========
SELECT
    -- 1. RANK & SCHEMA INFO
    RANK() OVER (ORDER BY dtm.inserts DESC, dtm.updates DESC, dtm.deletes DESC) AS "Rank",
    dtm.table_owner AS "Schema",
    dtm.table_name AS "Table Name",
    
    -- 2. DML COUNTS (Formatted)
    TO_CHAR(dtm.inserts, 'FM999,999,999,990') AS "Rows Inserted",
    TO_CHAR(dtm.updates, 'FM999,999,999,990') AS "Rows Updated",
    TO_CHAR(dtm.deletes, 'FM999,999,999,990') AS "Rows Deleted",
    TO_CHAR(dtm.inserts + dtm.updates + dtm.deletes, 'FM999,999,999,990') AS "Total DML",

    -- 3. RATE & TIMESTAMP INFO
    TO_CHAR(ROUND(dtm.inserts / NULLIF((SYSDATE - dtm.timestamp), 0)), 'FM999,999,999,990') AS "Avg Inserts/Day",
    TO_CHAR(dtm.timestamp, 'DD-MON-YYYY HH24:MI') AS "Modifications Since",
    dt.last_analyzed AS "Stats Last Collected", -- Pulled from DBA_TABLES
    ROUND(SYSDATE - dtm.timestamp, 1) AS "Days Since Mods"
FROM
    dba_tab_modifications dtm
JOIN
    dba_tables dt ON dtm.table_owner = dt.owner AND dtm.table_name = dt.table_name
WHERE
    (dtm.inserts > 0 OR dtm.updates > 0 OR dtm.deletes > 0) -- Filter for any DML activity
    AND dtm.table_owner NOT IN ('SYS','SYSTEM','OUTLN','DBSNMP','GSMADMIN_INTERNAL','XDB','APPQOSSYS')
ORDER BY
    "Rank" ASC;

Tuesday, November 18, 2025

partitions

 


CREATE OR REPLACE PROCEDURE schema_name.p_cleanup_drop_columns
AS
    -- Define a collection to hold all your DDL statements
    TYPE t_ddl_list IS TABLE OF VARCHAR2(512);
    
    -- *** 1. ADD YOUR STATEMENTS HERE ***
    v_ddl_statements t_ddl_list := t_ddl_list(
        -- Placeholder 1: REPLACE this line
        'ALTER TABLE OWNER1.TABLE_INVENTORY DROP COLUMN OLD_FLAG_ID', 
        
        -- Placeholder 2: REPLACE this line
        'ALTER TABLE OWNER2.AUDIT_LOGS DROP COLUMN LEGACY_COL_DATE',
        
        -- Placeholder 3: REPLACE this line
        'ALTER TABLE OWNER3.CONFIG_DATA DROP COLUMN TEMP_VALUE',
        
        -- Add as many 'ALTER TABLE ... DROP COLUMN ...' statements as needed
        -- 'ALTER TABLE schema_name.table_name DROP COLUMN column_to_drop'
        
        -- Placeholder N: REPLACE this line
        'ALTER TABLE OWNER4.MASTER_TABLE DROP COLUMN REDUNDANT_KEY'
    );
    
    v_current_ddl VARCHAR2(512);
    
BEGIN
    -- Loop through the defined list of DDL statements
    FOR i IN 1..v_ddl_statements.COUNT LOOP
        v_current_ddl := v_ddl_statements(i);
        
        -- Begin an inner block to handle errors for THIS specific statement
        BEGIN
            -- Output the DDL being run (will be logged by DBMS_SCHEDULER)
            DBMS_OUTPUT.PUT_LINE('Executing: ' || v_current_ddl);
            
            -- Execute the DDL statement
            EXECUTE IMMEDIATE v_current_ddl;
            
            -- Commit implicitly happens due to DDL, but good to ensure transaction boundary
            COMMIT; 
            
            DBMS_OUTPUT.PUT_LINE('SUCCESS: ' || v_current_ddl);
            
        EXCEPTION
            WHEN OTHERS THEN
                -- Log the error, but do not raise the exception (continue the loop)
                DBMS_OUTPUT.PUT_LINE('*** ERROR *** Failed to execute DDL: ' || v_current_ddl);
                DBMS_OUTPUT.PUT_LINE('SQLERRM: ' || SQLERRM);
                
                -- The NULL statement allows the procedure to proceed to the next item
                NULL; 
        END;
    END LOOP;
    
    DBMS_OUTPUT.PUT_LINE('Procedure p_cleanup_drop_columns finished processing ' || v_ddl_statements.COUNT || ' statements.');
    
END;
/

-- How to schedule the enhanced procedure
BEGIN
  DBMS_SCHEDULER.CREATE_JOB (
    job_name        => 'BACKGROUND_COLUMN_DROP_JOB',
    job_type        => 'STORED_PROCEDURE',
    job_action      => 'SCHEMA_NAME.P_CLEANUP_DROP_COLUMNS', -- The short, correct call
    enabled         => TRUE,
    auto_drop       => TRUE
  );
END;
/


==========
DECLARE
  -- *** Configuration Parameters (Same as yours) ***
  TYPE t_tab IS TABLE OF VARCHAR2(128);
  v_tables t_tab := t_tab(
    'OWNER.TABLE_N1',
    'OWNER.TABLE_N2',
    'OWNER.TABLE_N3',
    'OWNER.TABLE_N4',
    'OWNER.TABLE_N5'
  );
  v_max_boundary  NUMBER;
  v_start         NUMBER;
  v_sql           VARCHAR2(4000);
  
  -- *** New Iteration Variables ***
  v_new_boundary  NUMBER;
  v_new_pname     VARCHAR2(128);
  v_current_pname CONSTANT VARCHAR2(128) := 'REPORT_ID_7000'; -- The MAXVALUE partition
  
BEGIN
  -- 1. Find the current maximum boundary (Your logic is perfect here)
  SELECT MAX(TO_NUMBER(REGEXP_SUBSTR(high_value, '\d+')))
  INTO   v_max_boundary
  FROM   user_tab_partitions
  WHERE  table_name IN (
           SELECT UPPER(REGEXP_SUBSTR(column_value))
           FROM   TABLE(v_tables)
         )
    AND  partition_name LIKE 'REPORT_ID_%'
    AND  high_value NOT IN ('MAXVALUE', 'maxvalue') -- Exclude the MAXVALUE partition
    AND  high_value IS NOT NULL;
  IF v_max_boundary IS NULL THEN
    v_max_boundary := 270;
  END IF;
  v_start := v_max_boundary + 1;
  DBMS_OUTPUT.PUT_LINE('Highest existing boundary: ' || v_max_boundary);
  DBMS_OUTPUT.PUT_LINE('Will create partitions from ' || v_start || ' to ' || (v_start + 11));
  -- 2. Loop over each table
  FOR i IN 1..v_tables.COUNT LOOP
    DBMS_OUTPUT.PUT_LINE(' ');
    DBMS_OUTPUT.PUT_LINE('Processing table: ' || v_tables(i));
    -- 3. Loop 12 times to create 12 new partitions
    FOR j IN 0..11 LOOP
      v_new_boundary := v_start + j;
      v_new_pname := 'REPORT_ID_' || v_new_boundary;
      
      -- SPLIT PARTITION AT (new_boundary)
      -- This creates the new partition (P_XXX) up to the boundary,
      -- and leaves the remaining data (P_7000) to the right.
      v_sql := 'ALTER TABLE ' || v_tables(i) || ' SPLIT PARTITION ' || v_current_pname ||
               ' AT (' || (v_new_boundary + 1) || ')' || -- Boundary is always the next value (less than)
               ' INTO (PARTITION ' || v_new_pname || ' VALUES LESS THAN (' || (v_new_boundary + 1) || '), ' ||
               'PARTITION ' || v_current_pname || ')';
      -- Execute the DDL statement
      EXECUTE IMMEDIATE v_sql;
      DBMS_OUTPUT.PUT_LINE(' -> Created partition ' || v_new_pname);
    END LOOP;
    DBMS_OUTPUT.PUT_LINE('12 new partitions created successfully on ' || v_tables(i));
  END LOOP;
  
  DBMS_OUTPUT.PUT_LINE(' ');
  DBMS_OUTPUT.PUT_LINE('All done! 12 partitions added to ' || v_tables.COUNT || ' tables.');
EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE('ERROR: ' || SQLERRM);
    DBMS_OUTPUT.PUT_LINE('Failing SQL: ' || v_sql);
    RAISE;
END;
/

partitions drop

 

--  THIS ONE WORKS – tested on Oracle 19c / 21c RDS
BEGIN
  DBMS_SCHEDULER.CREATE_JOB (
    job_name   => 'CLEANUP_OLD_PARTITIONS_01',
    job_type   => 'PLSQL_BLOCK',
    job_action => q'[
      DECLARE
        v_owner       CONSTANT VARCHAR2(128) := 'OWNER1';           -- CHANGE THIS
        v_table       CONSTANT VARCHAR2(128) := 'YOUR_TABLE_NAME'; -- CHANGE THIS
        v_truncated            PLS_INTEGER := 0;
      BEGIN
        FOR rec IN (
          SELECT partition_name
          FROM   dba_tab_partitions
          WHERE  owner       = v_owner
            AND  table_name  = v_table
            AND  partition_name NOT IN ('P_202511', 'P_202510')   -- KEEP these two
        )
        LOOP
          EXECUTE IMMEDIATE
            'ALTER TABLE ' || v_owner || '.' || v_table ||
            ' TRUNCATE PARTITION ' || rec.partition_name ||
            ' DROP STORAGE';

          v_truncated := v_truncated + 1;

          -- Optional: commit every 20 partitions so redo log doesn’t explode
          IF MOD(v_truncated, 20) = 0 THEN
            COMMIT;
          END IF;
        END LOOP;

        COMMIT;

        DBMS_OUTPUT.PUT_LINE('SUCCESS: Truncated ' || v_truncated || ' partitions from ' ||
                             v_owner || '.' || v_table);

      EXCEPTION
        WHEN OTHERS THEN
          ROLLBACK;
          DBMS_OUTPUT.PUT_LINE('ERROR: ' || SQLERRM);
          RAISE;
      END;
    ]',
    start_date => SYSTIMESTAMP,
    enabled    => TRUE,
    auto_drop  => TRUE,
    comments   => 'Fast background TRUNCATE of all old partitions except last 2'
  );
END;
/
============================
BEGIN
  DBMS_SCHEDULER.CREATE_JOB (
    job_name        => 'CLEANUP_OLD_PARTITIONS_01',
    job_type        => 'PLSQL_BLOCK',
    job_action      => q'[
      DECLARE
        v_cnt_dropped  PLS_INTEGER := 0;
        v_cnt_rebuilt  PLS_INTEGER := 0;
      BEGIN
        -- 1. Drop old partitions with PARALLEL 12 (fastest possible segment drop)
        FOR rec IN (
          SELECT partition_name
          FROM   user_tab_partitions
          WHERE  table_name = 'YOUR_TABLE_NAME'            -- CHANGE THIS
            AND  partition_name NOT IN ('P_202511', 'P_202510')  -- CHANGE THESE
        ) LOOP
          EXECUTE IMMEDIATE
            'ALTER TABLE YOUR_TABLE_NAME DROP PARTITION ' || rec.partition_name ||
            ' PARALLEL 12';
          v_cnt_dropped := v_cnt_dropped + 1;
        END LOOP;

        DBMS_OUTPUT.PUT_LINE('Dropped ' || v_cnt_dropped || ' partitions with PARALLEL 12');

        -- 2. Rebuild global indexes with PARALLEL 12
        FOR idx IN (
          SELECT index_name
          FROM   user_indexes
          WHERE  table_name = 'YOUR_TABLE_NAME'
            AND (status = 'UNUSABLE' OR partitioned = 'NO')
        ) LOOP
          EXECUTE IMMEDIATE
            'ALTER INDEX ' || idx.index_name || 
            ' REBUILD ONLINE PARALLEL 12';
          v_cnt_rebuilt := v_cnt_rebuilt + 1;
        END LOOP;

        DBMS_OUTPUT.PUT_LINE('Rebuilt ' || v_cnt_rebuilt || ' global indexes with PARALLEL 12');

      EXCEPTION
        WHEN OTHERS THEN
          DBMS_OUTPUT.PUT_LINE('ERROR: ' || SQLERRM);
          RAISE;
      END;]',
    start_date      => SYSTIMESTAMP,
    enabled         => TRUE,
    auto_drop       => TRUE,
    comments        => 'Ultra-fast partition purge - DROP + REBUILD both with PARALLEL 12'
  );
END;
/