Creating List partition to an already Existing Table - oracle

I am trying to Create a list partition Based on the column "REFRESH_FLAG_Y" which has only Y and N as its Values, Below is the Alter Table used to Create the partition
ALTER TABLE "EDW"."LABOR_SCHEDULE_DAY_F" MODIFY
PARTITION BY LIST ("REFRESH_FLAG")
(PARTITION "REFRESH_FLAG_Y" VALUES ('Y') ,
PARTITION "REFRESH_FLAG_N" VALUES ('N')) ;
COMMIT;
But Whenever I execute the code I get an Error message
ERROR at line 1:
ORA-14400: inserted partition key does not map to any partition

You did tag the question with Oracle 11g tag; do you really use it?
This is a 12c example; it works if everything is OK:
SQL> create table labor_schedule_day_f as
2 select 1 id, 'Y' refresh_flag from dual union all
3 select 2 id, 'N' refresh_flag from dual;
Table created.
SQL> alter table labor_schedule_Day_f modify
2 partition by list (refresh_flag)
3 (partition refresh_flag_y values ('Y'),
4 partition refresh_flag_n values ('N')
5 );
Table altered.
Error you reported means this:
SQL> drop table labor_schedule_day_f;
Table dropped.
SQL> create table labor_schedule_day_f as
2 select 1 id, 'Y' refresh_flag from dual union all
3 select 2 id, 'N' refresh_flag from dual;
Table created.
Insert a row whose REFRESH_FLAG isn't Y nor N (so it violates the rule you specified):
SQL> insert into labor_schedule_day_f values (3, 'X');
1 row created.
Using the same ALTER TABLE statement as previously:
SQL> alter table labor_schedule_Day_f modify
2 partition by list (refresh_flag)
3 (partition refresh_flag_y values ('Y'),
4 partition refresh_flag_n values ('N')
5 );
alter table labor_schedule_Day_f modify
*
ERROR at line 1:
ORA-14400: inserted partition key does not map to any partition
SQL>
See? Error you got, which means that
which has only Y and N as its Values
isn't true.
P.S. You'd get the same result even if refresh_flag was NULL for some rows.

Related

Oracle: constraint preventing insert more than N (variable) rows

I've got a table defining the max number of objects for each customer.
Table_1
id_table_1 numeric primary key
cd_object varchar2(20)
max_number number
Another table stores the object assigned to each customer
Table_2
id_table_2 numeric primary key
cd_customer varchar2(20)
cd_object varchar2(20)
How can I set up a constraint in table_2 in order to prevent more than "max_number" record for each "customer - object" couple?
For example:
Table_1
cd_object / max_number
xxx / 1
yyy / 2
Table_2
insert "customer_1", "xxx" -> OK!
insert "customer_1", "xxx" -> KO!
insert "customer_1", "yyy" -> OK!
insert "customer_1", "yyy" -> OK!
insert "customer_1", "yyy" -> KO!
Thanks in advance for your replies.
This constraint is more complex than a CHECK constraint can handle. One day we hope Oracle will support SQL ASSERTIONS which are constraints of arbitrary complexity.
Meanwhile this can be done (with caution re performance) using materialized views (MVs) and constraints. I blogged about this may years ago: your requirement is very similar to my example 3 there. Applying to your case it would be something like:
create materialized view table_2_mv
build immediate
refresh complete on commit as
select t2.cd_customer, t2.cd_object, t1.max_number, count(*) cnt
from table_2 t2
join table_1 t1 on t1.cd_object = t2.cd_object
group by t2.cd_customer, t2.cd_object, t1.max_number;
alter table table_2_mv
add constraint table_2_mv_chk
check (cnt <= max_number)
deferrable;
Pure trigger-based solutions tend to fail in the real world as when 2 users similtaneously add a record that just takes the count to the maximum, both succeed and when committed leave the table with more rows than the maximum in it!
However, taking into account your comment that you have 2M rows in table_2, which perhaps makes the MV approach above unusable, there could be another approach that does involve triggers:
Create a table that denormalizes information from table_1 and table_2 like this:
create table denorm as
select t2.cd_customer, t2.cd_object, t1.max_number, count(*) cnt
from table_2 t2
join table_1 t1 on t1.cd_object = t2.cd_object
group by t2.cd_customer, t2.cd_object, t1.max_number;
Use a trigger or triggers on table_1 to ensure denorm.max_number is always correct - e.g when table_1.max_number is updated to a new value, update the corresponding denorm table rows.
Use a trigger or triggers on table_2 to update the denorm.cnt value - e.g. when a row is added, increment denorm.cnt, when a row is deleted, decrement it.
Add a check constraint to denorm
alter table denorm
add constraint denorm_chk
check (cnt <= max_number);
This is essentially the same as the MV solution, but avoids the full refresh by using triggers to maintain the denorm table as you go along. It works in a multi-user system because the updates to the denorm table serialize changes to table_2 so that 2 users cannot modify it simultaneously and break the rules.
You can use the trigger on TABLE_2 as following:
-- creating the tables
SQL> CREATE TABLE TABLE_1 (
2 ID_TABLE_1 NUMBER PRIMARY KEY,
3 CD_OBJECT VARCHAR2(20),
4 MAX_NUMBER NUMBER
5 );
Table created.
SQL> CREATE TABLE TABLE_2 (
2 ID_TABLE_2 NUMBER PRIMARY KEY,
3 CD_CUSTOMER VARCHAR2(20),
4 CD_OBJECT VARCHAR2(20)
5 );
Table created.
-- creating the trigger
SQL> CREATE OR REPLACE TRIGGER TRG_TABLE_2_MAX_OBJECT BEFORE
2 INSERT OR UPDATE ON TABLE_2
3 FOR EACH ROW
4 DECLARE
5 LV_MAX_NUMBER TABLE_1.MAX_NUMBER%TYPE;
6 LV_COUNT NUMBER;
7 BEGIN
8 BEGIN
9 SELECT
10 MAX_NUMBER
11 INTO LV_MAX_NUMBER
12 FROM
13 TABLE_1
14 WHERE
15 CD_OBJECT = :NEW.CD_OBJECT;
16
17 EXCEPTION
18 WHEN OTHERS THEN
19 LV_MAX_NUMBER := -1;
20 END;
21
22 SELECT
23 COUNT(1)
24 INTO LV_COUNT
25 FROM
26 TABLE_2
27 WHERE
28 CD_OBJECT = :NEW.CD_OBJECT;
29
30 IF LV_MAX_NUMBER = LV_COUNT AND LV_MAX_NUMBER >= 0 THEN
31 RAISE_APPLICATION_ERROR(-20000, 'Not allowed - KO');
32 END IF;
33
34 END;
35 /
Trigger created.
-- testing the code
SQL> INSERT INTO TABLE_1 VALUES (1,'xxx',1);
1 row created.
SQL> INSERT INTO TABLE_1 VALUES (2,'yyy',2);
1 row created.
SQL> INSERT INTO TABLE_2 VALUES (1,'customer_1','xxx');
1 row created.
SQL> INSERT INTO TABLE_2 VALUES (2,'customer_1','xxx');
INSERT INTO TABLE_2 VALUES (2,'customer_1','xxx')
*
ERROR at line 1:
ORA-20000: Not allowed - KO
ORA-06512: at "TEJASH.TRG_TABLE_2_MAX_OBJECT", line 28
ORA-04088: error during execution of trigger 'TEJASH.TRG_TABLE_2_MAX_OBJECT'
SQL> INSERT INTO TABLE_2 VALUES (3,'customer_1','yyy');
1 row created.
SQL> INSERT INTO TABLE_2 VALUES (4,'customer_1','yyy');
1 row created.
SQL> INSERT INTO TABLE_2 VALUES (5,'customer_1','yyy');
INSERT INTO TABLE_2 VALUES (5,'customer_1','yyy')
*
ERROR at line 1:
ORA-20000: Not allowed - KO
ORA-06512: at "TEJASH.TRG_TABLE_2_MAX_OBJECT", line 28
ORA-04088: error during execution of trigger 'TEJASH.TRG_TABLE_2_MAX_OBJECT'
SQL>
-- Checking the data in the TABLE_2
SQL> SELECT * FROM TABLE_2;
ID_TABLE_2 CD_CUSTOMER CD_OBJECT
---------- -------------------- --------------------
1 customer_1 xxx
3 customer_1 yyy
4 customer_1 yyy
SQL>
Cheers!!

How to select partition name of specific data?

I want to select a data and wanna see in which partition.
partition column is : code (varchar column)
Select .... -- I want to find partition name
from
partition_table
where to_number(code) = 55;
why I need to this:
I have a data which code is '55' but in that table when I use partition column I do not select it. But there is data which value is '55'
So I want to that data in which partition.
And the data is not in PDEFAULT partition. I ve already check it.
edit
data is in another partition. I think there is a problem with exchange partition process
thanks in advance
There are a couple of ways.
1) The rowid will point to the partition object
SQL> create table t ( x int, y int )
2 partition by range (x )
3 ( partition p1 values less than ( 100 ),
4 partition p2 values less than ( 200 )
5 );
Table created.
SQL>
SQL> insert into t values (34,34);
1 row created.
SQL>
SQL> select rowid from t;
ROWID
------------------
AAA0cqAAHAAAAQ6AAA
1 row selected.
SQL>
SQL> select dbms_rowid.rowid_object(rowid) from t;
DBMS_ROWID.ROWID_OBJECT(ROWID)
------------------------------
214826
1 row selected.
SQL>
SQL> select subobject_name
2 from user_objects
3 where data_Object_id =
4 ( select dbms_rowid.rowid_object(rowid) from t );
SUBOBJECT_NAME
------------------------------------------------------------
P1
2) You can data mine the dictionary to probe the HIGH_VALUE column in USER_TAB_PARTITIONS. I did a video on how to do that here
https://www.youtube.com/watch?v=yKHQQXKdfOM

Drop a column from a Range Partitioned and Compressed table

I have a table with data and need to remove a column, which is marked as unused. But it gives the error due to the compressed table.
I have used the command
ALTER TABLE <table name> MOVE NOCOMPRESS NOLOGGING PARALLEL 4;
But it gives this error:
ORA-14511: cannot perform operation on a partitioned object
How can I disable the partitioned? And how can I remove the unused column?
No, you cannot move partitioned table with one alter table statement, you need to perform relocation of that table into a new segment partition by partition:
Create test table:
SQL> create table t1(
2 col1 number,
3 col2 number
4 )
5 partition by range(col1) (
6 partition p_1 values less than (10) compress,
7 partition p_2 values less than (20) compress
8 );
Table created.
Populate test table with some sample data:
SQL> insert into t1(col1, col2)
2 select level
3 , level
4 from dual
5 connect by level <= 3;
3 rows created.
SQL> commit;
Commit complete.
SQL> select * from t1;
COL1 COL2
---------- ----------
1 1
2 2
3 3
Drop column statement fails:
SQL> alter table t1 drop column col2;
alter table t1 drop column col2
*
ERROR at line 1:
ORA-39726: unsupported add/drop column operation on compressed tables
Table relocation fails:
SQL> alter table t1 move nocompress;
alter table t1 move nocompress
*
ERROR at line 1:
ORA-14511: cannot perform operation on a partitioned object
Perform relocation of each partition:
SQL> alter table t1 move partition p_1 nocompress;
Table altered.
SQL> alter table t1 move partition p_2 nocompress;
Table altered.
When there are too many partitions, you can easily generate alter table statements while querying user_tab_partitions data dictionary view. For example:
SQL> column res format a50
SQL> select 'alter table ' || t.table_name ||
2 ' move partition ' || t.partition_name ||
3 ' nocompress;' as res
4 from user_tab_partitions t
5 where t.table_name = 'T1';
RES
--------------------------------------------------
alter table T1 move partition P_1 nocompress;
alter table T1 move partition P_2 nocompress;
After you have moved all partitions with nocompress option, you can drop column(s) issuing:
alter table t1 drop column col2
statement, or
alter table t1 drop unused columns
statement, if you already marked column(s) as unused before relocation.
Dropping unused columns:
Make col2 unused
SQL> alter table t1 set unused(col2);
Table altered.
List tables with unused columns in our schema
SQL> column table_name format a5
SQL> column table_name format a5
SQL> select *
2 from user_unused_col_tabs;
TABLE COUNT
----- ----------
T1 1
Relocate partitions
SQL> alter table T1 move partition P_1 nocompress;
Table altered.
SQL> alter table T1 move partition P_2 nocompress;
Table altered.
Drop unused columns:
SQL> alter table t1 drop unused columns;
Table altered.
Make sure we dropped everything we wanted to drop. Col2 is gone:
SQL> desc t1;
Name Null? Type
-------- -------- -----------
COL1 NUMBER
There are no tables with unused columns:
SQL> select *
2 from user_unused_col_tabs;
no rows selected

Oracle: how to drop a subpartition of a specific partition

I am using an oracle 11 table with interval partitioning and list subpartitioning like this (simplified):
CREATE TABLE LOG
(
ID NUMBER(15, 0) NOT NULL PRIMARY KEY
, MSG_TIME DATE NOT NULL
, MSG_NR VARCHAR2(16 BYTE)
) PARTITION BY RANGE (MSG_TIME) INTERVAL (NUMTOYMINTERVAL (1,'MONTH'))
SUBPARTITION BY LIST (MSG_NR)
SUBPARTITION TEMPLATE (
SUBPARTITION login VALUES ('FOO')
, SUBPARTITION others VALUES (DEFAULT)
)
(PARTITION oldvalues VALUES LESS THAN (TO_DATE('01-01-2010','DD-MM-YYYY')));
How do I drop a specific subpartitition for a specific month without knowing the (system generated) name of the subpartition? There is a syntax "alter table ... drop subpartition for (subpartition_key_value , ...)" but I don't see a way to specify the month for which I am deleting the subpartition. The partition administration guide does not give any examples, either. 8-}
You can use the metadata tables to get the specific subpartition name:
SQL> insert into log values (1, sysdate, 'FOO');
1 row(s) inserted.
SQL> SELECT p.partition_name, s.subpartition_name, p.high_value, s.high_value
2 FROM user_tab_partitions p
3 JOIN
4 user_tab_subpartitions s
5 ON s.table_name = p.table_name
6 AND s.partition_name = p.partition_name
7 AND p.table_name = 'LOG';
PARTITION_NAME SUBPARTITION_NAME HIGH_VALUE HIGH_VALUE
--------------- ------------------ ------------ ----------
OLDVALUES OLDVALUES_OTHERS 2010-01-01 DEFAULT
OLDVALUES OLDVALUES_LOGIN 2010-01-01 'FOO'
SYS_P469754 SYS_SUBP469753 2012-10-01 DEFAULT
SYS_P469754 SYS_SUBP469752 2012-10-01 'FOO'
SQL> alter table log drop subpartition SYS_SUBP469752;
Table altered.
If you want to drop a partition dynamically, it can be tricky to find it with the ALL_TAB_SUBPARTITIONS view because the HIGH_VALUE column may not be simple to query. In that case you could use DBMS_ROWID to find the subpartition object_id of a given row:
SQL> insert into log values (4, sysdate, 'FOO');
1 row(s) inserted.
SQL> DECLARE
2 l_rowid_in ROWID;
3 l_rowid_type NUMBER;
4 l_object_number NUMBER;
5 l_relative_fno NUMBER;
6 l_block_number NUMBER;
7 l_row_number NUMBER;
8 BEGIN
9 SELECT rowid INTO l_rowid_in FROM log WHERE id = 4;
10 dbms_rowid.rowid_info(rowid_in =>l_rowid_in ,
11 rowid_type =>l_rowid_type ,
12 object_number =>l_object_number,
13 relative_fno =>l_relative_fno ,
14 block_number =>l_block_number ,
15 row_number =>l_row_number );
16 dbms_output.put_line('object_number ='||l_object_number);
17 END;
18 /
object_number =15838049
SQL> select object_name, subobject_name, object_type
2 from all_objects where object_id = '15838049';
OBJECT_NAME SUBOBJECT_NAME OBJECT_TYPE
--------------- --------------- ------------------
LOG SYS_SUBP469757 TABLE SUBPARTITION
As it turns out, the "subpartition for" syntax does indeed work, though that seems to be a secret Oracle does not want to tell you about. :-)
ALTER TABLE TB_LOG_MESSAGE DROP SUBPARTITION FOR
(TO_DATE('01.02.2010','DD.MM.YYYY'), 'FOO')
This deletes the subpartition that would contain MSG_TIME 2010/02/01 and MSG_NR FOO. (It is not necessary that there is an actual row with this exact MSG_TIME and MSG_NR. It throws an error if there is no such subpartition, though.)
Thanks for the post - it was very useful for me.
One observation though on the above script to identify the partition and delete it:
The object_id returned by dbms_rowid.rowid_info is not the object_id of the all_objects table. It is actually the data_object_id. It is observed that usually these ids match. However, after truncating the partitioned table several times, these ids diverged in my database. Hence it might be reasonable to instead use the data_object_id to find out the name of the partition:
select object_name, subobject_name, object_type
from all_objects where data_object_id = '15838049';
From the table description of ALL_OBJECTS:
OBJECT_ID Object number of the object
DATA_OBJECT_ID Object number of the segment which contains the object
http://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_rowid.htm
In the sample code provided in the above link, DBMS_ROWID.ROWID_OBJECT(row_id) is used instead to derive the same information that is given by dbms_rowid.rowid_info. However, the documentation around this sample mentions that it is a data object number from the ROWID.
Examples
This example returns the ROWID for a row in the EMP table, extracts
the data object number from the ROWID, using the ROWID_OBJECT function
in the DBMS_ROWID package, then displays the object number:
DECLARE object_no INTEGER; row_id ROWID; ... BEGIN
SELECT ROWID INTO row_id FROM emp
WHERE empno = 7499; object_no := DBMS_ROWID.ROWID_OBJECT(row_id); DBMS_OUTPUT.PUT_LINE('The obj. # is
'|| object_no); ...

How can I import a partition from one table into another in Oracle?

I would like to know if the following steps are possible and how fast this is:
Create a partition named part1 in Table A
Drop partition part1 in Table B
Import the Table A partition part1 into Table B
Can you provide me with an example if it is possible indeed? Or any resources I can look at?
Note that the tables would have the exact same structure.
You can do something similar with the ALTER TABLE ... EXCHANGE PARTITION command. This would exchange a single partition with a table that has the same structure.
A little example:
/* Partitionned Table Creation */
SQL> CREATE TABLE table_a (
2 ID NUMBER PRIMARY KEY,
3 DATA VARCHAR2(200)
4 )
5 PARTITION BY RANGE (ID) (
6 PARTITION part100 VALUES LESS THAN (100),
7 PARTITION part200 VALUES LESS THAN (200)
8 );
Table created
/* Swap table creation */
SQL> CREATE TABLE swap_table (
2 ID NUMBER PRIMARY KEY,
3 DATA VARCHAR2(200)
4 );
Table created
SQL> INSERT INTO swap_table SELECT ROWNUM, 'a' FROM dual CONNECT BY LEVEL <= 99;
99 rows inserted
SQL> select count(*) from table_a partition (part100);
COUNT(*)
----------
0
This will exchange the partition part100 with the transition table swap_table:
SQL> ALTER TABLE table_a EXCHANGE PARTITION part100 WITH TABLE swap_table;
Table altered
SQL> select count(*) from table_a partition (part100);
COUNT(*)
----------
99

Resources