I spent a lot of time yesterday digging into performance issues on a new database only to find a simple issue where two tables were joined on a column that was a number on one table and a varchar2 on the other. That column was a varchar2(4) on six or eight tables but one table – and it had to be the one with the most rows – had the same column defined as number(4) and as a result the queries were running with inefficient plans. All I had to do to find this was get a plan of the sample query I was working on and look for TO_NUMBER in the predicates section but of course I spent hours looking at other things first. So, I thought I would document how to make a quick check for this kind of type conversion. It is “implicit” type conversion because there is no TO_NUMBER in the sql itself. The optimizer has to add the TO_NUMBER so it can compare the character column to the number column.
Here is how I setup the example tables to mimic the situation I saw yesterday:
-- table with number column
create table ntest (div_nbr number(4));
-- table with character column
create table ctest (div_nbr varchar2(4));
Here is a sample query that joins these two tables on the columns of the same name but different types:
-- join on the column
select count(*)
from ntest,ctest
where ntest.div_nbr=ctest.div_nbr;
Here is how I get the plan which will includes the predicates section with the TO_NUMBER conversion function:
-- get plan
select * from table(dbms_xplan.display_cursor(null,null,'ALL'));
Finally, here is the plan with the predicates section with the TO_NUMBER function:
------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 5 (100)|
| 1 | SORT AGGREGATE | | 1 | 17 | |
|* 2 | HASH JOIN | | 1 | 17 | 5 (20)|
| 3 | TABLE ACCESS FULL| NTEST | 1 | 13 | 2 (0)|
| 4 | TABLE ACCESS FULL| CTEST | 1 | 4 | 2 (0)|
------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("NTEST"."DIV_NBR"=TO_NUMBER("CTEST"."DIV_NBR"))
Step 2 of the plan is the hash join and the predicate information section shows how the two columns are joined for this step. CTEST.DIV_NBR has to be converted to a number before it can be compared to NTEST.DIV_NBR.
So, I recommend putting a check for TO_NUMBER in the predicate information section of the plan into your toolkit of things to check when tuning a query. This kind of thing shows up more often than you would think and it is hard to find because if you look at the query itself you just see a normal join on the DIV_NBR column.
Our resolution of this issue was to change the type of the one table to varchar2(4). This combined with tuning optimizer_index_cost_adj resulted in dramatic improvements in performance on our test system.
Here is a zip of the script I used to show how to find TO_NUMBER in the predicate section.
– Bobby