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.