-- fitness test for pl/sql implementation of "ranges formula"


@range_sel_formula.sql

set echo on
set serverouput on size 1000000 format wrapped


create or replace procedure range_sel_formula_check (
  p_table_name  varchar2,
  p_column_name varchar2)
as
  l_table_name  varchar2(30) := upper (p_table_name);
  l_column_name varchar2(30) := upper (p_column_name);
  l_num_rows         number;
  l_num_distinct     number;
  l_endpoint_num     number;
  l_min_x            number;
  l_max_x            number;
  l_B                number;

  type range_sel_number_list is table of number      index by binary_integer;
 
  l_x_points range_sel_number_list;
  l_#low_idx    int;
  l_#high_idx   int;
  l_low_x_idx   int;
  l_high_x_idx  int;
  l_#low        varchar2(2);
  l_#high       varchar2(2);
  l_low_x       number;
  l_high_x      number;
  l_stmt        varchar2(200);
  l_card_f      number;
  l_cardinality number;
  l_num_checked number := 0;
begin
  select num_rows
    into l_num_rows
    from user_tables
   where table_name = l_table_name;
   
  select min (endpoint_value), max (endpoint_value), count(*)
    into l_min_x, l_max_x, l_endpoint_num
    from user_tab_histograms
   where table_name = l_table_name
     and column_name = l_column_name;

  if l_endpoint_num != 2 then
    raise_application_error (-20001, 'wrong number of endpoints: '||l_endpoint_num);
  end if;

  select num_distinct
    into l_num_distinct
    from user_tab_columns
   where table_name = l_table_name
     and column_name = l_column_name;

  l_B := (l_max_x - l_min_x) / l_num_distinct;

  l_x_points( 0) := l_min_x;
  l_x_points( 1) := l_min_x + 1e-3;
  l_x_points( 2) := l_min_x + l_B / 2;
  l_x_points( 3) := l_min_x + l_B - 1e-3;
  l_x_points( 4) := l_min_x + l_B;
  l_x_points( 5) := l_min_x + l_B + 1e-3;
  l_x_points( 6) := l_min_x + l_B + 0.21 * (l_max_x - l_min_x - 2*l_B);
  l_x_points( 7) := l_min_x + l_B + 0.81 * (l_max_x - l_min_x - 2*l_B);
  l_x_points( 8) := l_max_x - l_B - 1e-3;
  l_x_points( 9) := l_max_x - l_B;
  l_x_points(10) := l_max_x - l_B + 1e-3;
  l_x_points(11) := l_max_x - l_B + l_B / 2;
  l_x_points(12) := l_max_x - 1e-3;
  l_x_points(13) := l_max_x;

  for l_#low_idx in 0..1 loop
    l_#low := case when l_#low_idx = 0 then '>' else '>=' end;
    for l_#high_idx in 0..1 loop
      l_#high := case when l_#high_idx = 0 then '<' else '<=' end;
      for l_low_x_idx in l_x_points.first .. l_x_points.last loop
        l_low_x := l_x_points (l_low_x_idx);
        for l_high_x_idx in l_x_points.first .. l_x_points.last loop
        l_high_x := l_x_points (l_high_x_idx);
        if l_low_x < l_high_x then 
          delete from plan_table;
          l_stmt := 'explain plan for '
            || 'select x from t'
            || ' where x ' || l_#low  || ' ' || l_low_x
            || '   and x ' || l_#high || ' ' || l_high_x;
          dbms_output.put_line (l_stmt);
          execute immediate l_stmt;
    
          select cardinality
            into l_cardinality
            from plan_table
           where operation = 'SELECT STATEMENT';
          
          commit;

          l_card_f := range_sel_formula (l_#low, l_low_x, l_#high, l_high_x, 
                                         l_min_x, l_max_x, l_num_rows, l_num_distinct);          
         
          if l_card_f not in (-10,-11) -- -10 or -11 signals "not implemented" for ranges completely in bands
             and abs (l_card_f - l_cardinality) > 1
          then
            raise_application_error (-20010, 'failed '||l_stmt||'; formula='||l_card_f||' actual='||l_cardinality);
          end if;

          l_num_checked := l_num_checked + 1;
        end if;
        end loop;
      end loop;
    end loop;
  end loop;  

  dbms_output.put_line ('checked combinations: '||l_num_checked);
end range_sel_formula_check;
/
show errors;

-- check with not-whole numbers
drop table t;

create table t as select decode (mod (rownum-1, 3), 0, -1.937, 1, 0.122, 2, +12.1853) x from dual connect by level <= 1000000;

exec dbms_stats.gather_table_stats (user, 't', method_opt=>'for all columns size 1', estimate_percent=>100);

exec range_sel_formula_check ('t', 'x');

-- check discussion cases

drop table t;

create table t as select 10 * mod (rownum-1, 2) x from dual connect by level <= 2000000;

exec dbms_stats.gather_table_stats (user, 't', method_opt=>'for all columns size 1', estimate_percent=>100);

exec range_sel_formula_check ('t', 'x');

-- add a third distinct value
insert /*+ append */ into t(x) select 6.1 from dual connect by level <= 1000000;
commit;

exec dbms_stats.gather_table_stats (user, 't', method_opt=>'for all columns size 1', estimate_percent=>100);

exec range_sel_formula_check ('t', 'x');

-- add a fourth distinct value
insert /*+ append */ into t(x) select 7.42 from dual connect by level <= 1000000;
commit;
exec dbms_stats.gather_table_stats (user, 't', method_opt=>'for all columns size 1', estimate_percent=>100);

exec range_sel_formula_check ('t', 'x');








