I’ve written earlier about how to calculate a query’s scalar subquery cache size, and I’ve had an interesting thought since the last blog entry. The last entry was all about scalar subqueries that appear in a query’s select clause, but the same mechanism exists for an unnested subquery that uses a FILTER operation.

My thought was this–If I have a query that uses 1 table within a FILTER operation, I’d have 1024 buckets (same as what we had with the scalar subquery cache size) but…occasionally, a FILTER operation will call multiple tables. My thought is–if I have more than one table in my scalar subquery…do I still only get 1024 buckets total? Or would I get 1024 buckets for each subquery? In other words…do I get 1024 buckets for each FILTER step in the plan? Or each table that the FILTER step invokes?

I decided to figure it out with a similar setup from what I had last time:

create table test_subq_call
(
     id varchar2(100) constraint pk_test_subq_call primary key
   , numb_funct_calls number default '0' not null
);

create or replace function test_subq_cache (p_n in number)
  return number
  authid definer
is
  pragma autonomous_transaction;
begin
 
  update test_subq_call
  set numb_funct_calls = numb_funct_calls + 1
  where id = test_subq_cache.p_n;
 
  if sql%rowcount != 1 then
    raise_application_error(-20001, 'Updated zero rows :(');
  end if;
 
  commit;
 
  return null;
 
end test_subq_cache;
/

create or replace function test_subq_cache (p_n in number, p_l in varchar2)
  return number
  authid definer
is
  pragma autonomous_transaction;
begin
 
  update test_subq_call
  set numb_funct_calls = numb_funct_calls + 1
  where id = test_subq_cache.p_n || test_subq_cache.p_l;
 
  if sql%rowcount != 1 then
    raise_application_error(-20001, 'Updated zero rows :(');
  end if;
 
  commit;
 
  return null;
 
end test_subq_cache;
/

create or replace function generate_rows
  return t_number
  pipelined
  authid definer
is
  pragma autonomous_transaction;
  CONST_MAX_NUMBER_OF_TESTS constant pls_integer := 2000000;
begin
 
  for v_rownum in 1 .. CONST_MAX_NUMBER_OF_TESTS + 1 loop
    insert into test_subq_call(id) values (v_rownum || 'a');
    insert into test_subq_call(id) values (v_rownum || 'b');
    commit;
   
    pipe row(v_rownum);
   
    if v_rownum > 1 then
      pipe row (v_rownum - 1);
    end if;
  end loop;
end generate_rows;
/

create table zero1
as
select 0 zero from dual;

create table zero2
as
select 0 zero from dual;

This gives us the needed setup to create the following query:/

select count(a.column_value)
from table(generate_rows()) a
where exists (select *
              from zero1 b
              where b.zero = test_subq_cache(a.column_value, 'a'))
      or
      exists (select *
              from zero2 c
              where c.zero = test_subq_cache(a.column_value, 'b'))
;

Running this query allows for Oracle to generate 2 rows in our TEST_SUBQ_CALL table. Each record is anticipated to be called twice…unless subquery caching is used, and we only end up calling it once.

After running the above query, I ran this query to count up the number of anticipated calls, in comparison with the actual number of calls:

select total_anticipated_func_calls - actual_func_calls number_of_caching_hash_buckets
from (select count(*) *2 total_anticipated_func_calls
           , sum(numb_funct_calls) actual_func_calls
      from test_subq_call
      where id not like '2000001%');

The result was 2048 buckets (1024 * 2). Unsurprisingly, when I re-rigged the same test for 3 separate tables, Oracle allocated 3072 buckets (1024 * 3).

So the answer is, for modern versions of Oracle, I imagine you get however many tables are in your filter clause * 1024 for you subquery caching.

Free Oracle SQL Tuning Guide

Checkout my FREE guide, 7 SQL Tuning Secrets You Can Use Immediately, Even If You’ve Never Tuned a Query In Your Life!

Get it here: tuningsql.com/secrets

7 SQL Tuning Secrets You can Use Immediately, Even If You've Never Tuned A Query In Your Life
https://tuningsql.com/secrets

Leave a Reply

Your email address will not be published. Required fields are marked *