One of the great features about partitioning,
and most specifically range-based partitioning, is the ability to load
data quickly and easily with minimal impact on the current
users using:
alter table call exchange
partition data_2007 with table call_temp;
This command swaps over the definitions of the
named partition and the CALL table, so that the data suddenly exists
in the right place in the partitioned table. Moreover, with the
inclusion of the two optional extra clauses, index definitions will be
swapped and Oracle will not check whether the data actually belongs in
the partition - so the exchange is very quick.
In this example, the primary key and the
partition key in the partitioned table CALL both are build on the same
column «id».
Therefore the primary key on CALL can be a LOCAL index and the indexes
doesn't become UNUSABLE (must not be rebuild) after the EXCHANGE.
-- Create
the Partition Table (Destination Table)
CREATE TABLE call (
id NUMBER(12,6),
v1 VARCHAR2(10),
data VARCHAR2(100)
)
PARTITION BY RANGE(id) (
-- Partion Key = Primary Key
PARTITION call_partition VALUES LESS THAN (MAXVALUE)
);
-- Next,
create the temporary Table which is used to load the Data offline
CREATE TABLE call_temp (
id NUMBER(12,6),
v1 VARCHAR2(10),
data VARCHAR2(100)
);
-- Load
1'000'000 Rows into the offline Table
INSERT /*+ append ordered full(s1) use_nl(s2) */
INTO call_temp
SELECT
TRUNC((ROWNUM-1)/500,6),
TO_CHAR(ROWNUM),
RPAD('X',100,'X')
FROM
all_tables s1,
all_tables s2
WHERE
ROWNUM <= 1000000;
-- Add
LOCAL Primary Key to the Partition Table as a local Index
ALTER TABLE call
ADD CONSTRAINT pk_call PRIMARY KEY(id)
USING INDEX (CREATE INDEX pk_call ON CALL(id) NOLOGGING
LOCAL);
-- Add Primary Key to the offline Table
ALTER TABLE call_temp
ADD CONSTRAINT pk_call_temp PRIMARY KEY(id)
USING INDEX (CREATE INDEX pk_call_temp ON call_temp(id) NOLOGGING);
-- Now swap the offline Table into the Partition
ALTER TABLE CALL
EXCHANGE PARTITION call_partition WITH TABLE call_temp
INCLUDING INDEXES
WITHOUT VALIDATION;
Elapsed: 00:00:00.01
Oracle is checking that the exchange won’t
cause a uniqueness problem. The query is searching the entire CALL
table (excluding the partition we are exchanging) to see if there are
any duplicates of the rows which we are loading. This is particularly
daft, since the unique constraint is maintained through a local index,
so it must include the partitioning key ID, which means there is only
one legal partition in which a row can be, and we have already
promised (through the without validation clause) that all the rows
belong where we are putting them.
-- For the next load, first
truncate the CALL Table, then process the next load.
TRUNCATE TABLE call;
ALTER TABLE CALL
EXCHANGE PARTITION call_partition WITH TABLE call_temp
INCLUDING INDEXES
WITHOUT VALIDATION;
In this example, the primary key and the
partition key in the partitioned table CALL are NOT the same. The
primary key is build on the column
«id»,
but the partition key is build on «created_date».
Therefore the primary key on CALL must be a GLOBAL index and the
indexes must be maintained using the clause UPDATE GLOBAL
INDEXES in the EXCHANGE.
-- Create
and populate a small lookup table
CREATE TABLE lookup (
id
NUMBER(10),
description VARCHAR2(50)
);
ALTER TABLE lookup ADD (
CONSTRAINT pk_lookup PRIMARY KEY (id)
);
INSERT INTO lookup (id, description) VALUES (1, 'ONE');
INSERT INTO lookup (id, description) VALUES (2, 'TWO');
INSERT INTO lookup (id, description) VALUES (3, 'THREE');
COMMIT;
-- Create and populate a temporary table to load
the data
CREATE TABLE call_temp (
id
NUMBER(10),
created_date DATE,
lookup_id NUMBER(10),
data VARCHAR2(50)
);
-- Load the temporary table
DECLARE
l_lookup_id lookup.id%TYPE;
l_create_date DATE;
BEGIN
FOR i IN 1 .. 1000000 LOOP
IF MOD(i, 3) = 0 THEN
l_create_date := ADD_MONTHS(SYSDATE, -24);
l_lookup_id := 2;
ELSIF MOD(i, 2) = 0 THEN
l_create_date := ADD_MONTHS(SYSDATE, -12);
l_lookup_id := 1;
ELSE
l_create_date := SYSDATE;
l_lookup_id := 3;
END IF;
INSERT INTO call_temp (id, created_date, lookup_id, data)
VALUES (i, l_create_date, l_lookup_id, 'Loaded Data for ' ||
i);
END LOOP;
COMMIT;
END;
/
-- Apply Primary Key to the CALL_TEMP table.
ALTER TABLE call_temp ADD (
CONSTRAINT pk_call_temp PRIMARY KEY (id)
);
-- Add Key and Foreign Key Constraint to the
CALL_TEMP table.
CREATE INDEX idx_call_temp_created_date ON call_temp(created_date);
CREATE INDEX idx_call_temp_lookup_id ON call_temp(lookup_id);
ALTER TABLE call_temp ADD (
CONSTRAINT fk_call_temp_lookup_id
FOREIGN KEY (lookup_id)
REFERENCES lookup(id)
);
-- Next we create a new table with the
appropriate partition structure
-- to act as the destination table. The destination must have the
-- same constraints and indexes defined.
CREATE TABLE call (
id
NUMBER(10),
created_date DATE,
lookup_id NUMBER(10),
data VARCHAR2(50)
)
PARTITION BY RANGE (created_date)
(PARTITION call_temp_created_date VALUES LESS THAN (MAXVALUE));
-- Add Primary Key to CALL which must be GLOBAL
ALTER TABLE call ADD
CONSTRAINT pk_call PRIMARY KEY (id)
USING INDEX (CREATE INDEX pk_call ON CALL(id)
GLOBAL
);
-- Add Keys and Foreign Key Constraint to the
CALL table
-- which can be LOCAL
CREATE INDEX idx_call_created_date ON call(created_date) LOCAL;
CREATE INDEX idx_call_lookup_id ON call(lookup_id) LOCAL;
ALTER TABLE call ADD (
CONSTRAINT fk_call_lookup_id
FOREIGN KEY (lookup_id)
REFERENCES lookup(id)
);
-- With this destination table in place we can
start the conversion.
-- We now switch the segments associated with the source table and the
-- partition in the destination table using EXCHANGE PARTITION
SET TIMING ON;
ALTER TABLE call
EXCHANGE PARTITION call_temp_created_date
WITH TABLE call_temp
WITHOUT VALIDATION
UPDATE GLOBAL INDEXES;
Elapsed:
00:00:27.19
This is significantly slower than in the
previous example!
The UPDATE GLOBAL INDEXES is needed
because without it that would leave the global indexes associated with
the partition in an UNUSABLE state. If the UPDATE
GLOBAL INDEXES clause is added, the performance is reduced
since the index rebuild is part of the issued DDL. The index updates
are logged and it should only be used when the number of rows is low
and data must stay available. For larger numbers of rows index
rebuilds are more efficient and allow index reorganization.