Skip to Main Content
  • Questions
  • PARTITION names in a stored procedure

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Ed.

Asked: July 10, 2001 - 4:33 pm UTC

Last updated: July 02, 2012 - 9:36 am UTC

Version: 8.1.7.1

Viewed 10K+ times! This question is

You Asked

Hi Tom--
I have the most vexing problem-- I have a stored procedure to look at a very volatile table consisting of 150 partitions, if the partition is empty, truncate. Long story, but the partitions of this table can grow enormously and be deleted from in their entirety by an application. I would like this truncate routine to run at night (no contention with users) and lower the high water mark of these partitions to help performance.

This is the procedure:
==================================================================
CREATE OR REPLACE PROCEDURE CLEANUP_SANDBOX AS
v_count NUMBER;
v_partition_name VARCHAR2(30);
sql_statement VARCHAR2(100);

cursor SANDBOX_USERS IS
select unique partition_name from USER_TAB_PARTITIONS
where TABLE_NAME like 'CONFIG_SANDBOX';

BEGIN
FOR SANDBOX_RECORD IN SANDBOX_USERS LOOP

v_partition_name := sandbox_record.partition_name;

DBMS_OUTPUT.PUT_LINE(v_partition_name);

SELECT count(*) INTO v_count FROM xcm.config_sandbox partition (v_partition_name);

if v_count = 0 THEN
DBMS_OUTPUT.PUT_LINE(v_count);
sql_statement := 'ALTER TABLE XCM.CONFIG_SANDBOX TRUNCATE PARTITION PT'||v_partition_name;
DBMS_OUTPUT.PUT_LINE(sql_statement);
EXECUTE IMMEDIATE sql_statement;
DBMS_OUTPUT.PUT_LINE('TRUNCATED PARTITION '||v_partition_NAME);
end if;

END LOOP;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SUBSTR(SQLERRM,1,80));
END;
=================================================================
I consistently get invalid partition name (ora-02149). Why? Config_sandbox has 150 partitions name 'PT1'...'PT150'. When I run this as a PL/SQL block in SQLPlus and supply the partition name (eg. PT1) it runs good as gold. My dbms_output of partition_name supplies 'PT1' (see below) so this is correctly getting into the variable. I would guess a datatype mismatch is going on but from all I can discover the partition_name is varchar2(30).

serveroutput:
SQL> execute xcm.cleanup_sandbox
PT1
ORA-02149: Specified partition does not exist

PL/SQL procedure successfully completed.

Any suggestions?


and Tom said...

You cannot use varibles where an IDENTIFIER is mandatory - the queries:

select * from t partition (p1);
select * from t partition (p2);

are totally different queries and need to be parsed/optimized/security checked and everything separately.

You must use dynamic sql.

A faster approach, rather then read every row, is just check for A row:

execute immediate
'select count(*)
from dual
where exists ( select NULL
from xcm.config_sandbox
partition( ' || v_partition_name || ')
)' INTO v_count;


That'll do as little work as possible....


Rating

  (5 ratings)

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

Comments

A reader, June 26, 2009 - 10:39 am UTC

Hi Tom,

Do an
execute immediate 'insert into table partition ('||partition name||) '

will be slower than
insert into table partition ('MY_PART')

Thanks
Tom Kyte
June 26, 2009 - 11:22 am UTC

it depends. totally insufficient data to answer.

"Maybe" is the only resonable answer one can give.



I can give you examples whereby they would be identical performance wise and another whereby the static sql would be better and another whereby the dynamic sql would be.


so, you have to provide more background information.


a) case a - they are same

in this case, you are executing an insert into table AS SELECT. You do this once in the session. The AS SELECT takes some measurable amount of time.

In this case, both will parse the sql statement once (a sql statement has to be parsed AT LEAST once and AT MOST once per session - if you execute it once, it'll be parsed once). Since they both parse once and both execute it once, they will spend the same amount of resources exactly - they are the same in this case.


b) case b - static sql is best.


You have a routine like:


CREATE TABLE t
(
  dt  date,
  x   int,
  y   varchar2(30)
)
PARTITION BY RANGE (dt)
(
  PARTITION part1 VALUES LESS THAN (to_date('01-jan-2008','dd-mon-yyyy')) ,
  PARTITION part2 VALUES LESS THAN (to_date('01-jan-2009','dd-mon-yyyy'))
)
/

create or replace procedure static
as
begin
    insert into t partition(part1) values ( sysdate-1000, 1, 1 );
    update t set x = x+1 where rownum = 1;
end;
/

create or replace procedure dynamic
as
    l_query long;
begin
    for i in 1 .. 2
    loop
        if ( i = 1 )
        then
            l_query := 'insert into t partition(part1) values ( sysdate-1000, 1, 1 )';
        else
            l_query := 'update t set x = x+1 where rownum = 1';
        end if;
        execute immediate l_query;
    end loop;
end;
/


@trace
exec static
exec dynamic
connect /

@trace
exec static
exec static
exec dynamic
exec dynamic
@tk "sys=no"



the tkprof would show that the static insert is parsed once and executed many, whereas the dynamic is parsed once per execution (note the wacky code in the dynamic routine, if I used different logic, this would not be the case - execute immediate caches the last executed sql statement on a given line of code)

c) case c - dynamic sql is better. Here you have some weird sql statement that is generic beyond belief (like a huge multi-table insert or something with every possible case considered). You spend so many extra resources being generic in the static sql - that it is just inefficient.

bulk select for partition and insert into partition

SSS, June 30, 2012 - 12:51 am UTC

Hi Tom,
I am trying to write a pl/sql block to initially select the partition names for table 'A'
then loop for each partition found and perform the query as
insert into B as select col1 from A partition (partition name) where conditions

How do I start for this

Thanks
Tom Kyte
July 02, 2012 - 7:00 am UTC

you'd have to use dynamic sql.

but it would beg the question - why would you not just

insert /*+ append */ into b select col1 from a where conditions;


????

dataobj to partition

Laurent Schneider, July 02, 2012 - 7:28 am UTC

or maybe with dataobj to partition, which does not require execute immediate

http://docs.oracle.com/cd/E11882_01/server.112/e26088/functions047.htm#CIHCFEAF

drop table t;
create table t(x) partition by hash(x) partitions 8 as select rownum from all_objects;
declare 
  i number;
begin 
  for f in (select * from user_objects where object_name='T'and OBJECT_TYPE='TABLE PARTITION')
  loop 
    select count(*) into i from t partition (DATAOBJ_TO_PARTITION(t,f.DATA_OBJECT_ID));
    dbms_output.put_line(f.subobject_name||' has '||i||' row(s)');
  end loop;
end;
/

SYS_P84 has 1467 row(s)
SYS_P83 has 1457 row(s)
SYS_P82 has 1474 row(s)
SYS_P81 has 1379 row(s)
SYS_P80 has 1376 row(s)
SYS_P79 has 1431 row(s)
SYS_P78 has 1432 row(s)
SYS_P77 has 1473 row(s)


Tom Kyte
July 02, 2012 - 9:36 am UTC

nice

but still begs the question "why" (for the above poster that is)

indeed

Laurent Schneider, July 02, 2012 - 10:30 am UTC

It would be definitely better to use neither dynamic sql nor dataobj to partition

Dataobj to partition is so uncommon that you will hardly find someone in Oracle Support to help you if you hit a bug. It is also documented as "unuseful" if you are not a domain index developer working with system partitioning ;)

DATAOBJ_TO_PARTITION is good + 2 alternates

Yaroslav Batozskiy, July 28, 2012 - 11:15 am UTC

DATAOBJ_TO_PARTITION is very convenient
Always specify DATA_OBJECT_ID (never OBJECT_ID) from ALL_OBJECTS as second parameter. If values is match ā€” then segment is never move yet. After moving ā€” DATA_OBJECT_ID is changing, and OBJECT_ID is still.
Function not work with partitioned IOT ā€” function related to Data Cartridge and for System partitioning, but system partitioning not applicable to IOT. I think, working with another 4 type of partitioning is side-effect, but is work good.

Iā€™m checked this function for _all 5 type_ of partitioning heap tables (11.2.0.3.0). All Ok!

But, you have these alternative variants, does not considered above:
1. Determining partition range through ALL_TAB_PARTITIONS.HIGH_VALUE and build WHERE clause
2. Use CASE :-)

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