Oracle FAQ Your Portal to the Oracle Knowledge Grid
HOME | ASK QUESTION | ADD INFO | SEARCH | E-MAIL US
 

Home -> Community -> Usenet -> c.d.o.misc -> Re: realistic cardinality for PL/SQL procedures in SELECT statements

Re: realistic cardinality for PL/SQL procedures in SELECT statements

From: Volker Apelt <gq437x_at_yahoo.de>
Date: Thu, 17 Aug 2006 14:57:46 +0200
Message-ID: <lgbqqj1pv9.fsf@chi.biosolveit.loc>


"Gints Plivna" <gints.plivna_at_gmail.com> writes:

>> How can I tell the CBO what number of records the procedure
>> returns?
>
> With cardinality hint.
>
>> So, the query can change its character from a 'pick one record'
>> to a 'filter many records' and the change is driven by the
>> size of the result set of the procedure.
>
> We had the similar problem and it was resolved using 2 query versions
> something like:
> if predicted_count <= treshold then
> SELECT /*+ cardinality(table_name, 1) */ column_list FROM blabla
> else
> SELECT /*+ ALL_ROWS */ column_list FROM blabla
> end if
>
> For the second case you can as well as add some big cardinality number
> if it is necessary.

Hello Gints,

I have tried the CARDINALITY hint and it works if the hinted object is a table but not for functions.

eg:
-- with table TEST_SMALL_RESULTSET
select /*+ cardinality(TEST_SMALL_RESULTSET, 12) */ * from TEST_SMALL_RESULTSET;
Execution Plan


   0 SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=12 Bytes=468)    1 0 TABLE ACCESS (FULL) OF 'TEST_SMALL_RESULTSET'              (Cost=3 Card=12 Bytes=468)

Is that the correct way to use the cardinality hint with functions? Is its dependent on oracle version? We have 9.2.0.7.

My goggle research showed me some other approaches like

/* FIRST_ROWS DYNAMIC_SAMPLING(TABLE_ALIAS 2) */   to let the optimizer peek into the return values   DYNAMIC_SAMPLING seems to switch from CBO to RBO, right?   It had no effect in my queries.

or append a WHERE ROWNUM >= 0 to force the conversion to a

   temporary table in memory.
   It had no effect in my queries, too.
So, none worked.

I have already read T.Kyte's examples on his web site http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:3779680732446#15740265481549  Subject "Oracle 9i generates statistics automatically?"  comment in reply to Steve's questions from February 18, 2004

and can reproduce his example with STR2TBL as function. (its in the middle of the topic.)
But, if I try my own procedure in his kind of query CARDINALITY shows no effect.
I tried many different versions and hints, but failed.

You can find the complete source code below.

> For the first case you can try probably as well
> first_rows_1 hint.

[ use several optimized queries with hints for different   ranges if required ]

I hope to get away with two cases, one for few rows and one for nearly all. But first I have to find two different queries that work.

If you have any comment to my example code below or have an working example please let me know.

Thank you,

Volker

PS.
  CARDINALITY does ignore the ',' in the parameter list, right?   CARDINALITY(A 2) and CARDINALITY(A, 2) are OK?


SELECT /*+ cardinality(SG, 10) */ * FROM ( SELECT * from TABLE (
 TEST_MODUL.test_data(

     CURSOR(SELECT * FROM TEST_SMALL_RESULTSET )
     , 5)) SG) 

;

Execution Plan


   0 SELECT STATEMENT Optimizer=CHOOSE (Cost=11 Card=8168 Bytes=318552)    1 0 VIEW (Cost=11 Card=8168 Bytes=318552)

   2    1     VIEW (Cost=11 Card=8168 Bytes=1078176)
   3    2       COLLECTION ITERATOR (PICKLER FETCH) OF 'TEST_DATA'

SELECT * FROM (
SELECT /*+ cardinality(SG, 200) */ * from TABLE (  TEST_MODUL.test_data(

     CURSOR(SELECT * FROM TEST_SMALL_RESULTSET )
     , 5)) SG)

/
-- same Card values

SELECT /*+ cardinality(SG, 10) */ * FROM ( SELECT /*+ cardinality(SG, 10) */ * from TABLE (  TEST_MODUL.test_data(

     CURSOR(SELECT * FROM TEST_SMALL_RESULTSET )
     , 5)) SG) 

;
-- same Card values

/* this one mimics the layout of T.Kyte example closer, * but shows no change in the query plan with hints */

select * from TEST_SMALL_RESULTSET
where ID_TABLE_A IN (SELECT /*+ cardinality(SG 3) */ ID_TABLE_A FROM TABLE (TEST_MODUL.test_data2) SG );

CREATE OR REPLACE PACKAGE TEST_MODUL
IS
FUNCTION test_data( v_cursor IN sys_refcursor

                      ,batch_size  IN NUMBER)
   RETURN test_data_ids
   PIPELINED;
FUNCTION test_data2

   RETURN test_data_ids
   PIPELINED ;
FUNCTION test_data3

   RETURN test_data_ids;
END;
/
CREATE OR REPLACE PACKAGE BODY TEST_MODUL IS

--
FUNCTION test_data( v_cursor    IN sys_refcursor
                       ,batch_size IN NUMBER
            )
    RETURN test_data_ids
    PIPELINED AS
        ID_TABLE_A       coll_number;
        ATTRIB_1         coll_number;
        ATTRIB_2         coll_number;
     --
        v_batch_size     NUMBER := batch_size;
        l_row		 test_data_rowType; 
    BEGIN
	IF v_batch_size <= 0 THEN
            v_batch_size := 100;
        END IF;
        LOOP 
            FETCH v_cursor  BULK COLLECT
		INTO  ID_TABLE_A 
                    , ATTRIB_1  
                    , ATTRIB_2
              LIMIT v_batch_size;
            --
            -- long complicated comptuation ommitted
            -- 
            FOR i in 1 .. ID_TABLE_A.count() LOOP
                -- long complicated comptuation ommitted
                PIPE ROW (
                  test_data_rowType(
                      ID_TABLE_A(i)
                    , ATTRIB_1(i)
                    , ATTRIB_1(i)
		));
	    END LOOP;
            EXIT WHEN v_cursor%NOTFOUND;
        END LOOP;
    	CLOSE v_cursor;
       	RETURN;
    END;
FUNCTION test_data2
    RETURN test_data_ids
    PIPELINED
AS
    BEGIN
        for rec in (select * from TEST_SMALL_RESULTSET) LOOP 
            --
            PIPE ROW (
                  test_data_rowType(
                      rec.ID_TABLE_A
                    , rec.ATTRIB_1
                    , rec.ATTRIB_2
		));
        END LOOP;
       	RETURN;
    END;
FUNCTION test_data3
    RETURN test_data_ids
AS
	l_data   test_data_ids := test_data_ids();
BEGIN
        for rec in (select * from TEST_SMALL_RESULTSET) LOOP 
            --
	    l_data.extend;
            l_data(l_data.count) := 
                  test_data_rowType(
                      rec.ID_TABLE_A
                    , rec.ATTRIB_1
                    , rec.ATTRIB_2
		);
        END LOOP;
       	RETURN l_data;
    END;
END;
/
show errors

=================== crate and fill table with data
create table TEST_SMALL_RESULTSET(
	ID_TABLE_A NUMBER, 
	ATTRIB_1   NUMBER, 
	ATTRIB_2   NUMBER	
);

declare
  c_size NUMBER := 10;	
begin 
  for id IN  1 .. c_size
  loop
     insert into 
        TEST_SMALL_RESULTSET(ID_TABLE_A, ATTRIB_1, ATTRIB_2)
        VALUES(id, dbms_random.value(1,10), 
		   dbms_random.value(1,20));
  end loop;
  commit;
end;
/



-- 
Volker Apelt                   
Received on Thu Aug 17 2006 - 07:57:46 CDT

Original text of this message

HOME | ASK QUESTION | ADD INFO | SEARCH | E-MAIL US