Skip to Main Content

Breadcrumb

Question and Answer

Connor McDonald

Thanks for the question, Sunil Kumar.

Asked: June 16, 2017 - 5:10 am UTC

Last updated: July 08, 2017 - 11:25 am UTC

Version: 12C

Viewed 10K+ times! This question is

You Asked

Hi Connor,

After following Partitioning videos from KISS series started practicing on my own and have a query:

In a Range partition, I want to create the partioining based on Country column and all values that belong to AU should go to AU_01 PARTITION AND NZ should go to NZ_01 partition,

first point is, I cannot say PARTITION AU_01 VALUES EQUAL TO ('AU') -> SO ONLY THAT SPECIFIC PARTITIONS GO HERE ? IN BELOW CASE, WHEN I INSERT TWO ROWS These are going into NZ only.

CREATE TABLE sku_RANGE
(
   item      VARCHAR2 (100),
   loc       VARCHAR2 (100),
   country   VARCHAR2 (10)
)
PARTITION BY RANGE
   (country)
   (
      PARTITION au_01 VALUES LESS THAN ('AU'),
      PARTITION NZ_01 VALUES LESS THAN ('NZ'));

INSERT INTO sku_RANGE
     VALUES ('100', 'ABC', 'AU');

INSERT INTO sku_RANGE
     VALUES ('100', 'ABC', 'NX');

SELECT *
  FROM SKU_RANGE PARTITION (NZ_01);



with LiveSQL Test Case:

and Chris said...

For range partitioning, you need to provide a value larger than those to go in the partition. So for NZ to go in NZ_01, the less than value has to be 'NZA' (or something else "bigger" than 'NZ':

CREATE TABLE sku_RANGE
(
   item      VARCHAR2 (100),
   loc       VARCHAR2 (100),
   country   VARCHAR2 (10)
)
PARTITION BY RANGE
   (country)
   (
      PARTITION au_01 VALUES LESS THAN ('AUA'),
      PARTITION NZ_01 VALUES LESS THAN ('NZA'));

INSERT INTO sku_RANGE
     VALUES ('100', 'ABC', 'AU');

INSERT INTO sku_RANGE
     VALUES ('100', 'ABC', 'NZ');

SELECT *
  FROM SKU_RANGE PARTITION (NZ_01);

ITEM  LOC  COUNTRY  
100   ABC  NZ 


But if all you have is these two values, I recommend going for list partitions instead:

CREATE TABLE sku_list
(
   item      VARCHAR2 (100),
   loc       VARCHAR2 (100),
   country   VARCHAR2 (10)
)
PARTITION BY LIST
   (country)
   (
      PARTITION au_01 VALUES ('AU'),
      PARTITION NZ_01 VALUES ('NZ'));


INSERT INTO sku_list
     VALUES ('100', 'ABC', 'AU');

INSERT INTO sku_list
     VALUES ('100', 'ABC', 'NZ');

SELECT *
  FROM SKU_LIST PARTITION (NZ_01);

ITEM  LOC  COUNTRY  
100   ABC  NZ 

Rating

  (3 ratings)

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

Comments

Thanks

Sunil Kumar Noothi, June 29, 2017 - 6:49 am UTC

Can you please provide me the syntax for interval partitioning.

create table t1 (a1 number, b1 varchar2(100));

insert into t1 values (1,'abc');
insert into t1 values (2,'abc');
insert into t1 values (3,'abc');

For every new value of a1 in the table, I want a new partition. I saw a video for dates having numtoyearinterval, but trying to understand how we can achieve this with numbers
Connor McDonald
June 30, 2017 - 4:03 am UTC

SQL> create table t1 (a1 number, b1 varchar2(100))
  2  partition by range ( a1 )
  3  interval ( 1 )
  4  (
  5   partition p1 values less than ( 1 )
  6  );

Table created.

Excellent one

Sunil Kumar Noothi, July 06, 2017 - 9:25 am UTC

This is working perfectly as expected, and happy with your pointers.

Lastly, when a new partition is creating, i.e., if my partition column is 1, it's create new partition and if my partition column is 2, it's creating another partition perfectly. As per design it is creating a partition name as sys_1234 and sys_1235 and so on.... But is there way that I can specify a partition name based on partition value?


For example if I define a partition table as below:

create table (id number, value1 varchar2(100))
partition by range (id)
interval (numtodsinterval (1))
( partition p_value less than 10)
)


now if I enter a value with 11 a new partition will be created with name sys_xxxx rather can I create a partition with the name as 11?

Please let me know if this is possible?
Connor McDonald
July 07, 2017 - 2:24 am UTC

No you cannot name it, but you can *rename* it. So just have a job that runs (say) nightly that takes care of it.

Here's a code snippet from a routine I used to have to rename interval partitions for timestamps, but you can modify it to suit your needs

declare
  l_high_value         varchar2(4000);
  l_partition_boundary date;
  l_parname            varchar2(40);
  l_ddl                varchar2(4000);
  
BEGIN
    --
    -- then we clean up and rename interval partitions to make life easier for us
    --

    for i in ( 
      select a.owner, a.table_name, a.partitioning_type, t.partition_name, t.high_value, a.interval
      from   all_part_tables a, all_tab_partitions t
      where  a.owner in (...)
      and    a.partitioning_type = 'RANGE'
      and    a.table_name not like 'BIN$%'
      and    a.interval is not null
      and    t.table_owner = a.owner
      and    t.table_name  = a.table_name
      and    regexp_like(t.partition_name,'^SYS_P[[:digit:]]{1,10}') ) 
    loop  
      begin
        l_high_value := i.high_value;
        execute immediate 'select '||l_high_value||'-1 from dual' into l_partition_boundary;

        if upper(i.interval) like 'NUMTODSINTERVAL%' then
           l_parname := substr(i.table_name,1,21)||'_'||to_char(l_partition_boundary,'YYYYMMDD');
        elsif upper(i.interval) like 'NUMTOYMINTERVAL%MONTH%' then
           l_parname := substr(i.table_name,1,23)||'_'||to_char(l_partition_boundary,'YYYYMM');
        elsif upper(i.interval) like 'NUMTOYMINTERVAL%YEAR%' then
           l_parname := substr(i.table_name,1,25)||'_'||to_char(l_partition_boundary,'YYYY');
        elsif upper(i.interval) like 'INTERVAL''%''DAY%' then
           l_parname := substr(i.table_name,1,21)||'_'||to_char(l_partition_boundary,'YYYYMMDD');
        elsif upper(i.interval) like 'INTERVAL''%''MONTH%' then
           l_parname := substr(i.table_name,1,23)||'_'||to_char(l_partition_boundary,'YYYYMM');
        elsif upper(i.interval) like 'INTERVAL''%''YEAR%' then
           l_parname := substr(i.table_name,1,25)||'_'||to_char(l_partition_boundary,'YYYY');
        else
           -- unknown interval format... log it and skip over this partition
           l_parname := null;
           logger ('Cannot determine correct partition name for table ' || i.table_name || ' partition ' || i.partition_name); 
        end if;
      exception
        when others then
           l_parname := null;
           logger ('Cannot determine correct partition name for table ' || i.table_name || ' partition ' || i.partition_name||' hi='||l_high_value); 
      end;

      if (l_parname is not null) then
        l_ddl := 'alter table '||i.owner||'.'||i.table_name||' rename partition '||i.partition_name||' to '||l_parname;
        logger(l_ddl);

        begin
          execute immediate l_ddl;
        exception
          when others then
            logger('Failed with '||sqlerrm);
        end;

      end if;
    end loop;

    --
    -- then we can use those new partition names to do the same for indexes
    --

    for i in ( 
            with tabpar as ( 
             select /*+ materialize */ 
                     a.owner, a.table_name, a.partitioning_type, t.partition_name, t.partition_position
              from   all_part_tables a, all_tab_partitions t
              where  a.owner in (...)
              and    a.partitioning_type = 'RANGE'
              and    a.table_name not like 'BIN$%'
              and    a.interval is not null
              and    t.table_owner = a.owner
              and    t.table_name  = a.table_name
            ), indpar as (
             select /*+ materialize */ 
                     ai.owner, ai.table_name, ai.index_name, i.partition_name, i.partition_position
              from   all_part_indexes ai, all_ind_partitions i
              where  ai.owner in (...)
              and    ai.partitioning_type = 'RANGE'
              and    ai.table_name not like 'BIN$%'
              and    ai.interval is not null
              and    i.index_owner = ai.owner
              and    ai.index_name  = i.index_name
              and    ai.locality = 'LOCAL'
            )
            select t.owner, t.table_name, t.partition_name new_par_name, i.index_name, i.partition_name
                  from   tabpar t, indpar i
                  where  i.owner = t.owner
                  and    i.table_name = t.table_name
                  and    i.partition_position = t.partition_position
                  and    not regexp_like(t.partition_name,'^SYS_P[[:digit:]]{1,10}')
                  and    regexp_like(i.partition_name,'^SYS_P[[:digit:]]{1,10}')   
    )
    loop  
      l_ddl := 'alter index '||i.owner||'.'||i.index_name||' rename partition '||i.partition_name||' to '||i.new_par_name;
      logger(l_ddl);
      begin
        execute immediate l_ddl;
      exception
        when others then
          logger('Failed with '||sqlerrm);
      end;
    end loop;
END;



Awesome Reply

Sunil Kumar Noothi, July 07, 2017 - 2:47 am UTC

I asked for a suggestion and you reply me with solution, What can be more beautiful than this.

With all your suggestions, I created my first partition table and seeing great results. I will try to use your code to maintain

Now, I create a table of partition and validated the data and then dropped the table.

I cannot see that table in user_tables which is expected. but I can see entries in user_tab_partitions table.

however, table_name is replaced with BIN$U6CIYK4PdaPgU1crigquGw==$0

My understanding is there shouldn't be any entries in this user_tab_partitions table right?
Connor McDonald
July 08, 2017 - 11:25 am UTC

This is the name as it sits in the recycle bin.

You can see in my pseudo-code why I exclude tables prefixed with "BIN$"

More to Explore

VLDB

If you are new to partitioning, check out Connor McDonald's introduction series here.

VLDB

Documentation set on VLDB and Partitioning.