patternMinor
Outer Join suppressing Index usage?
Viewed 0 times
suppressingjoinusageindexouter
Problem
I have a client program that is executing a query against a View that outer joins one table to another. The performance is bad and I have been trying to tune it by adding the right index. The query in question is only actually using the second table, so I have been testing against that table directly.
I found (several) indexes that worked fine for the query against the table but when I switched it to use the View, they stopped using any indexes and just did full scans on both tables instead. As these tables are large (2-3 million rows each) this is very slow.
To simply testing, I changed the query to bypass the and just incorporate the outer join into the query itself. This successfully reproduced the problem, but left the mystery of why the outer join wouldn't use the indexes.
Here's the table, with all of the indexes I have added while testing:
```
CREATE TABLE TEST_DATA
(ID NUMBER(11,0) PRIMARY KEY,
FORMATTED_RESULT VARCHAR2(255 BYTE),
F_RESULT NUMBER,
IDNUM NUMBER(11,0),
IDNUM_DESCRIPTION VARCHAR2(128 BYTE),
LAB_NUMBER NUMBER(11,0),
SEQ_NUMBER NUMBER(11,0),
ORDERNO NUMBER(11,0),
SUPPL_FORMATTED_RESULT VARCHAR2(255 BYTE),
SUPPL_IDNUM NUMBER(11,0),
SUPPL_IDNUM_DESCRIPTION VARCHAR2(128 BYTE),
SUPPL_UNIT VARCHAR2(16 BYTE)
) ;
CREATE UNIQUE INDEX TEST_LN_SQN_ORDER ON TEST_DATA (LAB_NUMBER, SEQ_NUMBER, ORDERNO) ;
CREATE INDEX TEST_LN_SQN ON TEST_DATA (LAB_NUMBER, SEQ_NUMBER) ;
CREATE INDEX TD_CUIDD_CUFR ON TEST_DATA (UPPER(COALESCE(SUPPL_IDNUM_DESCRIPTION,IDNUM_DESCRIPTION)), UPPER(COALESCE(SUPPL_FORMATTED_RESULT,FORMATTED_RESULT))) ;
CREATE INDEX TD_UFR_IDN ON TEST_DATA (UPPER(FORMATTED_RESULT), IDNUM) ;
CREATE INDEX TD_UIDD_UFR ON TEST_DATA (UPPER(IDNUM_DESCRIPTION), UPPER(FORMATTED_RESULT)) ;
CREATE INDEX TD_CUFR_CIDN_SN_LN ON TEST_DATA (UPPER(COALESCE(SUPPL_FORMATTED_RESULT,FORMATTED_RESULT)), COALESCE(SUPPL_IDNUM,IDNUM), SEQ_NUMBER, LAB_NUMBER) ;
CREATE INDEX TD_SN_LN_CUFR_CIDN ON
I found (several) indexes that worked fine for the query against the table but when I switched it to use the View, they stopped using any indexes and just did full scans on both tables instead. As these tables are large (2-3 million rows each) this is very slow.
To simply testing, I changed the query to bypass the and just incorporate the outer join into the query itself. This successfully reproduced the problem, but left the mystery of why the outer join wouldn't use the indexes.
Here's the table, with all of the indexes I have added while testing:
```
CREATE TABLE TEST_DATA
(ID NUMBER(11,0) PRIMARY KEY,
FORMATTED_RESULT VARCHAR2(255 BYTE),
F_RESULT NUMBER,
IDNUM NUMBER(11,0),
IDNUM_DESCRIPTION VARCHAR2(128 BYTE),
LAB_NUMBER NUMBER(11,0),
SEQ_NUMBER NUMBER(11,0),
ORDERNO NUMBER(11,0),
SUPPL_FORMATTED_RESULT VARCHAR2(255 BYTE),
SUPPL_IDNUM NUMBER(11,0),
SUPPL_IDNUM_DESCRIPTION VARCHAR2(128 BYTE),
SUPPL_UNIT VARCHAR2(16 BYTE)
) ;
CREATE UNIQUE INDEX TEST_LN_SQN_ORDER ON TEST_DATA (LAB_NUMBER, SEQ_NUMBER, ORDERNO) ;
CREATE INDEX TEST_LN_SQN ON TEST_DATA (LAB_NUMBER, SEQ_NUMBER) ;
CREATE INDEX TD_CUIDD_CUFR ON TEST_DATA (UPPER(COALESCE(SUPPL_IDNUM_DESCRIPTION,IDNUM_DESCRIPTION)), UPPER(COALESCE(SUPPL_FORMATTED_RESULT,FORMATTED_RESULT))) ;
CREATE INDEX TD_UFR_IDN ON TEST_DATA (UPPER(FORMATTED_RESULT), IDNUM) ;
CREATE INDEX TD_UIDD_UFR ON TEST_DATA (UPPER(IDNUM_DESCRIPTION), UPPER(FORMATTED_RESULT)) ;
CREATE INDEX TD_CUFR_CIDN_SN_LN ON TEST_DATA (UPPER(COALESCE(SUPPL_FORMATTED_RESULT,FORMATTED_RESULT)), COALESCE(SUPPL_IDNUM,IDNUM), SEQ_NUMBER, LAB_NUMBER) ;
CREATE INDEX TD_SN_LN_CUFR_CIDN ON
Solution
This is mostly a partial answer to part 1 with some speculation. You and I know that the following query:
Is equivalent to this query:
However, that doesn't mean that Oracle knows that the two queries are equivalent. The equivalence of the two queries is required for Oracle to be able to use the
Adding
If we try a slightly more complicated filter such as
We can reason out that the
Here is a db fiddle for some of the examples.
For the second question, I'm unable to think of a way to work around this. You could try taking advantage of table elimination. As you said this query doesn't even require the
There are currently a few limitations of table elimination:
-
Multi-column primary key-foreign key constraints are not supported.
-
Referring to the join key elsewhere in the query will prevent table elimination. For an inner join, the join keys on each side of the join are equivalent, but if the query contains other references to the join key from the table that could otherwise be eliminated, this prevents elimination. A workaround is to rewrite the query to refer to the join key from the other table (we realize this is not always possible).
Perhaps there is a way to use that for this problem but I'm unable to work around the restrictions.
select TD.LAB_NUMBER
from REQUEST_INFO RI
LEFT JOIN TEST_DATA TD ON TD.LAB_NUMBER = RI.LAB_NUMBER AND TD.SEQ_NUMBER = RI.SEQ_NUMBER
where UPPER(COALESCE(TD.SUPPL_FORMATTED_RESULT,TD.FORMATTED_RESULT))='491(10)376'
and COALESCE(TD.SUPPL_IDNUM, TD.IDNUM)=40549;Is equivalent to this query:
select TD.LAB_NUMBER
from REQUEST_INFO RI
INNER JOIN TEST_DATA TD ON
TD.LAB_NUMBER = RI.LAB_NUMBER
AND TD.SEQ_NUMBER = RI.SEQ_NUMBER
AND UPPER(COALESCE(TD.SUPPL_FORMATTED_RESULT,TD.FORMATTED_RESULT))='491(10)376'
and COALESCE(TD.SUPPL_IDNUM, TD.IDNUM)=40549;However, that doesn't mean that Oracle knows that the two queries are equivalent. The equivalence of the two queries is required for Oracle to be able to use the
TD_CUFR_CIDN_SN_LN index. What we're hoping for here is an OUTER JOIN to INNER JOIN conversion. I haven't had a lot of luck finding good information about this, so let's look at the explain plans:Adding
TD.LAB_NUMBER IS NOT NULL to the WHERE clause is a very direct way to let Oracle know that OUTER JOIN to INNER JOIN conversion is possible. We can see that it occurred by looking at the highlighted line. I think that pretty much any column will allow the conversion, although picking the wrong column may change the query results.If we try a slightly more complicated filter such as
(TD.LAB_NUMBER IS NOT NULL OR TD.SEQ_NUMBER IS NOT NULL) then the join conversion does not happen:We can reason out that the
OUTER JOIN is really an INNER JOIN but the query optimizer may have not been programmed to do that. In the original query, you have a COALESCE() expression which is probably just too complex for the query optimizer to apply the query transformation.Here is a db fiddle for some of the examples.
For the second question, I'm unable to think of a way to work around this. You could try taking advantage of table elimination. As you said this query doesn't even require the
REQUEST_INFO table. However, there are a few restrictions:There are currently a few limitations of table elimination:
-
Multi-column primary key-foreign key constraints are not supported.
-
Referring to the join key elsewhere in the query will prevent table elimination. For an inner join, the join keys on each side of the join are equivalent, but if the query contains other references to the join key from the table that could otherwise be eliminated, this prevents elimination. A workaround is to rewrite the query to refer to the join key from the other table (we realize this is not always possible).
Perhaps there is a way to use that for this problem but I'm unable to work around the restrictions.
Code Snippets
select TD.LAB_NUMBER
from REQUEST_INFO RI
LEFT JOIN TEST_DATA TD ON TD.LAB_NUMBER = RI.LAB_NUMBER AND TD.SEQ_NUMBER = RI.SEQ_NUMBER
where UPPER(COALESCE(TD.SUPPL_FORMATTED_RESULT,TD.FORMATTED_RESULT))='491(10)376'
and COALESCE(TD.SUPPL_IDNUM, TD.IDNUM)=40549;select TD.LAB_NUMBER
from REQUEST_INFO RI
INNER JOIN TEST_DATA TD ON
TD.LAB_NUMBER = RI.LAB_NUMBER
AND TD.SEQ_NUMBER = RI.SEQ_NUMBER
AND UPPER(COALESCE(TD.SUPPL_FORMATTED_RESULT,TD.FORMATTED_RESULT))='491(10)376'
and COALESCE(TD.SUPPL_IDNUM, TD.IDNUM)=40549;Context
StackExchange Database Administrators Q#168694, answer score: 2
Revisions (0)
No revisions yet.