cross-column-poc-3.sql

text/plain

Filename: cross-column-poc-3.sql
Type: text/plain
Part: 0
Message: Re: proposal : cross-column stats
-- 
create table zip_codes_usa (zip_code char(5), latitude float, longitude float, zip_class char(1), post_name varchar(28), state char(2), county char(3));

insert into zip_codes_usa select zip_code, latitude::float, longitude::float, zip_class, poname, state, county from zipnov99 ;

-- table used to store statistics
CREATE TABLE cross_stats (
    columns VARCHAR[],
    ndistinct INT[]
);

/*
* Collects statistics.
*
* This is a very stupid / slow implementation.
*/
CREATE OR REPLACE FUNCTION collect_stats(p_col_a VARCHAR, p_col_b VARCHAR) RETURNS void AS $$
DECLARE

  v_columns VARCHAR[];

  v_dist_total INT;
  v_dist_a INT;
  v_dist_b INT;

  v_distinct INT[];

BEGIN

  /* columns */
  v_columns := array_append(v_columns, p_col_a);
  v_columns := array_append(v_columns, p_col_b);

  RAISE NOTICE 'counting distinct values ...';

  EXECUTE 'SELECT COUNT(DISTINCT ' || p_col_a || ') dist_a, 
		  COUNT(DISTINCT ' || p_col_b || ') dist_b,
                  COUNT(DISTINCT ' || p_col_a || ' || '':'' || ' || p_col_b || ') dist_total
	   FROM zip_codes_usa' INTO v_dist_a, v_dist_b, v_dist_total;

  v_distinct := array_append(v_distinct, v_dist_a);
  v_distinct := array_append(v_distinct, v_dist_b);
  v_distinct := array_append(v_distinct, v_dist_total);

  -- save stats
  DELETE FROM cross_stats;
  INSERT INTO cross_stats VALUES (v_columns, v_distinct);

END;
$$ LANGUAGE plpgsql;

-- compute the estimate when there are range conditions on both columns, i.e. something like
-- ... WHERE (col_a = '40999') AND (col_b = '029')
CREATE OR REPLACE FUNCTION get_estimate(p_value_a VARCHAR, p_value_b VARCHAR) RETURNS INT[] AS $$
DECLARE

  -- the estimate
  v_estimate FLOAT;
  v_estimates INT[];

  -- coefficients
  v_count FLOAT;
  v_coeffs FLOAT[];
  v_ndistinct INT[];

  v_columns VARCHAR[];

BEGIN

  SELECT columns, ndistinct INTO v_columns, v_ndistinct FROM cross_stats;

  SELECT reltuples INTO v_count FROM pg_class WHERE relname = 'zip_codes_usa';

  v_estimate := v_count * get_frequency('zip_codes_usa', v_columns[1], p_value_a);
  v_estimates := array_append(v_estimates, round(v_estimate)::int);

  RAISE NOTICE 'estimate for column % is %',v_columns[1],v_estimate;

  v_estimate := v_count * get_frequency('zip_codes_usa', v_columns[2], p_value_b);
  v_estimates := array_append(v_estimates, round(v_estimate)::int);

  RAISE NOTICE 'estimate for column % is %',v_columns[2],v_estimate;

  -- heuristics (not part of the solution described in the article)
  IF (v_ndistinct[1] = v_ndistinct[3]) THEN
    v_estimate := v_estimates[1];
  ELSIF (v_ndistinct[1] = v_ndistinct[3]) THEN
    v_estimate := v_estimates[2];
  ELSE
    v_estimate := (v_ndistinct[1]::float / v_ndistinct[3]) * v_estimates[1] + (v_ndistinct[2]::float / v_ndistinct[3]) * v_estimates[2];
  END IF;

  v_estimates := array_append(v_estimates, round(v_estimate)::int);

  RAISE NOTICE 'combined estimate is %',v_estimate;

  RETURN v_estimates;

END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION get_frequency(p_table VARCHAR, p_column VARCHAR, p_value VARCHAR) RETURNS FLOAT AS $$
DECLARE
  v_values VARCHAR[];
  v_freqs  FLOAT[];
  v_freq   FLOAT := 0;
  v_ndist  INT;
BEGIN

  SELECT n_distinct, most_common_vals, most_common_freqs INTO v_ndist, v_values, v_freqs FROM pg_stats WHERE tablename = p_table AND attname = p_column;

  IF (v_values IS NULL) THEN
    v_freq := (1::float / v_ndist);
    RAISE NOTICE 'frequency for column % is %', p_column, v_freq;
    RETURN v_freq;
  END IF;

  -- is it one of the MCVs?
  FOR v_idx IN 1..array_upper(v_values,1) LOOP
    IF (v_values[v_idx] = p_value) THEN
      v_freq := v_freqs[v_idx];
      RAISE NOTICE 'frekvency for column % is % (from MCV)', p_column, v_freq;
      RETURN v_freqs[v_idx];
    END IF;

    v_freq := v_freq + v_freqs[v_idx];

  END LOOP;

  v_ndist := (v_ndist - array_upper(v_values,1));
  IF (v_ndist <= 0) THEN
    v_ndist := 1;
  END IF;

  v_freq := (1 - v_freq) / v_ndist;
  RAISE NOTICE 'frequency for colunm % is %', p_column, v_freq;
  RETURN v_freq;

END;
$$ LANGUAGE plpgsql;