create or replace
package body just_stats
as
-- Definitions of custom types.
------
type colcounts
is
table of number index by binary_integer;
type coldistinct
is
table of number index by binary_integer;
type colmin
is
table of varchar2(32000) index by binary_integer;
type colmax
is
table of varchar2(32000)index by binary_integer;
type colhist
is
table of varchar2(32000) index by binary_integer;
type colhiststats
is
table of dbms_stats.statrec index by binary_integer;
type colhiststatsnum
is
table of dbms_stats.numarray index by binary_integer;
type colhiststatsvar
is
table of dbms_stats.chararray index by binary_integer;
type colhiststatsdate
is
table of dbms_stats.datearray index by binary_integer;
------
-- Definiotion of package variables that would hold manually computed statistics
------
-- Column statistics
l_colcnt colcounts;
l_coldis coldistinct;
l_colmin colmin;
l_colmax colmax ;
l_colhist colhist;
l_colhiststats colhiststats;
l_colhiststatsn colhiststatsnum ;
l_colhiststatsv colhiststatsvar ;
l_colhiststatsd colhiststatsdate ;
-- Table statistics
l_tab_cnt number ;
-- Index statistics
l_ind_cnt number;
l_ind_leaf_blocks number;
l_ind_distinct number;
l_ind_clustering_factor number;
-- Saving index statistics in the DD using an autonomous transaction
procedure save_index_stats(
p_owner varchar2 ,
p_index varchar2)
is
pragma autonomous_transaction;
begin
dbms_stats.set_index_stats(ownname => p_owner,
indname => p_index,
numrows => l_ind_cnt,
numlblks => l_ind_leaf_blocks,
numdist => l_ind_distinct,
clstfct => l_ind_clustering_factor ,
no_invalidate => FALSE);
end;
-- Saving table or table partition statistics in the DD using an autonomous transaction
procedure save_table_stats(
p_owner varchar2 ,
p_table varchar ,
p_scope varchar2 ,
p_partname varchar2 ,
p_cnt number)
is
pragma autonomous_transaction;
begin
if p_scope = 'GLOBAL' then
dbms_stats.set_table_stats( ownname => p_owner ,
tabname => p_table ,
numrows => p_cnt ,
no_invalidate => FALSE);
else
dbms_stats.set_table_stats( ownname => p_owner ,
tabname => p_table ,
partname => p_partname ,
numrows => p_cnt,
no_invalidate => FALSE);
end if;
end;
-- Saving column statistics in the DD using an autonomous transaction
procedure save_col_stats(
p_owner varchar2 ,
p_table varchar ,
p_scope varchar2 ,
p_partname varchar2 )
is
pragma autonomous_transaction;
type colhist
is
table of varchar2(32000) index by binary_integer;
l_srec dbms_stats.statrec ;
l_novals dbms_stats.numarray;
l_chvals dbms_stats.chararray;
l_dtvals dbms_stats.datearray;
l_nulls number;
l_min_num number;
l_max_num number;
l_min_char varchar2(100);
l_max_char varchar2(100);
l_min_date date;
l_max_date date;
m_distcnt number;
m_density number;
m_nullcnt number;
m_avgclen number;
begin
-- Loops through all columns in the table
for i in (
select
column_name ,
column_id ,
data_type
from
dba_tab_columns
where
table_name = p_table
and owner = p_owner
order by
column_id
)
loop
-- Checks the data type.
if
i.data_type = 'NUMBER'
or i.data_type = 'FLOAT'
or i.data_type = 'VARCHAR2'
or i.data_type = 'DATE'
or substr(i.data_type,1,9) = 'TIMESTAMP' then
l_nulls :=l_tab_cnt - l_colcnt ( i.column_id);
l_srec.epc := 2;
l_srec.eavs := null;
-- Histograms are supported for tables only
if p_scope = 'GLOBAL' then
-- Checking for frequency histogram on that column
if l_colhist(i.column_id) = 'FREQUENCY' then
if i.data_type = 'NUMBER' then
-- Populate l_srec structure for NUMBER from the package variables
l_srec.epc:=l_colhiststats(i.column_id).bkvals.count;
l_srec.bkvals:=l_colhiststats(i.column_id).bkvals;
dbms_stats.prepare_column_values (l_srec, l_colhiststatsn(i.column_id));
elsif i.data_type = 'VARCHAR2' then
-- Populate l_srec structure for VARCHAR2 from the package variables
l_srec.epc :=l_colhiststats(i.column_id).bkvals.count;
l_srec.bkvals:=l_colhiststats(i.column_id).bkvals;
dbms_stats.prepare_column_values (l_srec, l_colhiststatsv(i.column_id));
elsif i.data_type = 'DATE' then
-- Populate l_srec structure for DATE from the package variables
l_srec.epc :=l_colhiststats(i.column_id).bkvals.count;
l_srec.bkvals:=l_colhiststats(i.column_id).bkvals;
dbms_stats.prepare_column_values (l_srec, l_colhiststatsd(i.column_id));
end if;
-- Checking for height balanced histogram on that column
elsif l_colhist(i.column_id) ='HEIGHT BALANCED' then
if i.data_type = 'NUMBER' then
-- Populate l_srec structure for NUMBER from the package variables
l_srec.epc :=l_colhiststatsn(i.column_id).count;
l_srec.bkvals:=null;
dbms_stats.prepare_column_values (l_srec, l_colhiststatsn(i.column_id));
elsif i.data_type = 'VARCHAR2' then
-- Populate l_srec structure for VARCHAR2 from the package variables
l_srec.epc :=l_colhiststatsv(i.column_id).count;
l_srec.bkvals:=null;
dbms_stats.prepare_column_values (l_srec, l_colhiststatsv(i.column_id));
elsif i.data_type = 'DATE' then
-- Populate l_srec structure for DATE from the package variables
l_srec.epc :=l_colhiststatsd(i.column_id).count;
l_srec.bkvals:=null;
dbms_stats.prepare_column_values (l_srec, l_colhiststatsd(i.column_id));
end if;
end if;
end if;
if ( i.data_type = 'NUMBER' or i.data_type = 'FLOAT' ) then
if l_colhist(i.column_id) is null then
-- Populate l_srec structure for NUMBER from the package variables
l_srec.bkvals :=null;
l_min_num:=to_number(l_colmin(i.column_id));
l_max_num:=to_number(l_colmax(i.column_id));
l_novals := dbms_stats.numarray (l_min_num,l_max_num) ;
dbms_stats.prepare_column_values (l_srec, l_novals);
end if;
elsif ( i.data_type = 'VARCHAR2' ) then
if l_colhist(i.column_id) is null then
-- Populate l_srec structure for VARCHAR2 from the package variables
l_srec.bkvals :=null;
l_min_char := substr(l_colmin(i.column_id),1,20);
l_max_char := substr( l_colmax(i.column_id), 1, 20) ;
l_chvals := dbms_stats.chararray( l_min_char , l_max_char);
dbms_stats.prepare_column_values (l_srec, l_chvals);
end if;
elsif ( i.data_type = 'DATE' or substr(i.data_type,1,9) = 'TIMESTAMP' ) then
if l_colhist(i.column_id) is null then
-- Populate l_srec structure for DATE from the package variables
l_srec.bkvals :=null;
l_min_date := to_date(l_colmin(i.column_id),'MM-DD-YYYY:HH24:MI:SS');
l_max_date := to_date( l_colmax(i.column_id), 'MM-DD-YYYY:HH24:MI:SS');
l_dtvals := dbms_stats.datearray( l_min_date , l_max_date);
dbms_stats.prepare_column_values (l_srec, l_dtvals);
end if ;
end if;
-- Write the statistics in into the data dictionary
if p_scope = 'GLOBAL' then
-- Table
dbms_stats.set_column_stats (ownname => p_owner , tabname => p_table, colname => i.column_name, distcnt => l_coldis(i.column_id) , nullcnt => l_nulls , srec => l_srec , no_invalidate => FALSE);
else
-- Partition
dbms_stats.set_column_stats (ownname => p_owner , tabname => p_table, partname => p_partname , colname => i.column_name, distcnt => l_coldis(i.column_id) , nullcnt => l_nulls , srec => l_srec , no_invalidate => FALSE);
end if;
end if;
end loop;
end;
-- Gather statistics for tables and partitions by issuing dynamically constructed SQLs
procedure gather_table_part_stats(
ownname varchar2 ,
tabname varchar2 ,
p_scope varchar2 ,
partname varchar2 default null ,
estimate_percent number default null ,
method_opt varchar2 default null ,
p_degree number default null)
as
--type l_numnum is table of number_number;
type l_numnum
is
table of number index by binary_integer;
l_colswithhistograms l_numnum ;
l_colswithhistogramssize l_numnum ;
l_table_q varchar2(32000);
l_out varchar2(32000);
l_column_q varchar2(32000);
l_rest varchar2(32000);
l_int_cnt number ;
l_dash_cnt number;
l_t varchar2(1000);
l_cnt number;
l_res_num number;
l_res_varchar2 varchar2(32000);
l_res_date date;
l_res_timestamp timestamp(9);
l_c_name number ;
l_d number;
col_cnt integer;
rec_tab dbms_sql.desc_tab2;
r number;
l_method_opt_parsing varchar2(32000);
l_column varchar2(32000);
l_col_id number;
l_col_sz varchar2(100);
begin
-- Parsing method_opt to get which columns should get histogrms
-- and how many buckets are to be used
-- I am sure there is a better way....
for i in (select column_id from dba_tab_columns where owner = ownname and table_name = tabname) loop
l_colswithhistograms(i.column_id):=0;
l_colswithhistogramssize(i.column_id):=0;
end loop;
l_method_opt_parsing:=ltrim(method_opt);
if upper(substr(l_method_opt_parsing,1,3)) = 'FOR' then
l_method_opt_parsing:=ltrim(substr(l_method_opt_parsing,4));
if upper(substr(l_method_opt_parsing,1,7)) = 'COLUMNS' then
l_method_opt_parsing:=ltrim(substr(l_method_opt_parsing,8));
while (length (l_method_opt_parsing) > 0) loop
if instr(l_method_opt_parsing,' ') = 0 and instr(l_method_opt_parsing,',') = 0 then
l_column:=upper(ltrim(l_method_opt_parsing));
else
if (instr(l_method_opt_parsing,' ') > 0 and instr(l_method_opt_parsing,',') = 0)
or (instr(l_method_opt_parsing,' ') > 0 and instr(l_method_opt_parsing,' ') < instr(l_method_opt_parsing,',') and instr(l_method_opt_parsing,',') > 0) then
l_column:=upper(ltrim(substr(l_method_opt_parsing,1,instr(l_method_opt_parsing,' '))));
else
l_column:=upper(ltrim(substr(l_method_opt_parsing,1,instr(l_method_opt_parsing,',')-1)));
end if;
end if;
select column_id
into l_col_id
from dba_tab_columns
where owner = ownname
and table_name = tabname
and column_name = ltrim(rtrim(l_column));
l_colswithhistograms(l_col_id):=1;
if instr(l_method_opt_parsing,' ') = 0 and instr(l_method_opt_parsing,',') = 0 then
l_method_opt_parsing:=null;
else
if (instr(l_method_opt_parsing,' ') > 0 and instr(l_method_opt_parsing,' ') < instr(l_method_opt_parsing,',')) or (instr(l_method_opt_parsing,',') = 0) then
l_method_opt_parsing:=ltrim(substr(l_method_opt_parsing,instr(l_method_opt_parsing,' ')));
else
l_method_opt_parsing:=ltrim(substr(l_method_opt_parsing,instr(l_method_opt_parsing,',')-1));
end if;
end if;
if upper(substr(l_method_opt_parsing,1,4)) = 'SIZE' then
l_method_opt_parsing:=ltrim(substr(l_method_opt_parsing,5));
if instr(l_method_opt_parsing,',') > 0 then
l_col_sz:=upper(ltrim(substr(l_method_opt_parsing,1,instr(l_method_opt_parsing,',')-1)));
else
l_col_sz:=upper(ltrim(l_method_opt_parsing));
end if;
l_colswithhistogramssize(l_col_id):=to_number(l_col_sz);
l_method_opt_parsing:=ltrim(substr(l_method_opt_parsing,instr(l_method_opt_parsing,',')));
end if ;
if instr(l_method_opt_parsing,',') = 0 then
l_method_opt_parsing:=null;
else
l_method_opt_parsing:=ltrim(substr(l_method_opt_parsing,instr(l_method_opt_parsing,',')+1));
end if;
end loop;
end if;
end if;
-- Building the query text in l_table_q
-- Startinng with count
if p_degree is null then
-- No parallelism
l_table_q:=' select /*+ no_parallel(t) no_parallel_index(t) dbms_stats cursor_sharing_exact use_weak_name_resl dynamic_sampling(0) no_monitoring */ count(*) ';
else
-- With parallelism
l_table_q:=' select /*+ parallel('||p_degree||') dbms_stats cursor_sharing_exact use_weak_name_resl dynamic_sampling(0) no_monitoring */ count(*) ';
end if;
-- For each column in the table
for i in
(select
column_name ,
data_type
from
dba_tab_columns
where
owner = ownname
and table_name = tabname
order by
column_id
)
loop
-- Checking if the column type is among the supported types
if
i.data_type = 'NUMBER'
or i.data_type = 'FLOAT'
or i.data_type = 'VARCHAR2'
or i.data_type = 'DATE'
or substr(i.data_type,1,9) = 'TIMESTAMP' then
-- Add entries for number of not null records, number of distinct records, min and max values
l_table_q :=l_table_q||',count(t.'||i.column_name||')';
l_table_q :=l_table_q||',count(distinct(t.'||i.column_name||'))';
l_table_q :=l_table_q||',min(t.'||i.column_name||')';
l_table_q :=l_table_q||',max(t.'||i.column_name||')';
end if;
end loop;
if p_scope = 'GLOBAL' then
-- Add the "from" clause for a table
if estimate_percent is not null then
-- Add "sample" clause
l_table_q :=l_table_q||' from '||ownname||'.'||tabname||' sample ('||estimate_percent||' ) t' ;
else
-- No "sample" clause - full table scan
l_table_q:=l_table_q||' from '||ownname||'.'||tabname||' t' ;
end if;
else
-- Add the "from" clause for a partition
if estimate_percent is not null then
-- Add "sample" calause
l_table_q :=l_table_q||' from '||ownname||'.'||tabname||' partition ( '||partname||' ) sample ( '||estimate_percent||' ) t ' ;
else
-- No "sample" clause - full table scan
l_table_q:=l_table_q||' from '||ownname||'.'||tabname||' partition ( '||partname||' ) t ' ;
end if;
end if;
-- Create a cursor
l_c_name := dbms_sql.open_cursor;
-- Parse the SQL query in l_table_q
dbms_sql.parse(l_c_name, l_table_q, dbms_sql.native);
-- l_cnt would be used to go through all columns in the dynamically created SQL statement (l_table_q)
l_cnt:=1;
-- Defining the first column - the number of records in the table
dbms_sql.define_column (l_c_name, l_cnt ,l_res_num );
-- For each column in the table, in the same order as when l_table_q was generated
for i in
(select
column_name ,
data_type ,
data_length
from
dba_tab_columns
where
owner = ownname
and table_name = tabname
order by
column_id
) loop
-- Checking if the column type is among the supported types.
-- The check should be identical to the one used when generating the query
if
i.data_type = 'NUMBER'
or i.data_type = 'FLOAT'
or i.data_type = 'VARCHAR2'
or i.data_type = 'DATE'
or substr(i.data_type,1,9) = 'TIMESTAMP'
then
-- Defining the first of each column group - the number of non-null values
l_cnt :=l_cnt+1;
dbms_sql.define_column (l_c_name, l_cnt ,l_res_num );
-- Defining the second of each column group - the number of distinct values
l_cnt:=l_cnt+1;
dbms_sql.define_column (l_c_name, l_cnt ,l_res_num );
-- Since the next two columns are not necessary numbers
-- we have to check their type and supply the correct arguments to dbms_sql.define_column
if ( i.data_type = 'NUMBER'
or i.data_type = 'FLOAT') then
-- Define the the third and forth column as a number
l_cnt :=l_cnt+1;
dbms_sql.define_column (l_c_name, l_cnt ,l_res_num );
l_cnt:=l_cnt+1;
dbms_sql.define_column (l_c_name, l_cnt ,l_res_num );
elsif i.data_type = 'VARCHAR2' then
-- Define the the third and forth column as a varchar
l_cnt :=l_cnt+1;
dbms_sql.define_column (l_c_name, l_cnt ,l_res_varchar2 ,i.data_length);
l_cnt:=l_cnt+1;
dbms_sql.define_column (l_c_name, l_cnt ,l_res_varchar2 ,i.data_length);
elsif i.data_type = 'DATE' then
-- Define the the third and forth column as a date
l_cnt :=l_cnt+1;
dbms_sql.define_column (l_c_name, l_cnt ,l_res_date );
l_cnt:=l_cnt+1;
dbms_sql.define_column (l_c_name, l_cnt ,l_res_date );
elsif substr(i.data_type,1,9) = 'TIMESTAMP' then
-- Define the the third and forth column as a timestamp
l_cnt :=l_cnt+1;
dbms_sql.define_column (l_c_name, l_cnt ,l_res_timestamp );
l_cnt:=l_cnt+1;
dbms_sql.define_column (l_c_name, l_cnt ,l_res_timestamp );
end if;
end if;
end loop;
-- Execute the query
l_d := dbms_sql.execute(l_c_name);
-- Describe the returned columns
dbms_sql.describe_columns2(l_c_name ,col_cnt , rec_tab);
r:= dbms_sql.fetch_rows (l_c_name);
-- Fetch the first row. The query will return only one row
l_dash_cnt:=1;
-- Get the value of the first column - number of records into l_res_num
l_cnt :=1;
dbms_sql.column_value (l_c_name, l_cnt ,l_res_num );
-- Set the package variable
l_tab_cnt:=l_res_num;
-- For each column in the table, in the same order as when l_table_q was generated
for i in
(select
column_id,
column_name ,
data_type
from
dba_tab_columns
where
owner = ownname
and table_name = tabname
order by
column_id
)
loop
-- Checking if the column type is among the supported types.
-- The check should be identical to the one used when generating the query
if
i.data_type = 'NUMBER'
or i.data_type = 'FLOAT'
or i.data_type = 'VARCHAR2'
or i.data_type = 'DATE'
or substr(i.data_type,1,9) = 'TIMESTAMP'
then
-- Get the second column, number of non-null values, into l_res_num
l_cnt :=l_cnt+1;
dbms_sql.column_value (l_c_name, l_cnt ,l_res_num );
-- Save that number into package variable l_colcnt
-- l_colcnt is an array that would store the values for all columns
l_colcnt(i.column_id):=l_res_num;
-- Get the third column, number of distinct values, into l_res_num
l_cnt :=l_cnt+1;
dbms_sql.column_value (l_c_name, l_cnt ,l_res_num );
-- Save that number into package variable l_coldis
-- l_coldis is an array that would store the values for all columns
l_coldis(i.column_id):=l_res_num;
-- Since the next two columns are not necessary numbers
-- We have to convert them to CHAR and stored them in an array
if i.data_type = 'NUMBER' or i.data_type = 'FLOAT' then
-- Save the min number into l_res_num
-- convert l_res_num to CHAR save it into l_colmin, a package array that stores min values for all columns
l_cnt :=l_cnt+1;
dbms_sql.column_value (l_c_name, l_cnt ,l_res_num );
l_colmin(i.column_id):=to_char(l_res_num);
-- Save the max number into l_res_num
-- convert l_res_num to CHAR save it into l_colmax, a package array that stores min values for all columns
l_cnt :=l_cnt+1;
dbms_sql.column_value (l_c_name, l_cnt ,l_res_num );
l_colmax(i.column_id):=to_char(l_res_num);
elsif i.data_type = 'VARCHAR2' then
-- Save the min value into l_res_varchar2
-- convert l_res_varchar2 to CHAR save it into l_colmin, a package array that stores min values for all columns
l_cnt :=l_cnt+1;
dbms_sql.column_value (l_c_name, l_cnt ,l_res_varchar2 );
l_colmin(i.column_id):=l_res_varchar2 ;
-- Save the max value into l_res_varchar2
-- convert l_res_varchar2 to CHAR save it into l_colmax, a package array that stores max values for all columns
l_cnt :=l_cnt+1;
dbms_sql.column_value (l_c_name, l_cnt ,l_res_varchar2 );
l_colmax(i.column_id):=l_res_varchar2 ;
elsif (i.data_type = 'DATE' ) then
-- Save the min date into l_res_date
-- convert l_res_date to CHAR save it into l_colmin, a package array that stores min values for all columns
l_cnt :=l_cnt+1;
dbms_sql.column_value (l_c_name, l_cnt ,l_res_date);
l_colmin(i.column_id):=to_char(l_res_date,'MM-DD-YYYY:HH24:MI:SS');
-- Save the max date into l_res_date
-- convert l_res_date to CHAR save it into l_colmax, a package array that stores max values for all columns
l_cnt :=l_cnt+1;
dbms_sql.column_value (l_c_name, l_cnt ,l_res_date );
l_colmax(i.column_id) :=to_char(l_res_date,'MM-DD-YYYY:HH24:MI:SS');
elsif substr(i.data_type,1,9) = 'TIMESTAMP' then
-- Save the min timestamp into l_res_timestamp (Note - loss of precision may occur)
-- convert l_res_timestam to CHAR and save it into l_colmin, a package array that stores min values for all columns
l_cnt :=l_cnt+1;
dbms_sql.column_value (l_c_name, l_cnt ,l_res_timestamp);
l_colmin(i.column_id):=to_char(l_res_timestamp,'MM-DD-YYYY:HH24:MI:SS');
-- Save the max timestamp into l_res_timestamp (Note - loss of precision may occur)
-- convert l_res_timestam to CHAR and save it into l_colmax, a package array that stores max values for all columns
l_cnt :=l_cnt+1;
dbms_sql.column_value (l_c_name, l_cnt ,l_res_timestamp );
l_colmax(i.column_id):=to_char(l_res_timestamp,'MM-DD-YYYY:HH24:MI:SS');
end if;
-- Check if table or partition. Only tables can have histograms
if p_scope = 'GLOBAL' then
-- Check if a histogrms has to be created on the column
-- The package does not have any "AUTO" functionality, so the invoker must specifically request a histogram
if l_colswithhistograms (i.column_id) = 1 then
-- Deciding what type of histogram to create
if (l_colswithhistogramssize(i.column_id) > 1 and l_colswithhistogramssize(i.column_id) < 254 ) or (l_coldis(i.column_id) < 254) then
-- Creating frequency histogram
l_colhist(i.column_id) :='FREQUENCY';
if l_colswithhistogramssize(i.column_id) > 1 and l_colswithhistogramssize(i.column_id) < 254 then
if i.data_type = 'VARCHAR2' then
l_column_q :='SELECT * FROM ( SELECT * FROM (SELECT * FROM (SELECT substr('||i.column_name||',1,32) col , count(*) cnt from '||ownname||'.'||tabname||' group by SUBSTR('||i.column_name||',1,32) ) order by cnt desc ) where rownum <='||l_colswithhistogramssize(i.column_id)||') order by col';
else
l_column_q :='SELECT * FROM ( SELECT * FROM (SELECT * FROM (SELECT '||i.column_name||' col , count(*) cnt from '||ownname||'.'||tabname||' group by '||i.column_name||' ) order by cnt desc ) where rownum <='||l_colswithhistogramssize(i.column_id)||') order by col';
end if;
else
-- Specify the SQL text of the query that would get the frequency data
if i.data_type = 'VARCHAR2' then
l_column_q :='SELECT substr('||i.column_name||',1,32) , count(*) from '||ownname||'.'||tabname||' group by substr('||i.column_name||',1,32) order by '||i.column_name;
else
l_column_q :='SELECT '||i.column_name||' , count(*) from '||ownname||'.'||tabname||' group by '||i.column_name||' order by '||i.column_name;
end if;
end if;
-- Since the data type of the first column of the query would depend on the column type
-- we need to have different statement for each supported data type
if i.data_type = 'NUMBER' then
-- Store the frequency data, which it is itself an array , into a package variables l_colhiststatsn and l_colhiststats
execute immediate l_column_q bulk collect into l_colhiststatsn(i.column_id) ,l_colhiststats (i.column_id).bkvals ;
elsif i.data_type = 'VARCHAR2' then
-- Store the frequency data, which it is itself an array , into a package variables l_colhiststatsv and l_colhiststats
execute immediate l_column_q bulk collect into l_colhiststatsv(i.column_id) ,l_colhiststats (i.column_id).bkvals ;
elsif i.data_type = 'DATE' then
-- Store the frequency data, which it is itself an array , into a package variables l_colhiststatsv and l_colhiststats
execute immediate l_column_q bulk collect into l_colhiststatsd(i.column_id) ,l_colhiststats (i.column_id).bkvals ;
end if;
else
-- Creating hieght balanced histograms
l_colhist(i.column_id):='HEIGHT BALANCED';
if i.data_type = 'VARCHAR2' then
l_column_q :='SELECT VAL FROM (select val , bkt , lag(bkt,1) over (order by val) pr from ( select substr('||i.column_name||',1,32) val , ntile(256) over (order by '||i.column_name||') bkt
from '||ownname||'.'||tabname||' )) where bkt > pr order by bkt';
else
l_column_q :='SELECT VAL FROM (select val , bkt , lag(bkt,1) over (order by val) pr from ( select '||i.column_name||' val , ntile(256) over (order by '||i.column_name||') bkt
from '||ownname||'.'||tabname||' )) where bkt > pr order by bkt';
end if;
if i.data_type = 'NUMBER' then
execute immediate l_column_q bulk collect into l_colhiststatsn(i.column_id);
elsif i.data_type = 'VARCHAR2' then
execute immediate l_column_q bulk collect into l_colhiststatsv(i.column_id);
elsif i.data_type = 'DATE' then
execute immediate l_column_q bulk collect into l_colhiststatsd(i.column_id);
end if;
end if;
else
-- Set the l_colhist flag to indicate there would be no histogram for that column
l_colhist(i.column_id):=null;
end if;
else
l_colhist(i.column_id):=null;
end if;
end if;
end loop;
-- Save the collected table statistics into the data dictionaty using an autonomous transaction
save_table_stats (ownname , tabname , p_scope , partname , l_tab_cnt);
-- Save the collected column statistics into the data dictionaty using an autonomous transaction
save_col_stats (ownname , tabname , p_scope , partname );
-- Check is table or partition. If table will gather index statistics
if p_scope = 'GLOBAL' then
-- Loop though all indexes that belong to the table
for inds in
(select
index_name
from
dba_indexes
where
owner = ownname
and table_name = tabname
and index_type > 'LOB'
)
loop
-- Call the procedure that gathers index stats
gather_index_stats(ownname , inds.index_name);
null;
end loop;
end if;
end gather_table_part_stats;
procedure gather_table_stats(
ownname varchar2 ,
tabname varchar2 ,
partname varchar2 default null ,
estimate_percent number default null,
method_opt varchar2 default null ,
degree number default null ,
granularity varchar2 default 'AUTO' )
as
l_part varchar2(100);
begin
-- Is the table partitioned?
select
partitioned
into
l_part
from
dba_tables
where
owner = ownname
and table_name = tabname ;
if l_part = 'NO' then
-- If table not partitioned then gather stats for the whole table
gather_table_part_stats ( ownname , tabname , 'GLOBAL', partname ,estimate_percent ,method_opt , degree );
else
-- If table is partitioned then we have look at granurality
if granularity = 'GLOBAL' then
-- gather stats on the whole table
gather_table_part_stats ( ownname , tabname ,'GLOBAL' , partname ,estimate_percent ,method_opt , degree );
elsif granularity = 'PARTITION' then
-- gather stats on the spacified partition
gather_table_part_stats ( ownname , tabname ,'PARTITION' , partname ,estimate_percent ,method_opt , degree );
elsif granularity = 'ALL' or granularity = 'GLOBAL AND PARTITION' then
-- gather stats on the whole table
gather_table_part_stats ( ownname , tabname ,'GLOBAL' , partname ,estimate_percent ,method_opt , degree );
-- and gather stats on every partition
for part in
(select partition_name
from dba_tab_partitions
where table_owner = ownname
and table_name = tabname
)
loop
gather_table_part_stats ( ownname , tabname ,'PARTITION' , part.partition_name ,method_opt , degree );
end loop;
else
-- if no granurality specified all stats (table + all partitions) are gathered
gather_table_part_stats ( ownname , tabname ,'GLOBAL' , partname , estimate_percent ,method_opt , degree );
for part in
(select partition_name
from dba_tab_partitions
where table_owner = ownname
and table_name = tabname
)
loop
gather_table_part_stats ( ownname , tabname ,'PARTITION' , part.partition_name , estimate_percent ,method_opt , degree );
end loop;
end if;
end if;
end;
procedure gather_index_stats(
p_owner varchar2 ,
p_index varchar2)
is
l_index_q varchar2(32000);
l_obj_id number;
l_num_ind_columns number ;
l_table_name varchar2(30);
l_inv_columns number;
begin
-- This procedure is able to gather index stats only on
-- indexes that are on NUMBER','VARCHAR2','DATE','FLOAT and TIMESTAMP columns
select
count(*)
into
l_inv_columns
from
dba_ind_columns a ,
dba_tab_columns b
where
a.index_name = p_index
and a.index_owner = p_owner
and a.index_owner = b.owner
and a.column_name = b.column_name
and ( b.data_type not in ('NUMBER','VARCHAR2','DATE','FLOAT'))
and substr(b.data_type,1,9) > 'TIMESTAMP' ;
if l_inv_columns = 0 then
-- Construct the dynamic SQL
-- Starting with number of records and numner of leaf nodes
l_index_q :='select /*+ no_parallel_index(t,"I_O") dbms_stats cursor_sharing_exact use_weak_name_resl dynamic_sampling(0) no_monitoring no_expand index(t,"'||p_index||'") */ count(*) as nrw,count(distinct sys_op_lbid(';
-- The object ID of the index is needed for the query
select
object_id
into
l_obj_id
from
dba_objects
where
object_name = p_index
and owner = p_owner
and object_type = 'INDEX' ;
l_index_q :=l_index_q||l_obj_id||',''L'',t.rowid)) as nlb,count(distinct ';
-- Now building the clause that would compute the number of distinct entries
-- Find the number of columns in the index
select
count(*)
into
l_num_ind_columns
from
dba_ind_columns
where
index_name = p_index
and index_owner = p_owner;
if l_num_ind_columns = 1 then
-- If index is only on one column
for cols in
(select
column_name
from
dba_ind_columns
where
index_name = p_index
and index_owner = p_owner
)
loop
-- Should fire only ones
l_index_q:=l_index_q||'"'||cols.column_name||'")';
end loop;
else
for cols in
(select
column_name ,
column_position
from
dba_ind_columns
where
index_name = p_index
and index_owner = p_owner
order by
column_position
)
loop
if cols.column_position = 1 then
-- special rule for the first column. This is to produce syntactically correct statement
l_index_q :=l_index_q||' hextoraw(sys_op_descend( "'||cols.column_name||'")';
else
l_index_q:=l_index_q||'||sys_op_descend( "'||cols.column_name||'")';
end if;
end loop;
l_index_q:=l_index_q||'))';
end if;
select
table_name
into
l_table_name
from
dba_indexes
where
index_name = p_index
and owner = p_owner;
-- Adding the clause for the clustering factor
l_index_q :=l_index_q||' , sys_op_countchg(substrb(t.rowid,1,15),1) as clf from '||p_owner||'.'||l_table_name||' t ';
-- Going through all records and adding a "not NULL" clause
for cols in
(select
column_name ,
column_position
from
dba_ind_columns
where
index_name = p_index
and index_owner = p_owner
order by
column_position
)
loop
if cols.column_position = 1 then
l_index_q :=l_index_q||' where '||cols.column_name||' is not NULL ';
else
l_index_q:=l_index_q||' and '||cols.column_name||' is not NULL ';
end if;
end loop;
-- Execute the query and store the results in package variables
execute immediate l_index_q into l_ind_cnt ,
l_ind_leaf_blocks ,
l_ind_distinct ,
l_ind_clustering_factor ;
-- Saving the above gathered statistics in the data dictionary
save_index_stats (p_owner , p_index ) ;
end if;
end;
end just_stats;