Skip to Main Content
  • Questions
  • Cardinality of table collections in SQL

Breadcrumb

Question and Answer

Connor McDonald

Thanks for the question, Curtis.

Asked: July 13, 2016 - 9:27 pm UTC

Last updated: June 07, 2018 - 1:26 am UTC

Version: Oracle Enterprise 12c

Viewed 1000+ times

You Asked

I'm doing two queries, one will use an index I am trying to use. The first query it does not use the index and does full table scan. number_tbl will be a parameter passed into a stored procedure. We are using a type to pass in a table of numbers. I'm stripping out the sql to simply test the statement outside of the procedure. Query 2 will use the index without using the type collection. Dynamic Sampling seems to be the difference in the second statement. There must be a better way to pass in a table of numbers and have the optimizer use the index and things for me to learn. Thanks for any help.

create index rt_cl_key_LOCATION_CK_ID on cl_key
(LOCATION_CELL_KEY_ID)

#1
select ck.*
from cl_key ck
where ck.location_cl_key_id in
(select num
from table(cast(number_tbl(number_obj(1938316)
,number_obj(2190749)) as
number_tbl)))

Plan Hash Value : 973163600

------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 15802 | 1596002 | 1633 | 00:00:01 |
| * 1 | HASH JOIN RIGHT SEMI | | 15802 | 1596002 | 1633 | 00:00:01 |
| 2 | COLLECTION ITERATOR CONSTRUCTOR FETCH | | 16360 | 32720 | 19 | 00:00:01 |
| 3 | TABLE ACCESS FULL | cl_key | 1580187 | 156438513 | 1611 | 00:00:01 |
------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
------------------------------------------
* 1 - access("CK"."LOCATION_CELL_KEY_ID"=SYS_OP_ATG(VALUE(KOKBF$),1,2,2))

#2
select ck.*
from lynx.cl_key ck
where ck.location_cell_key_id in
(1938316,2190749)


Plan Hash Value : 2640744059

------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 68 | 6732 | 7 | 00:00:01 |
| 1 | INLIST ITERATOR | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED | cl_key | 68 | 6732 | 7 | 00:00:01 |
| * 3 | INDEX RANGE SCAN | rt_cl_key_LOCATION_CK_ID | 5 | | 4 | 00:00:01 |
------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
------------------------------------------
* 3 - access("CK"."LOCATION_CELL_KEY_ID"=1938316 OR "CK"."LOCATION_CELL_KEY_ID"=2190749)


Note
-----
- dynamic sampling used for this statement


and Connor said...

If you *know* the cardinality of the collection will always be small, you can use an index hint to always force the use of an index lookup.

In the more general case, Adrian Billington wrote up a nice piece on some of the options you have to let the optimizer know the cardinality of a incoming collection, so at least it will make an intelligent decision on whether to use the index.

http://www.oracle-developer.net/display.php?id=427

Another option is to put the collection into a global temporary table, and use that within the SQL.

Hope this helps.

Rating

  (3 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Implementing Extensible Optimiser

Curtis Jamison, July 28, 2016 - 1:58 pm UTC

Thanks, I've implemented the last example in the article you suggested. I'm seeing full table scans move over to index scan as is demonstrated in the article. One difference I am seeing is in the estimated number of rows from the article shows.

An example is I will create a number_list collection and create the wrapper in the related article...

CREATE OR REPLACE TYPE number_list AS TABLE OF NUMBER;

SELECT * FROM TABLE(number_list(1,2,3))

Plan Hash Value : 1748000095

-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16360 | 32720 | 18 | 00:00:01 |
| 1 | COLLECTION ITERATOR CONSTRUCTOR FETCH | | 16360 | 32720 | 18 | 00:00:01 |
-----------------------------------------------------------------------------------------

This is using 16360 as rows prior to adding the collection wrapper around it.

Now let's change the query to use the wrapper...

SELECT * FROM TABLE(number_list_wrapper(number_list(1,2,3)))

Plan Hash Value : 760883980

---------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
---------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2 | 18 | 00:00:01 |
| 1 | COLLECTION ITERATOR PICKLER FETCH | NUMBER_LIST_WRAPPER | 1 | 2 | 18 | 00:00:01 |
---------------------------------------------------------------------------------------------------

In the explain plan I am seeing Rows of 1. Shouldn't it be 3? Also when monitoring it is showing an estimate rows of 1 with actual rows of 3. I would think it should show estimate of 3 too.

Thanks again for the help.
Connor McDonald
August 08, 2016 - 7:56 am UTC

Hmmm...I'm not seeing that

SQL> CREATE OR REPLACE TYPE number_list AS TABLE OF NUMBER;
  2  /

Type created.

SQL>
SQL> SELECT * FROM TABLE(number_list(1,2,3))
  2  /

COLUMN_VALUE
------------
           1
           2
           3

SQL>
SQL>
SQL>
SQL> CREATE FUNCTION collection_wrapper(
  2                      p_collection IN number_list
  3                      ) RETURN number_list IS
  4      BEGIN
  5         RETURN p_collection;
  6      END collection_wrapper;
  7  /

Function created.

SQL>
SQL> CREATE TYPE collection_wrapper_ot AS OBJECT (
  2
  3     dummy_attribute NUMBER,
  4
  5     STATIC FUNCTION ODCIGetInterfaces (
  6                     p_interfaces OUT SYS.ODCIObjectList
  7                     ) RETURN NUMBER,
  8
  9     STATIC FUNCTION ODCIStatsTableFunction (
 10                     p_function   IN  SYS.ODCIFuncInfo,
 11                     p_stats      OUT SYS.ODCITabFuncStats,
 12                     p_args       IN  SYS.ODCIArgDescList,
 13                     p_collection IN number_list
 14                     ) RETURN NUMBER
 15
 16  );
 17  /

Type created.

SQL>
SQL> CREATE TYPE BODY collection_wrapper_ot AS
  2
  3     STATIC FUNCTION ODCIGetInterfaces (
  4                     p_interfaces OUT SYS.ODCIObjectList
  5                     ) RETURN NUMBER IS
  6     BEGIN
  7        p_interfaces := SYS.ODCIObjectList(
  8                           SYS.ODCIObject ('SYS', 'ODCISTATS2')
  9                           );
 10        RETURN ODCIConst.success;
 11     END ODCIGetInterfaces;
 12
 13     STATIC FUNCTION ODCIStatsTableFunction (
 14                     p_function   IN  SYS.ODCIFuncInfo,
 15                     p_stats      OUT SYS.ODCITabFuncStats,
 16                     p_args       IN  SYS.ODCIArgDescList,
 17                     p_collection IN  number_list
 18                     ) RETURN NUMBER IS
 19     BEGIN
 20        p_stats := SYS.ODCITabFuncStats(p_collection.COUNT);
 21        RETURN ODCIConst.success;
 22     END ODCIStatsTableFunction;
 23
 24  END;
 25  /

Type body created.

SQL>
SQL> ASSOCIATE STATISTICS WITH FUNCTIONS collection_wrapper USING collection_wrapper_ot;

Statistics associated.

SQL> SELECT * FROM TABLE(collection_wrapper(number_list(1,2,3)));


PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 4261576954

--------------------------------------------------------------------------------------------------------
| Id  | Operation                         | Name               | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                  |                    |     3 |     6 |    19   (0)| 00:00:01 |
|   1 |  COLLECTION ITERATOR PICKLER FETCH| COLLECTION_WRAPPER |     3 |     6 |    19   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------------

8 rows selected.

SQL>




Perhaps do a 10053 trace and see what you can find out.

Extensible Optimiser

Rajeshwaran, Jeyabal, August 08, 2016 - 11:37 am UTC

Extensible Optimiser - works only for literals and not for binds.

https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:9449968100346432848

10053 Error?

Mathew Butler, June 06, 2018 - 10:02 am UTC

I've been using the extensible optimizer on 12.1.0.2 and noticed some cases where the optimizer incorrectly identifies the cardinality of the collection. e.g. say, the collection contains 20 records, the optimizer believes there is a single record. This seems to match the case described here.

In our 10053 trace there was evidence that the optimizer produced an anonymous PL/SQL block that did not compile. The result of this ORA- exception was that the cardinality defaulted to 1.

I don't yet understand the cases where this can occur, since this mechanism seems to work for the same query in some (but clearly not all) cases.

The workaround I have in place is to use the dynamic_sampling hint at level two with a reference to the collection to request the optimizer to scan the collection directly to obtain its estimates.

This may be an acceptable workaround if the original poster is encountering the same issue.

I believe (don't have the details to hand) that there is an open bug on 12.1.0.2 for this issue.

Connor McDonald
June 07, 2018 - 1:26 am UTC

Thanks for the additional info

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library