Files
oracle/Kevin_Meade/showplanfrpspreadsheetcode11g.sql
2026-03-12 21:23:47 +01:00

142 lines
9.8 KiB
SQL

--
-- generate SQL for the FRP spreadsheet from the most recent plan in the plan_table
--
-- usage is: @SHOWPLANFRPSPREADSHEETCODE11G
--
col actual_frp format 999.0
col plan_frp format 999.0
col select_id noprint
with
--
-- generates a FRP SPREADSHEET query using plan table information for an EXPLAINED query
--
--
-- THIS CODE IS DEPENDENT UPON WHAT IS IN THE PLAN TABLE
-- among other things this means that if oracle changes the contents of this table, this query may stop working
--
-- several possible flaws can prevent this code from generating an executable SQL SELECT
--
-- 1. bind variables: the corresponding select here must be modified to group on the bind column and select an round(avg(count(*))
-- 2. join predicates: if any are used along with constant tests in a plan step, they must be manaully edited out of the correspoding select here
-- 3. packaged functions: column=functioncall predicates can be dropped in which case filtered_cardinality will be affected, check it if you query has these
-- 4. outer join is not supported. These should be removed. If the case expression becomes empty use count(*).
-- 5. correlated subqueries are confusing. This is because the appear as queries with bind variables. Test like #1 but ingnore their results.
--
--
-- get raw_data from the plan_table for each table reference in the query plan
-- a table may be used more than once in which case there will be more than one row returned here
-- this is managed by using ID so that we know the plan step the table reference refers to
-- note that some plan steps may be index lookups so in this section we translate the index to its underlying table
--
table_list as (
select a.id,a.object_owner table_owner,a.object_name table_name,a.access_predicates,a.filter_predicates,a.object_alias,a.cardinality
from plan_table a
,dba_tables b
where b.owner = a.object_owner
and b.table_name = a.object_name
and a.plan_id = (select max(plan_id) from plan_table)
union all
select a.id,b.table_owner,b.table_name object_name,a.access_predicates,a.filter_predicates,a.object_alias,a.cardinality
from plan_table a
,dba_indexes b
where b.owner = a.object_owner
and b.index_name = a.object_name
and a.plan_id = (select max(plan_id) from plan_table)
)
--
-- given the raw data for tables, modify the predicates so that we only see predicates for constant tests, no join predicates
-- join predicates are not used in FRP analysis
-- this is a bit of a hack as I never took the COMPILER and PARSER classes in school, basically this means it is almost 100%right
-- what we call "close enough for jazz"
--
, modified_table_list as (
select id,table_owner,table_name,object_alias,cardinality
,case when
instr(replace(access_predicates,'"="'),'=') > 0 or
instr(replace(access_predicates,'">"'),'>') > 0 or
instr(replace(access_predicates,'"<"'),'<') > 0 or
instr(replace(access_predicates,'">="'),'>=') > 0 or
instr(replace(access_predicates,'"<="'),'<=') > 0 or
instr(replace(access_predicates,'"!="'),'!=') > 0 or
instr(replace(access_predicates,'"<>"'),'<>') > 0 or
instr(replace(access_predicates,'" LIKE "'),' LIKE ') > 0 or
instr(replace(access_predicates,'" BETWEEN "'),' BETWEEN ') > 0 or
instr(replace(access_predicates,'" IN ("'),' IN (') > 0 or
instr(replace(access_predicates,'" NOT LIKE "'),' NOT LIKE ') > 0 or
instr(replace(access_predicates,'" NOT BETWEEN "'),' NOT BETWEEN ') > 0 or
instr(replace(access_predicates,'" NOT IN ("'),' NOT IN (') > 0
then access_predicates
end access_predicates
,case when
instr(replace(filter_predicates,'"="'),'=') > 0 or
instr(replace(filter_predicates,'">"'),'>') > 0 or
instr(replace(filter_predicates,'"<"'),'<') > 0 or
instr(replace(filter_predicates,'">="'),'>=') > 0 or
instr(replace(filter_predicates,'"<="'),'<=') > 0 or
instr(replace(filter_predicates,'"!="'),'!=') > 0 or
instr(replace(filter_predicates,'"<>"'),'<>') > 0 or
instr(replace(filter_predicates,'" LIKE "'),' LIKE ') > 0 or
instr(replace(filter_predicates,'" BETWEEN "'),' BETWEEN ') > 0 or
instr(replace(filter_predicates,'" IN ("'),' IN (') > 0 or
instr(replace(filter_predicates,'" NOT LIKE "'),' NOT LIKE ') > 0 or
instr(replace(filter_predicates,'" NOT BETWEEN "'),' NOT BETWEEN ') > 0 or
instr(replace(filter_predicates,'" NOT IN ("'),' NOT IN (') > 0
then filter_predicates
end filter_predicates
from table_list
)
--
-- do the final massaging of the raw data
-- in particular, get the true alias for each table, get data from dba_tables, generate an actual predicate we can test with
--
, plan_info as
(
select
id
, table_owner
, table_name
, substr(object_alias,1,instr(object_alias,'@')-1) table_alias
, cardinality
, (select num_rows from dba_tables where dba_tables.owner = modified_table_list.table_owner and dba_tables.table_name = modified_table_list.table_name) num_rows
, case
when access_predicates is null and filter_predicates is null then null
when access_predicates is null and filter_predicates is not null then filter_predicates
when access_predicates is not null and filter_predicates is null then access_predicates
when access_predicates is not null and filter_predicates is not null and access_predicates != filter_predicates then access_predicates||' and '||filter_predicates
else access_predicates
end predicate
from modified_table_list
)
--
-- look for places where indexes are accessed followed by table acces by rowid
-- combine the two lines into one
--
, combined_plan_info as (
select plan_info.table_owner,plan_info.table_name,plan_info.table_alias,plan_info.cardinality,plan_info.num_rows
,min(plan_info.id) id
,listagg(plan_info.predicate,' and ') within group (order by id) predicate
from plan_info
group by plan_info.table_owner,plan_info.table_name,plan_info.table_alias,plan_info.cardinality,plan_info.num_rows
)
--
-- give us a SQL statement that for each table reference, both counts all rows and counts only rows that pass the filter predictes
-- then do the math needed to generate an FRP SPREADSHEET
-- this version (4) only scans each table once instead of twice like the old versions
--
select 1 select_id,'with' sqltext from dual union all
select 2 select_id,' frp_data as (' from dual union all
select 3 select_id,' select '''||lpad(id,5,' ')||''' id,'''||table_owner||''' table_owner,'''||table_name||''' table_name,'''||table_alias||''' table_alias,'||nvl(to_char(num_rows),'cast(null as number)')||' num_rows,count(*) rowcount,'||cardinality||' cardinality,'||decode(predicate,null,'cast(null as number)','count(case when '||predicate||' then 1 end)')||' filtered_cardinality from '||table_owner||'.'||table_name||' '||table_alias||' union all'
from combined_plan_info
union all
select 4 select_id,' select null,null,null,null,null,null,null,null from dual' from dual union all
select 5 select_id,' )' from dual union all
select 6 select_id,'select frp_data.*,round(frp_data.filtered_cardinality/case when frp_data.rowcount = 0 then cast(null as number) else frp_data.rowcount end*100,1) actual_frp,decode(frp_data.filtered_cardinality,null,cast(null as number),round(frp_data.cardinality/case when frp_data.num_rows = 0 then cast(null as number) else frp_data.num_rows end*100,1)) plan_frp' from dual union all
select 7 select_id,'from frp_data' from dual union all
select 8 select_id,'where id is not null' from dual union all
select 9 select_id,'order by frp_data.id' from dual union all
select 10 select_id,'/' from dual
order by 1
/