modified on 28 March 2008 at 17:18 ••• 788 views

Mgmt p get max compression order

From Oramosswiki

Jump to: navigation, search
REM *************************************************************************************************
REM *
REM * File: mgmt_p_get_max_compression_order.prc
REM *
REM * Author: Jeff Moss
REM *
REM * Purpose: Provides facility to get the compressability of each column in a given table
REM *          Can be run at Table or Partition level and for a defined sample size.
REM *          Can specify string that must be the prefix order by columns - routine will work out
REM *          compressability of all the other columns individually with this prefix, e.g.
REM *           TABLE X(col1, col2, col3, col4, col5, col6)
REM *           specifying 'col3','col2','col5' as the prefix strings will make the routine work out compression
REM *           for col1 with order by col3,col2,col5,col1
REM *              ,col4 with order by col3,col2,col5,col4
REM *              and col6 with order by col3,col2,col5,col6
REM *            
REM * Execution: 
REM *   To just get the compressability of each individual column...
REM *     exec mgmt_p_get_max_compress_order(USER,'BIG_TABLE','PARTITION1',10000);
REM *   Then to get the compressability of the remaining columns when ordered by 
REM *     a commonly used access path:
REM *     exec mgmt_p_get_max_compress_order(USER,'BIG_TABLE','PARTITION1',10,'COL1','COL2','COL3');
REM *            
REM * History:
REM *
REM * Date         Author    Change
REM * 02-NOV-2005  Jeff Moss Initial version
REM * 26-NOV-2005  Jeff Moss Added facility to specify the prefix order by columns as suggested by
REM *                         David Aldridge.
REM * 25-JAN-2006  Jeff Moss Removed hard coded 32K block size dependency!
REM * 12-AUG-2007  Jeff Moss Removed hard coded schema name of AE_MGMT!
REM *************************************************************************************************
CREATE OR REPLACE
PROCEDURE mgmt_p_get_max_compress_order(p_table_owner IN VARCHAR2 DEFAULT USER
                                       ,p_table_name IN VARCHAR2
                                       ,p_partition_name IN VARCHAR2 DEFAULT NULL
                                       ,p_sample_size IN NUMBER DEFAULT 1000000
                                       ,p_prefix_column1 IN VARCHAR2 DEFAULT NULL
                                       ,p_prefix_column2 IN VARCHAR2 DEFAULT NULL
                                       ,p_prefix_column3 IN VARCHAR2 DEFAULT NULL
                                       ) IS
                                      

  CURSOR c_tab_columns(b_table_name VARCHAR2
                      ,b_table_owner VARCHAR2
                      ,b_prefix_column1 VARCHAR2
                      ,b_prefix_column2 VARCHAR2
                      ,b_prefix_column3 VARCHAR2) IS
    SELECT column_name
    ,      column_id
    FROM   dba_tab_columns
    WHERE  table_name = b_table_name
    AND    owner = b_table_owner
    AND    column_name != NVL(b_prefix_column1,'1234567890123456789012345678901')
    AND    column_name != NVL(b_prefix_column2,'1234567890123456789012345678901')
    AND    column_name != NVL(b_prefix_column3,'1234567890123456789012345678901')
    ORDER BY column_id;
  CURSOR c_tables(b_unique_id VARCHAR2,b_table_name VARCHAR2,b_table_owner VARCHAR2) IS
    SELECT dt.table_name
    ,      dt.blocks
    ,      dt.num_rows
    ,      dtc.column_name
    FROM   dba_tables dt
    ,      dba_tab_columns dtc
    WHERE  dt.table_name LIKE 'TEMP_COL%'||b_unique_id
    AND    dtc.table_name = b_table_name
    AND    dtc.owner = b_table_owner
    AND    dt.owner = b_table_owner
    AND    TO_NUMBER(SUBSTR(dt.table_name,10,3)) = dtc.column_id
    ORDER BY dt.blocks,dt.num_rows;

  CURSOR c_get_block_size IS
    SELECT (value / 1024) block_size_k
    FROM   v$parameter 
    WHERE  name = 'db_block_size';

  l_block_size NUMBER;
  l_unique_id NUMBER;
  l_master_table VARCHAR2(30);
  l_sql VARCHAR2(32767);
  l_column_table VARCHAR2(30);
  l_order_by_prefix VARCHAR2(254);
  
  e_942 EXCEPTION;
  PRAGMA EXCEPTION_INIT(e_942,-942);
  e_invalid_prefix_column EXCEPTION;

  FUNCTION mgmt_f_validate_prefix_column(p_table_owner IN VARCHAR2
                                        ,p_table_name IN VARCHAR2
                                        ,p_prefix_column IN VARCHAR2) RETURN BOOLEAN IS
    CURSOR c_column(b_table_owner VARCHAR2
                   ,b_table_name VARCHAR2
                   ,b_prefix_name VARCHAR2) IS
      SELECT 1
      FROM   dba_tab_columns
      WHERE  owner = b_table_owner
      AND    table_name = b_table_name
      AND    column_name = b_prefix_name;
  BEGIN
    FOR r_column IN c_column(p_table_owner
                            ,p_table_name
                            ,p_prefix_column) LOOP
      RETURN TRUE; -- we found a match
    END LOOP;
    RETURN FALSE; -- nothing found so no match
  END mgmt_f_validate_prefix_column;

BEGIN
  -- output paramters...
  dbms_output.put_line(LPAD('-',100,'-'));
  dbms_output.put_line('Running mgmt_p_get_max_compress_order...');
  dbms_output.put_line(LPAD('-',100,'-'));
  dbms_output.put_line('Table        : '||p_table_name);
  dbms_output.put_line('Sample Size  : '||TO_CHAR(p_sample_size));

  -- get a unique id to use for the working tables
  l_unique_id := TO_CHAR(SYSDATE,'DDMMYYYYHH24MISS');
  dbms_output.put_line('Unique Run ID: '||TO_CHAR(l_unique_id));

  -- now get block size from 
  OPEN c_get_block_size;
  FETCH c_get_block_size INTO l_block_size;
  IF c_get_block_size%NOTFOUND OR c_get_block_size%NOTFOUND IS NULL THEN
    CLOSE c_get_block_size;
    RAISE_APPLICATION_ERROR(-20001,'Cannot get block size!');
  ELSE
    CLOSE c_get_block_size;
  END IF;
  
  -- Now if we have any p_prefix_columns specified then we need to factor those in...
  -- Lets check the prefix_columns first to see if they are valid...
  IF p_prefix_column1 IS NOT NULL THEN
    IF NOT mgmt_f_validate_prefix_column(p_table_owner,p_table_name,p_prefix_column1) THEN
      RAISE e_invalid_prefix_column;
    END IF;
  END IF;

  IF p_prefix_column2 IS NOT NULL THEN
    IF NOT mgmt_f_validate_prefix_column(p_table_owner,p_table_name,p_prefix_column2) THEN
      RAISE e_invalid_prefix_column;
    END IF;
  END IF;

  IF p_prefix_column3 IS NOT NULL THEN
    IF NOT mgmt_f_validate_prefix_column(p_table_owner,p_table_name,p_prefix_column3) THEN
      RAISE e_invalid_prefix_column;
    END IF;
  END IF;

  -- Ok, we now have valid prefix columns...
  -- let's create the prefix string for the order by clause
  l_order_by_prefix := (CASE WHEN p_prefix_column1 IS NOT NULL THEN p_prefix_column1||',' END)||
                       (CASE WHEN p_prefix_column2 IS NOT NULL THEN p_prefix_column2||',' END)||
                       (CASE WHEN p_prefix_column3 IS NOT NULL THEN p_prefix_column3||',' END);
  
  dbms_output.put_line('ORDER BY Prefix: '||SUBSTR(l_order_by_prefix,1,LENGTH(l_order_by_prefix)-1));

  dbms_output.put_line(LPAD('-',100,'-'));

  l_master_table := 'TEMP_MASTER_'||TO_CHAR(l_unique_id);
  l_sql := 'CREATE TABLE '||l_master_table||
           ' NOLOGGING COMPRESS AS SELECT * FROM '||p_table_owner||'.'||p_table_name;

  IF p_partition_name IS NOT NULL THEN
    l_sql := l_sql||' PARTITION('||p_partition_name||')';
  END IF;

  l_sql := l_sql||' WHERE ROWNUM <= '||TO_CHAR(p_sample_size);

  dbms_output.put_line('Creating MASTER Table  : '||l_master_table);
  execute immediate(l_sql);

  FOR r_tab_columns IN c_tab_columns(p_table_name,p_table_owner,p_prefix_column1,p_prefix_column2,p_prefix_column3) LOOP
    dbms_output.put_line('Creating COLUMN Table '||TO_CHAR(r_tab_columns.column_id)||': '||r_tab_columns.column_name);
    l_column_table := 'TEMP_COL_'||LPAD(TO_CHAR(r_tab_columns.column_id),3,'0')||'_'||TO_CHAR(l_unique_id);
    l_sql := 'CREATE TABLE '||l_column_table||
             ' NOLOGGING COMPRESS AS SELECT * FROM '||l_master_table||
             ' ORDER BY '||l_order_by_prefix||r_tab_columns.column_name;
    execute immediate(l_sql);
    DBMS_STATS.GATHER_TABLE_STATS (ownname=>USER,tabname=>l_column_table,estimate_percent=>100);
  END LOOP;

  -- Now print out individual column compressions...
  dbms_output.put_line(LPAD('-',100,'-'));
  dbms_output.put_line('The output below lists each column in the table and the number of blocks/rows and space');
  dbms_output.put_line(' used when the table data is ordered by only that column, or in the case where a prefix');
  dbms_output.put_line(' has been specified, where the table data is ordered by the prefix and then that column.');
  dbms_output.put_line(' ');
  dbms_output.put_line('From this one can determine if there is a specific ORDER BY which can be applied to');
  dbms_output.put_line(' to the data in order to maximise compression within the table whilst, in the case of a');
  dbms_output.put_line(' a prefix being present, ordering data as efficiently as possible for the most common ');
  dbms_output.put_line(' access path(s).');
  dbms_output.put_line(LPAD('-',100,'-'));
  dbms_output.put_line('NAME                           COLUMN                         BLOCKS       ROWS         SPACE_GB');
  dbms_output.put_line('============================== ============================== ============ ============ ========');
  FOR r_tables IN c_tables(l_unique_id,p_table_name,p_table_owner) LOOP
    dbms_output.put_line(RPAD(r_tables.table_name,31)||
	                     RPAD(r_tables.column_name,30)||
	                     LPAD(TO_CHAR(r_tables.blocks),13)||
	                     LPAD(TO_CHAR(r_tables.num_rows),13)||
	                     LPAD(TO_CHAR(ROUND(r_tables.blocks * l_block_size / (1024*1024),4)),9)
                        );
  END LOOP;

  dbms_output.put_line(LPAD('-',100,'-'));

  -- First drop the Master table...
  l_sql := 'DROP TABLE '||l_master_table;
  BEGIN
    execute immediate(l_sql);
  EXCEPTION
    WHEN e_942 THEN NULL;
  END;   

  -- Next drop the Column tables...
  FOR r_tab_columns IN c_tab_columns(p_table_name,p_table_owner,p_prefix_column1,p_prefix_column2,p_prefix_column3) LOOP
    l_sql := 'DROP TABLE TEMP_COL_'||LPAD(TO_CHAR(r_tab_columns.column_id),3,'0')||'_'||TO_CHAR(l_unique_id);
    BEGIN
      execute immediate(l_sql);
    EXCEPTION
      WHEN e_942 THEN NULL;
    END;   
  END LOOP;
EXCEPTION
  WHEN e_invalid_prefix_column THEN
    RAISE_APPLICATION_ERROR(-20002,'mgmt_p_get_max_compress_order: Invalid prefix column specified.');
  WHEN OTHERS THEN
    -- Clear up the schema
    -- First drop the Master table...
    l_sql := 'DROP TABLE '||l_master_table;
    execute immediate(l_sql);

    -- Next drop the Column tables...
    FOR r_tab_columns IN c_tab_columns(p_table_name,p_table_owner,p_prefix_column1,p_prefix_column2,p_prefix_column3) LOOP
      l_sql := 'DROP TABLE TEMP_COL_'||LPAD(TO_CHAR(r_tab_columns.column_id),3,'0')||'_'||TO_CHAR(l_unique_id);
      execute immediate(l_sql);
    END LOOP;

  -- Now raise an application error...
  RAISE_APPLICATION_ERROR(-20001,'mgmt_p_get_max_compress_order encountered error: '||SQLERRM);
  
END mgmt_p_get_max_compress_order;
/