Legal | Privacy
Using the ETL Infrastructure of Oracle9i
 
 

Using the ETL Infrastructure of Oracle9i

Module Objectives

Purpose

In this module, you will learn how to use the Extraction, Transformation, and Loading (ETL) infrastructure of Oracle9i.

Objectives

After completing this module, you should be able to:

Perform a multi-table Insert
Compare the new Upsert functionality, implemented as SQL command MERGE, with its manual implementation prior to Oracle9i
Create and use table functions
Use synchronous Change Data Capture to track - and consume - incremental data changes
Combine components of Oracle9i’s ETL infrastructure into an efficient, parallel, and seamless transformation process without staging
Propagate information for a data mart by leveraging the new LIST partitioning from Oracle9i, new RANGE_LIST partitioning from Oracle9i Release 2, the transportable tablespace mechanism, and the new RESUMABLE statement capabilities

Prerequisites

Before starting this module, you should have:

Completed the Preinstallation module

Completed the Install Oracle9i Database module

Completed the Postinstallation module

Completed the Review the Sample Schema module
Completed the Setup Data Warehousing lesson

Downloaded etl2.zip and unzipped it into your working directory

Reference Material

The following is a list of useful reference materials if you want additional information about the topics in this module:

Documentation: Data Warehousing Guide


Overview

What Happens During the ETL Process?

What happens during the ETL process? During Extraction, the desired data has to be identified and extracted from many different sources, including database systems and applications. Very often, it’s not possible to identify the specific subset of interest, therefore more data than necessary has to be extracted, and the identification of the relevant data will be done at a later point in time. Depending on the source system’s capabilities (e.g., OS resources), some transformations may take place during this extraction process. The size of the extracted data varies from hundreds of kilobytes to hundreds of gigabytes, depending on the source system and the business situation. The same is true for the time delta between two (logically) identical extractions: the time span may vary between days/hours and minutes to near real-time. Web server log files for example can easily become hundreds of megabytes in a very short period of time.

After extracting data, it has to be physically transported to the target system or an intermediate system for further processing. Depending on the chosen way of transportation, some transformations can be done during this process, too. For example, a SQL statement, which directly accesses a remote target through a gateway, can concatenate two columns as part of the SELECT.

After extracting and transporting the data, the most challenging (and time consuming) part of ETL follows: Transformation and Loading into the target system.

This may include:

Application of complex filters.
Data has to be validated against information already existing in target database tables.
Data extracted without the knowledge of new versus changed information has to be checked against the target objects to determine whether it must be updated or inserted.
The same data has to be inserted several times as detail level and aggregated information.

This should be done as quickly as possible in a scaleable manner and must not affect the existing target with respect to concurrent access for information retrieval.

Oracle9i offers a wide variety of new capabilities to address all the issues and tasks relevant in an ETL scenario. It is important to understand that the database offers an infrastructure rather than trying to address a ‘one-size- fits-all’ solution. The underlying database has to enable the most appropriate ETL process flow for a specific customer need and not to dictate or constrain it from a technical perspective.

Become familiar with the new Multi-Table Insert

MyCompany gets some nonrelational data structures from one of its partner companies, which sells our products for a special advertisement campaign. The data structure is a denormalized, nonrelational record structure from a proprietary mainframe systems consisting of one record per customer and product per week.

Those data structures have to be inserted into our data warehouse. Since we’re storing our sales record data per customer and product per day, we have to transform the incoming data.

As part of the transformation, the nonrelational denormalized data structure must be transformed from one record per week into seven records, each consisting the information of a business day (first example business transformation).

In addition, our data warehouse keeps track of all new customers with a credit_limit above a certain limit. Those customers should be tracked in addition separately

We’re implementing those business transformations by leveraging Oracle 9i’s new multi-table insert capabilities.

1. Ensure that the appropriate partition is existent
2. Insert the appropriate Promotion record in the dimension table PROMOTIONS to avoid constraint violations
3. Use Multi-table insert for the first example business transformation
- Pivoting of data and insertion into the same table
4. Use Multi-table insert for the second example business transformation
- Conditional insertion of the same data into multiple tables with different structures

1. ENSURE THAT THE APPROPRIATE PARTITION IS EXISTENT

Unless you finished the previous module, Using High-Speed Data Load and Rolling Window Operations, you will need to create a new, empty partition for your SALES fact table. To do this, you perform the following steps:

1.

Execute the following query to see if the partition sales_q1_2001 already exists:

@select_partition.sql

SET PAGESIZE 999
COLUMN partition_name FORMAT A15
COLUMN high_value FORMAT A60

SELECT   partition_name, high_value 
FROM     user_tab_partitions 
WHERE    table_name= 'SALES' 
ORDER BY partition_position;

2.

If the partition sales_q1_2001 does not exist, execute the following SQL, logged in as the database user SH:

@create_partition_for_sales2.sql

Rem *****
Rem create ADDITIONAL PARTITION on sales
Rem *****
ALTER TABLE sales ADD PARTITION sales_q1_2001 VALUES LESS THAN (TO_DATE('01-APR-2001','DD-MON-YYYY'));
Rem *****
Rem what is in the partition now?
Rem empty
Rem *****
SELECT COUNT(*) FROM sales PARTITION (sales_q1_2001);

2. Insert the Appropriate Promotion Information

You have to insert a new record into the PROMOTIONS table to avoid a constraint violation during the multi-table insert. To do this, you perform the following steps:

1.

From a SQL*Plus session logged on to the SH schema, issue the following command:

@insert_into_promo.sql

INSERT INTO promotions VALUES
   (501,'special partner ','ad magazine','magazine', 10243,
TO_DATE('01-JAN-2001','dd-mon-yyyy'),
TO_DATE('01-APR-2001','dd-mon-yyyy'), 'Promotion total'); COMMIT;

2.

Control the validity of the PROMOTIONS dimension by executing the following command:

@validate_dimension.sql 
   
EXECUTE dbms_olap.validate_dimension('promotions_dim',
                                     'sh',false,true) 

SELECT COUNT(*) 
FROM   mview$_exceptions; 

The database object dimension helps to organize and group dimensional information into hierarchies. This represents natural 1:n relationships between columns or column groups (the levels of a hierarchy) that cannot be represented with constraint conditions. Going up a level in the hierarchy is called rolling up the data and going down a level in the hierarchy is called drilling down the data.

The information of a dimension object is declarative only and not enforced by the database. If the relationships described by the dimensions are incorrect, incorrect results can occur. Therefore, you should verify the relationships specified by CREATE DIMENSION using the DBMS_OLAP.VALIDATE_DIMENSION procedure periodically.

Please see Using Basic Database Functionality for Data Warehousing for more information about Oracle’s database object DIMENSION and its usage for Query Rewrite.


3. Use the Multi-Table Insert for Pivoting

Before issuing the multi-table insert, you want to investigate plans for this multi-table insert and its implementation prior to Oracle9i. To do this, you perform the following steps:

1.

Show the execution plan for the new multi-table insert. From a SQL*Plus session logged on to the SH schema, execute the following SQL script:

@explain_mti_new.sql

DELETE FROM PLAN_TABLE;

EXPLAIN PLAN FOR
   INSERT ALL
   INTO sales
      VALUES(product_id, customer_id,weekly_start_date,'P',
             501,q_sun,sales_sun)
   INTO sales
      VALUES(product_id, customer_id,weekly_start_date+1,'P',
             501,q_mon,sales_mon)
   INTO sales
      VALUES(product_id, customer_id,weekly_start_date+2,'P',
             501,q_tue,sales_tue)
   INTO sales
      VALUES(product_id, customer_id,weekly_start_date+3,'P',
             501,q_wed,sales_wed)
   INTO sales
       VALUES(product_id, customer_id,weekly_start_date+4,'P',
              501,q_thu,sales_thu)
   INTO sales
      VALUES(product_id, customer_id,weekly_start_date+5,'P',
             501,q_fri,sales_fri)
   INTO sales
      VALUES(product_id, customer_id,weekly_start_date+6,'P',
             501,q_sat,sales_sat)
   SELECT * 
   FROM   sales_input_table;
   
set linesize 120
set pagesize 999
@?/rdbms/admin/utlxpls

Note: The input source table is scanned only once! The complexity of the denormalization is handled within the several INSERT INTO branches, thus avoiding multiple scans.

2.

Now show the execution plan for the multi-table insert based on a UNION ALL set operation. From a SQL*Plus session logged on to the SH schema, execute the following SQL script:

@explain_mti_old.sql

TRUNCATE TABLE PLAN_TABLE;
EXPLAIN PLAN FOR
   INSERT INTO sales
         (prod_id, cust_id, time_id, channel_id,promo_id,amount_sold,
          quantity_sold)
      SELECT product_id, customer_id,weekly_start_date,'P',
             501,sales_sun,q_sun
      FROM   sales_input_table
      UNION ALL
      SELECT product_id, customer_id,weekly_start_date+1,'P',
             501,sales_mon,q_mon
      FROM   sales_input_table
      UNION ALL
      SELECT product_id, customer_id,weekly_start_date+2,'P',
             501,sales_tue,q_tue
      FROM   sales_input_table
      UNION ALL
      SELECT product_id, customer_id,weekly_start_date+3,'P',
             501,sales_wed,q_wed
      FROM   sales_input_table
      UNION ALL
      SELECT product_id, customer_id,weekly_start_date+4,'P',
             501,sales_thu,q_thu
      FROM   sales_input_table
      UNION ALL
      SELECT product_id, customer_id,weekly_start_date+5,'P',
             501,sales_fri,q_fri
      FROM   sales_input_table
      UNION ALL
      SELECT product_id, customer_id,weekly_start_date+6,'P',
             501,sales_sat,q_sat
      FROM   sales_input_table;

set linesize 120
set pagesize 999
@?/rdbms/admin/utlxpls

COMMIT;

Note: The input source table is scanned seven times! The complexity of the denormalization is handled within the several SELECT operations.

With an increasing number of input records, the superiority and the performance improvement of the new multi-table insert statement - by reducing the statement to only one SCAN - will become more and more obvious.

3.

Now, you are ready to execute the following SQL script to perform the new multi-table insert:

@do_mti.sql

INSERT ALL
   INTO sales VALUES
      (product_id, customer_id,weekly_start_date,'P',
       501,q_sun,sales_sun)
   INTO sales VALUES
      (product_id, customer_id,weekly_start_date+1,'P',
       501, q_mon,sales_mon)
   INTO sales VALUES
      (product_id, customer_id,weekly_start_date+2,'P',
       501, q_tue,sales_tue)
   INTO sales VALUES
      (product_id, customer_id,weekly_start_date+3,'P',
       501, q_wed,sales_wed)
   INTO sales VALUES
      (product_id, customer_id,weekly_start_date+4,'P',
       501, q_thu,sales_thu)
   INTO sales VALUES
      (product_id, customer_id,weekly_start_date+5,'P',
       501, q_fri,sales_fri)
   INTO sales VALUES
      (product_id, customer_id,weekly_start_date+6,'P',
       501, q_sat,sales_sat)
   SELECT * 
   FROM   sales_input_table;

COMMIT;


4. Using the Multi-Table Insert for Conditional Insertion

In this multi-table insert, you will insert data into several tables with different table structures based on some conditions. To do this, you perform the following steps:

1.

Create an intermediate table consisting ‘new’ information. From a SQL*Plus session logged on to the SH schema, execute the following SQL script which contains the SQL statements from steps 1-4:

@mti2_prepare.sql

Rem create intermediate table with some records

CREATE TABLE customers_new AS
   SELECT * 
   FROM   customers
   WHERE cust_id BETWEEN 2000 AND 20000;

2.

Disable constraints on SALES table; necessary for 3.

ALTER TABLE sales DISABLE CONSTRAINT sales_customer_fk;
3.

Delete some data from the PRODUCTS table

DELETE FROM customers WHERE cust_id BETWEEN 2000 AND 20000;
4.

Create an empty table for our special promotion information

CREATE TABLE customers_special AS
   SELECT cust_id, cust_credit_limit 
   FROM   customers 
   WHERE rownum > 1;
5.

Issue the multi-table insert into several tables with different table structures. From a SQL*Plus session logged on to the SH schema, execute the following SQL script:

@do_mti2.sql

INSERT FIRST WHEN cust_credit_limit >= 4500 THEN
   INTO customers
   INTO customers_special VALUES (cust_id, cust_credit_limit)
   ELSE
   INTO customers
   SELECT * FROM customers_new;

6.

Now, you can see what was inserted. Execute the following SQL script:

@control_mti2.sql

SELECT COUNT(*) FROM customers;
SELECT COUNT(*) FROM customers_special;
SELECT MIN(cust_credit_limit) FROM customers_special;

As expected, the second table, CUSTOMERS_SPECIAL, consists only of customer data with a cust_credit_limit greater than or equal to 4500.

7.

Before continuing you will want to reset your environment by executing the following SQL script:

@reset_mti2.sql

ALTER TABLE sales 
   MODIFY CONSTRAINT sales_customer_fk 
   RELY ENABLE NOVALIDATE;

DROP TABLE customers_special;
DROP TABLE customers_new;

COMMIT;


Using the New Upsert Functionality, SQL MERGE Keyword Overview

MyCompany has to update its product information in the data warehouse periodically with data from its products database. Unfortunately, we’re getting product delta information without the possibility to distinguish between new and updated information, so we have to figure this out on the data warehouse site.

To use the new SQL MERGE command to either update or insert the data conditionally, you perform the following steps:

1.

Create an external table (and directories) for the external products information.

2.

Perform an Upsert, using the new SQL MERGE command.

3.

Show the execution plan of the new MERGE command.

4.

Perform an Upsert using two separate SQL commands (prior to Oracle9i functionality).


1. Create an External Table (and Directories) for the New Product Information

Before you can create the external table, you will need to create a directory object in the database that will point to the directory on the file system where the data files will reside. Optionally, you can separate the location for the log, bad and discard files from the location of the data files, as shown in the following example.

1.

To create the directory, perform the following. If you have already completed High-Speed Data Load and Rolling Window Operations the directories will already exist and you can skip this step. From a SQL*Plus session logged on to the SH schema, execute the following SQL script:

@create_directory.sql

DROP DIRECTORY data_dir;
DROP DIRECTORY log_dir;

Rem *****
Rem CREATE DIRECTORIES
Rem note that security is controlled
Rem (a) with privileges on directory (read/write)
Rem (b) with privileges on external table
Rem *****

CREATE DIRECTORY data_dir AS 'd:\wkdir';
CREATE DIRECTORY log_dir AS 'd:\wkdir';
2.

When creating an external table, you are defining two parts of information:

1. The metadata information for the table representation inside the database
2. The access parameter definition of how to extract the data from the external file

After creation of this meta information, the external data can be accessed from within the database, without the necessity of an initial load.

To create the external table, from a SQL*Plus session logged on to the SH schema, execute the following SQL script:

@create_external_table2.sql

Rem *****
Rem preparation for upsert
Rem file prodDelta.dat, 1.2 MB must be available
Rem reuse of directory STAGE_DIR
Rem access via external table
Rem *****

DROP TABLE products_delta;

CREATE TABLE products_delta
(
  PROD_ID NUMBER(6), 
  PROD_NAME VARCHAR2(50),
  PROD_DESC VARCHAR2(4000),
  PROD_SUBCATEGORY VARCHAR2(50),
  PROD_SUBCAT_DESC VARCHAR2(2000),
  PROD_CATEGORY VARCHAR2(50),
  PROD_CAT_DESC VARCHAR2(2000),
  PROD_WEIGHT_CLASS NUMBER(2),
  PROD_UNIT_OF_MEASURE VARCHAR2(20),
  PROD_PACK_SIZE VARCHAR2(30),
  SUPPLIER_ID NUMBER(6),
  PROD_STATUS VARCHAR2(20),
  PROD_LIST_PRICE NUMBER(8,2),
  PROD_MIN_PRICE NUMBER(8,2)
)
ORGANIZATION external
(
  TYPE oracle_loader
  DEFAULT DIRECTORY data_dir
  ACCESS PARAMETERS
  (
    RECORDS DELIMITED BY NEWLINE CHARACTERSET US7ASCII
    BADFILE log_dir:'prod_delta.bad_xt'
    LOGFILE log_dir:'prod_delta.log_xt'
    FIELDS TERMINATED BY "|" LDRTRIM
  )
  location
  (
    'prodDelta.dat'
  )
)REJECT LIMIT UNLIMITED NOPARALLEL;


2. Performing an Upsert by Using the New SQL MERGE Command

To perform an upsert using the new SQL MERGE command, you use the following steps:

1.

From a SQL*Plus session logged on to the SH schema, execute the following SQL script:

@do_merge_new.sql

Rem *****
Rem MERGE
Rem new and modified product information has arrived
Rem use of external table again
Rem - seemlessly within the MERGE command !!!
Rem *****

MERGE INTO products t
USING products_delta s
ON ( t.prod_id=s.prod_id )
WHEN MATCHED THEN
UPDATE SET t.prod_list_price=s.prod_list_price, 
           t.prod_min_price=s.prod_min_price
WHEN NOT MATCHED THEN
INSERT
(prod_id, prod_name, prod_desc, prod_subcategory, 
prod_subcat_desc, prod_category, prod_cat_desc, 
prod_status, prod_list_price, prod_min_price)
VALUES
(s.prod_id, s.prod_name, s.prod_desc, s.prod_subcategory,
 s.prod_subcat_desc, s.prod_category, s.prod_cat_desc, 
 s.prod_status, s.prod_list_price, s.prod_min_price);

ROLLBACK;

Note: You will ROLLBACK so that you can reissue the same Upsert with two SQL statements in a subsequent operation.


3. Show the Execution Plan of the New MERGE Command

To look at the execution plan of the MERGE statement, you perform the following steps:

1.

From a SQL*Plus session logged on to the SH schema, run explain_merge_new.sql, or copy the following SQL statement into your SQL*Plus session:

@explain_merge_new.sql

TRUNCATE TABLE plan_table;

EXPLAIN PLAN FOR
   MERGE INTO products t
   USING products_delta s
   ON ( t.prod_id=s.prod_id )
   WHEN MATCHED THEN
      UPDATE SET t.prod_list_price=s.prod_list_price,
                 t.prod_min_price=s.prod_min_price
   WHEN NOT MATCHED THEN
      INSERT (prod_id, prod_name, prod_desc,prod_subcategory,
              prod_subcat_desc, prod_category, prod_cat_desc,
              prod_weight_class, prod_unit_of_measure, 
              prod_pack_size, supplier_id, prod_status, 
              prod_list_price, prod_min_price)
      VALUES (s.prod_id, s.prod_name, s.prod_desc, 
              s.prod_subcategory, s.prod_subcat_desc,
              s.prod_category, s.prod_cat_desc, 
              s.prod_weight_class, s.prod_unit_of_measure,
              s.prod_pack_size, s.supplier_id, s.prod_status,
              s.prod_list_price, s.prod_min_price);

set linesize 120
select * 
from   table(dbms_xplan.display);


4. Perform an Upsert by Using Two Separate SQL Commands (Before Oracle9i)

Before Oracle9i, such Upsert functionality could be implemented as a combination of two SQL statements (insert plus update) or in a procedural manner. To demonstrate how this was done, perform the following steps:

1.

To leverage an updateable join view, a primary or unique key must exist on the join column. Therefore you cannot use the external table directly. As a result, you need to create an intermediate table in the database with the appropriate unique key constraint. The script do_merge_816.sql contains the SQL for the next three steps.

CREATE TABLE prod_delta NOLOGGING AS SELECT * FROM products_delta;

CREATE UNIQUE INDEX p_uk ON prod_delta(prod_id);
2.

Issue the Update operation based on an updateable join view. This functionality was introduced in Oracle 8.1.6. From a SQL*Plus session logged on to the SH schema, execute the following SQL script:

UPDATE  
   ( SELECT s.prod_id, s.prod_list_price sprod_list_price, 
            s.prod_min_price sprod_min_price, t.prod_id,
            t.prod_list_price tprod_list_price, 
            t.prod_min_price tprod_min_price 
     FROM   products t, prod_delta s  
     WHERE  s.prod_id=t.prod_id) JV 
     SET    t.prod_list_price = s.prod_list_price, 
            t.prod_min_price = s.prod_min_price;
3.

Issue the second part of the MERGE - the Insert statement:

INSERT INTO products  
   ( prod_id, prod_name, prod_desc, prod_subcategory, 
     prod_subcat_desc, prod_category, prod_cat_desc, 
     prod_status, prod_list_price, prod_min_price) 
   SELECT prod_id, prod_name, prod_desc, prod_subcategory, 
          prod_subcat_desc, prod_category, prod_cat_desc, 
          prod_status, prod_list_price, prod_min_price 
   FROM   prod_delta s   
   WHERE NOT EXISTS    
            ( SELECT 1     
              FROM   products t     
              WHERE  t.prod_id = s.prod_id);  

ROLLBACK;

We have to issue two separate SQL statements to accomplish the same functionality than the new MERGE command. To leverage the updateable join view functionality of 8.1.6, we also had to copy the external table into a real database table and to create a unique index on the join column. In summary, more operations, more space requirements, and more processing time.


Table Function Overview

In the ETL process, the data extracted from a source system passes through a sequence of transformations before it is loaded into a data warehouse. Complex transformations are implemented in a procedural manner, either outside the database or inside the database in PL/SQL. When the results of a transformation become too large to fit into memory, they must be staged by materializing them either into database tables or flat files. This data is then read and processed as input to the next transformation in the sequence. Oracle9i's table functions provide the support for pipelined and parallel execution of such transformations implemented in PL/SQL, C or Java.

A table function is defined as a function, which can produce a set of rows as output; additionally, Oracle9i's table functions can take a set of rows as input. These sets of rows are processed iteratively in subsets, thus enabling a pipelined mechanism to stream these subset results from one transformation to the next before the first operation has finished. Furthermore, table functions can be processed transparently in parallel, which is equivalent to SQL operations such as a table scan or a sort. Staging tables are no longer necessary.

Examining Basic Implementation Issues for Table Functions

Because of the powerful and multifaceted usage of table functions as part of complex transformations, you will examine basic implementation issues for table functions only. To do this, you perform the following steps:

1.

Set up the basic objects for the table function.

2.

Perform nonpipelined table function, returning an array of records.

3.

Perform pipelined table function.

4.

Perform transparent parallel execution of table functions.

5. Perform table function with autonomous DML.
6. Perform seamless streaming through several table functions.

1. Setting Up the Basic Objects for the Table Function

A table function can produce a set of rows as output and can take a set of rows as input. Therefore, it is imperative that you have to define a record type and a collection of records to define the input and output; those types can either be strongly typed, that is, well-defined prior to their execution, or weakly typed, which means without a fixed record format before run time. You will examine both variants.

To set up the table function objects, you perform the following steps:

1.

Define the object (record) type for the examples. From a SQL*Plus session logged on to the SH schema, execute the following SQL script:

@setup_tf_record.sql

Rem object_types
CREATE TYPE product_t AS OBJECT (
    prod_id              NUMBER(6)
  , prod_name            VARCHAR2(50)
  , prod_desc            VARCHAR2(4000)
  , prod_subcategory     VARCHAR2(50)
  , prod_subcat_desc     VARCHAR2(2000)
  , prod_category        VARCHAR2(50)
  , prod_cat_desc        VARCHAR2(2000)
  , prod_weight_class    NUMBER(2)
  , prod_unit_of_measure VARCHAR2(20)
  , prod_pack_size       VARCHAR2(30)
  , supplier_id          NUMBER(6)
  , prod_status          VARCHAR2(20)
  , prod_list_price      NUMBER(8,2)
  , prod_min_price       NUMBER(8,2)
);
/

This is the basic object record type you will use in our examples.

2.

Define the object (collection) type for the examples. From a SQL*Plus session logged on to the SH schema, execute the following SQL script:

@setup_tf_collection.sql

CREATE TYPE product_t_table AS TABLE OF product_t;
/

This type represents the structure of the set of records, which will be delivered back by your table functions.

3.

Define a package for the REF CURSOR types. From a SQL*Plus session logged on to the SH schema, execute the following SQL script:

@setup_tf_package.sql

Rem package of all cursor types
Rem handle the input cursor type and the output cursor collection

CREATE OR REPLACE PACKAGE cursor_PKG as
 TYPE product_t_rec IS RECORD (
     prod_id              NUMBER(6)
   , prod_name            VARCHAR2(50)
   , prod_desc            VARCHAR2(4000)
   , prod_subcategory     VARCHAR2(50)
   , prod_subcat_desc     VARCHAR2(2000)
   , prod_category        VARCHAR2(50)
   , prod_cat_desc        VARCHAR2(2000)
   , prod_weight_class    NUMBER(2)
   , prod_unit_of_measure VARCHAR2(20)
   , prod_pack_size       VARCHAR2(30)
   , supplier_id          NUMBER(6)
   , prod_status          VARCHAR2(20)
   , prod_list_price      NUMBER(8,2)
   , prod_min_price       NUMBER(8,2));
 TYPE product_t_rectab IS TABLE OF product_t_rec;
 TYPE strong_refcur_t IS REF CURSOR RETURN product_t_rec;
 TYPE refcur_t IS REF CURSOR;
END;
/

4.

Create a log table for the table function example with autonomous DML. From a SQL*Plus session logged on to the SH schema, execute the following SQL script:

@setup_tf_logtab.sql

CREATE TABLE obsolete_products_errors 
  (prod_id NUMBER, msg VARCHAR2(2000));

This table will be used for the table function example which fans out some data as part of its execution.

Table functions return a set of records to the subsequent operation. The result set can be either delivered en-block, the default PL/SQL behavior, or pipelined - increments of the result set will be streamed to the subsequent operation as soon as they are produced.

For table function implementations in PL/SQL, the PL/SQL engine controls the size of the incrementally returned array. For interface implementations of table functions, in C or Java, it is up to the programmer to define the incremental return set.

See the Data Cartridge Developer’s Guide for details on interface implementations.


2. Performing Nonpipelined Table Function, Returning an Array of Records

The following example represents a simple table function, returning its result set nonpipelined as complete array. The table function simply filters out all records of product category 'Boys' and returns all other records. Note that the input REF CURSOR is weakly typed, meaning it could be any valid SQL statement. It is then up to the table function code to handle its input, probably based on other input variables.

To perform the nonpipelined table function, you perform the following steps:

1.

From a SQL*Plus session logged on to the SH schema, run create_tf_1.sql, or copy the following SQL statements into your SQL*Plus session:

@create_tf_1.sql

CREATE OR REPLACE FUNCTION obsolete_products(cur cursor_pkg.refcur_t)
   RETURN product_t_table 
   IS
      prod_id              NUMBER(6);
      prod_name            VARCHAR2(50);
      prod_desc            VARCHAR2(4000);
      prod_subcategory     VARCHAR2(50);
      prod_subcat_desc     VARCHAR2(2000);
      prod_category        VARCHAR2(50);
      prod_cat_desc        VARCHAR2(2000);
      prod_weight_class    NUMBER(2);
      prod_unit_of_measure VARCHAR2(20);
      prod_pack_size       VARCHAR2(30);
      supplier_id          NUMBER(6);
      prod_status          VARCHAR2(20);
      prod_list_price      NUMBER(8,2);
      prod_min_price       NUMBER(8,2);
   sales NUMBER:=0;
   objset product_t_table := product_t_table();
   i NUMBER := 0;
BEGIN
   LOOP
      FETCH cur INTO prod_id, prod_name, prod_desc, 
                     prod_subcategory, prod_subcat_desc, 
                     prod_category, prod_cat_desc,
                     prod_weight_class, prod_unit_of_measure,
                     prod_pack_size, supplier_id, prod_status,
                     prod_list_price, prod_min_price;
      EXIT WHEN cur%NOTFOUND; -- exit when last row is fetched
      -- Category BOYS is not meant to be obsolete
      -- and will be suppressed
      IF prod_status='obsolete' AND prod_category != 'Boys' THEN
         -- append to collection
         i:=i+1;
         objset.extend;
         objset(i):=product_t(prod_id, prod_name, prod_desc, 
                              prod_subcategory, prod_subcat_desc, 
                              prod_category, prod_cat_desc,
                              prod_weight_class,
                              prod_unit_of_measure, prod_pack_size, 
                              supplier_id, prod_status,
                              prod_list_price, prod_min_price);
      END IF;
   END LOOP;
   CLOSE cur;
   RETURN objset;
END;
/

2.

You cannot transparently SELECT from the defined table function obsolete_products. From a SQL*Plus session logged on to the SH schema, execute the following SQL script:

@use_tf_1.sql

SELECT DISTINCT UPPER(prod_category), prod_status
FROM   TABLE(obsolete_products(
             CURSOR(SELECT prod_id, prod_name, prod_desc,
                           prod_subcategory, prod_subcat_desc,
                           prod_category, prod_cat_desc, 
                           prod_weight_class, prod_unit_of_measure,
                           prod_pack_size, supplier_id,
                           prod_status, prod_list_price, 
                           prod_min_price
                    FROM   products)));

Note that the input SELECT list for the cursor variable must match the definition of the table function, in our case pkg.refcur_t. If you can ensure that the table definition, in our case products, match this input cursor, you can simplify the statement by using the SELECT * FROM TABLE notation.


3. Performing Pipelined Table Functions

The following example represents the same filtering-out than the previous example. Unlike the first one, this table function returns its result set pipelined, which means incrementally. Note that the input REF CURSOR is here strongly typed, meaning it could be only a SQL statement returning a record type of product_type_record.

To perform the pipelined table function, you perform the following steps:

1.

From a SQL*Plus session logged on to the SH schema, execute the following SQL script:

@create_tf_2.sql

Rem   same example, pipelined implementation
Rem   strong ref cursor (input type is defined)
Rem   a table without a strong typed input ref cursor 
Rem   cannot be parallelized

CREATE OR REPLACE FUNCTION 
   obsolete_products_pipe(cur cursor_pkg.strong_refcur_t) 
   RETURN product_t_table
   PIPELINED
   PARALLEL_ENABLE (PARTITION cur BY ANY) IS
      prod_id              NUMBER(6);      
      prod_name            VARCHAR2(50);   
      prod_desc            VARCHAR2(4000); 
      prod_subcategory     VARCHAR2(50);  
      prod_subcat_desc     VARCHAR2(2000);  
      prod_category        VARCHAR2(50);
      prod_cat_desc        VARCHAR2(2000);  
      prod_weight_class    NUMBER(2);
      prod_unit_of_measure VARCHAR2(20);
      prod_pack_size       VARCHAR2(30);
      supplier_id          NUMBER(6);
      prod_status          VARCHAR2(20);
      prod_list_price      NUMBER(8,2); 
      prod_min_price       NUMBER(8,2);
   sales NUMBER:=0;
BEGIN
   LOOP
      -- Fetch from cursor variable
      FETCH cur INTO prod_id, prod_name, prod_desc, prod_subcategory, 
                     prod_subcat_desc, prod_category, prod_cat_desc,
                     prod_weight_class, prod_unit_of_measure,
                     prod_pack_size, supplier_id, prod_status,
                     prod_list_price, prod_min_price;
      EXIT WHEN cur%NOTFOUND; -- exit when last row is fetched
      IF prod_status='obsolete' AND prod_category !='Boys' THEN
         PIPE ROW (product_t(prod_id, prod_name, prod_desc, 
                             prod_subcategory, prod_subcat_desc,
                             prod_category, prod_cat_desc, 
                             prod_weight_class, 
                             prod_unit_of_measure, prod_pack_size, 
                             supplier_id, prod_status, 
                             prod_list_price, prod_min_price));
      END IF;
   END LOOP;
   CLOSE cur;
   RETURN;
END;
/

2.

You can now transparently SELECT from the defined table function obsolete_products. From a SQL*Plus session logged on to the SH schema, execute the following SQL script:

@use_tf_2.sql

SELECT DISTINCT prod_category,
       DECODE(prod_status,'obsolete','NO LONGER AVAILABLE','N/A')
FROM   TABLE(obsolete_products_pipe(
          CURSOR(SELECT prod_id, prod_name, prod_desc, 
                        prod_subcategory, prod_subcat_desc, 
                        prod_category, prod_cat_desc, 
                        prod_weight_class, prod_unit_of_measure, 
                        prod_pack_size, supplier_id, prod_status,
                        prod_list_price, prod_min_price 
                 FROM   products)));


4. Perform Transparent Parallel Execution of Table Functions

Table functions can be parallelized transparently without any special intervention or setup. The only necessary declaration, which has to be done as part of the table function creation, is to tell the SQL engine if there are any restrictions or rules for parallelizing its execution.

Think of a table function that processes a complex aggregation operation over some product_category attributes. If we want to process this operation in parallel, we have to guarantee that all records with the same product category attributes are processed from the same parallel execution slave, so that our aggregation operation covers all records that belong to the same grouping. This implies that, during parallel processing, a distribution rule must be used for parallel processing, which guarantees the above described correct distribution.

This parallel distribution rule is defined as part of the table function header, for example

PARALLEL_ENABLE (PARTITION cur BY ANY) IS

That’s all a developer has to do to enable parallel execution of a table function.

To perform one of the table functions you previously defined in parallel, you perform the following steps:

1.

From a SQL*Plus session logged on to the SH schema, execute the following SQL script:

@pq_session.sql

SELECT * 
FROM   v$pq_sesstat 
WHERE  statistic in ('Queries Parallelized','Allocation Height');

2.

Now you can use one of the table functions in parallel. You will enforce the parallel execution with a PARALLEL hint on the REF CURSOR. From a SQL*Plus session logged on to the SH schema, execute the following SQL script:

@parallel_tf.sql

SELECT DISTINCT prod_category,
       DECODE(prod_status,'obsolete','NO LONGER AVAILABLE','N/A')
FROM   TABLE(obsolete_products_pipe(
                CURSOR(SELECT /*+ PARALLEL(a,4)*/
                              prod_id, prod_name, prod_desc, 
                              prod_subcategory, prod_subcat_desc,
                              prod_category, prod_cat_desc, 
                              prod_weight_class, 
                              prod_unit_of_measure,
                              prod_pack_size, supplier_id, 
                              prod_status, prod_list_price,
                              prod_min_price 
                       FROM   products a
                      )
                                    )
            );

3.

Rerun the SQL script to check the status of the parallelized queries:

@pq_session.sql

SELECT * 
FROM   v$pq_sesstat 
WHERE  statistic in ('Queries Parallelized','Allocation Height');


5. Perform Table Functions with Autonomous DML

Although a table function is part of a single atomic transaction, it provides the additional capability to fan out data to other tables within the scope of an autonomous transaction. This can be used in a variety of ways, such as for exception or progress logging or to fan out subsets of data to be used by other independent transformations.

The following example logs some "exception" information into the OBSOLETE_PRODUCTS_ERRORS table:

1.

From a SQL*Plus session logged on to the SH schema, execute the following SQL script:

@create_tf_3.sql

CREATE OR REPLACE FUNCTION obsolete_products_dml
   (cur cursor_pkg.strong_refcur_t,prod_cat varchar2 DEFAULT 'Men')
   RETURN product_t_table
   PIPELINED
   PARALLEL_ENABLE (PARTITION cur BY ANY) IS
      PRAGMA AUTONOMOUS_TRANSACTION;
      prod_id              NUMBER(6);
      prod_name            VARCHAR2(50);
      prod_desc            VARCHAR2(4000);
      prod_subcategory     VARCHAR2(50);
      prod_subcat_desc     VARCHAR2(2000);
      prod_category        VARCHAR2(50);
      prod_cat_desc        VARCHAR2(2000);
      prod_weight_class    NUMBER(2);
      prod_unit_of_measure VARCHAR2(20);
      prod_pack_size       VARCHAR2(30);
      supplier_id          NUMBER(6);
      prod_status          VARCHAR2(20);
      prod_list_price      NUMBER(8,2);
      prod_min_price       NUMBER(8,2);
      prod_total           VARCHAR2(13);
   sales NUMBER:=0;
BEGIN
   LOOP
     FETCH cur INTO prod_id, prod_name, prod_desc, prod_subcategory,
                    prod_subcat_desc, prod_category, prod_cat_desc, 
                    prod_weight_class, prod_unit_of_measure, 
                    prod_pack_size, supplier_id, prod_status,
                    prod_list_price, prod_min_price, prod_total;
     EXIT WHEN cur%NOTFOUND; -- exit when last row is fetched
     IF prod_status='obsolete' THEN
        IF prod_category=prod_cat THEN
           INSERT INTO obsolete_products_errors VALUES
           (prod_id, 'correction: category '||UPPER(prod_cat)||'
            still available');
        ELSE
        PIPE ROW (product_t( prod_id, prod_name, prod_desc, 
                             prod_subcategory, prod_subcat_desc,
                             prod_category, prod_cat_desc,
                             prod_weight_class, 
                             prod_unit_of_measure, prod_pack_size,
                             supplier_id, prod_status, 
                             prod_list_price, prod_min_price, 
                             prod_total));
        END IF;
     END IF;
   END LOOP;
   CLOSE cur;
   RETURN;
END;
/

2.

Now you can select from this table function and check the log table. From a SQL*Plus session logged on to the SH schema, execute the following SQL script:

@use_tf_3a.sql

TRUNCATE TABLE obsolete_products_errors;

SELECT DISTINCT prod_category, prod_status 
FROM   TABLE(obsolete_products_dml(
             CURSOR(SELECT prod_id, prod_name, prod_desc, 
                           prod_subcategory, prod_subcat_desc,
                           prod_category, prod_cat_desc, 
                           prod_weight_class, 
                           prod_unit_of_measure, 
                           prod_pack_size, supplier_id,
                           prod_status, prod_list_price, 
                           prod_min_price
                    FROM   products
                   )
                                  )
            );

SELECT DISTINCT msg 
FROM   obsolete_products_errors;

3.

You will want to select from this table function again with a different input argument and check the log table.

@use_tf_3b.sql

TRUNCATE TABLE obsolete_products_errors;

SELECT DISTINCT prod_category, prod_status 
FROM   TABLE(obsolete_products_dml(
             CURSOR(SELECT prod_id, prod_name, prod_desc, 
                           prod_subcategory, prod_subcat_desc,
                           prod_category, prod_cat_desc, 
                           prod_weight_class,
                           prod_unit_of_measure, 
                           prod_pack_size, supplier_id, 
                           prod_status, prod_list_price, 
                           prod_min_price
                    FROM   products
                   ),'Boys')
            );

SELECT DISTINCT msg 
FROM   obsolete_products_errors;

You will see that, dependent on input variable, the table function processes the input rows differently, thus delivering a different result set and insert different information into obsolete_products_error.


6. Perform Seamless Streaming Through Several Table Functions

Besides the transparent usage of table functions within SQL statements and its capability of being processed in parallel, one of the biggest advantages is that table functions can be called from within each other. Furthermore, table functions can be used in any SQL statement and become the input for all kinds of DML statements.

To see how this is done, perform the following steps:

1.

This SQL statement uses two table functions, nested in each other. From a SQL*Plus session logged on to the SH schema, execute the following SQL script:

@use_tf_stream.sql

SELECT DISTINCT prod_category, prod_status
FROM   TABLE(obsolete_products_dml(CURSOR(
             SELECT * 
             FROM   TABLE(obsolete_products_pipe(CURSOR(
                    SELECT prod_id, prod_name, 
                           prod_desc, prod_subcategory,
                           prod_subcat_desc, prod_category, 
                           prod_cat_desc, prod_weight_class, 
                           prod_unit_of_measure, 
                           prod_pack_size, supplier_id, 
                           prod_status, prod_list_price, 
                           prod_min_price
                    FROM  products)))
)));

2.

Now you can use this table function as INPUT for a CREATE TABLE AS SELECT command. From a SQL*Plus session logged on to the SH schema, execute the following SQL script:

@CTAs_tf.sql

CREATE TABLE PIPE_THROUGH AS
SELECT DISTINCT prod_category, prod_status
FROM TABLE(obsolete_products_dml(CURSOR(
SELECT *
FROM TABLE(obsolete_products_pipe(CURSOR(
SELECT prod_id, prod_name, prod_desc,
prod_subcategory, prod_subcat_desc,
prod_category, prod_cat_desc,
prod_weight_class,
prod_unit_of_measure,
prod_pack_size, supplier_id,
prod_status, prod_list_price,
prod_min_price
FROM products)))
)));

3.

Before you move to the next section of this module, you will want to clean up your environment by executing the following script:

@cleanup_tf.sql

DROP TABLE obsolete_products_errors;
DROP TABLE pipe_through;
DROP FUNCTION obsolete_products;
DROP FUNCTION obsolete_products_pipe;
DROP FUNCTION obsolete_products_dml;


USE SYNCHRONOUS CHANGE DATA CAPTURE (CDC) TO CAPTURE AND CONSUME INCREMENTAL SOURCE CHANGES

Oftentimes, data warehousing involves the extraction and transportation of relational data from one or more source databases, into the data warehouse for analysis. Change Data Capture quickly identifies and processes only the data that has changed, not entire tables, and makes the change data available for further use.

Without Change Data Capture, database extraction is a cumbersome process in which you move the entire contents of tables into flat files, and then load the files into the data warehouse. This ad hoc approach is expensive in a number of ways.

Change Data Capture does not depend on intermediate flat files to stage the data outside of the relational database. It captures the change data resulting from INSERT, UPDATE, and DELETE operations made to user tables. The change data is then stored in a database object called a change table, and the change data is made available to applications in a controlled way.

PUBLISH AND SUBSCRIBE MODEL

Most Change Data Capture systems have one publisher that captures and publishes change data for any number of Oracle source tables. There can be multiple subscribers accessing the change data. Change Data Capture provides PL/SQL packages to accomplish the publish and subscribe tasks.

PUBLISHER

The publisher is usually a database administrator (DBA) who is in charge of creating and maintaining schema objects that make up the Change Data Capture system. The publisher performs these tasks:

Determines the relational tables (called source tables) from which the data warehouse application is interested in capturing change data.

Uses the Oracle supplied package, DBMS_LOGMNR_CDC_PUBLISH, to set up the system to capture data from one or more source tables.

Publishes the change data in the form of change tables.

Allows controlled access to subscribers by using the SQL GRANT and REVOKE statements to grant and revoke the SELECT privilege on change tables for users and roles.

SUBSCRIBERS

The subscribers, usually applications, are consumers of the published change data. Subscribers subscribe to one or more sets of columns in source tables. Subscribers perform the following tasks:

Use the Oracle supplied package, DBMS_LOGMNR_CDC_SUBSCRIBE, to subscribe to source tables for controlled access to the published change data for analysis.

Extend the subscription window and create a new subscriber view when the subscriber is ready to receive a set of change data.

Use SELECT statements to retrieve change data from the subscriber views.

Drop the subscriber view and purge the subscription window when finished processing a block of changes.

Drop the subscription when the subscriber no longer needs its change data.

Please consult the ‘Oracle Data Warehousing Guide’ for a more detailed discussion about the Change Data Capture Architecture.

We will now use synchronous CDC to track all the incremental changes for the table PRODUCTS. For demonstration purposes the publisher and subscriber will be the same database user.

1.

We will first create a new intermediate table where we apply all changes. From a SQL*Plus session logged on to the SH schema, run cr_cdc_target.sql, or copy the following SQL statements into your SQL*Plus session:

@cr_cdc_target.sql

CREATE TABLE my_prod_price_change_for_boys
   (prod_id number(6), prod_min_price number(8,2), 
    prod_list_price number(8,2), when date);

2.

If you never used Change Data Capture before, you won’t see any objects related to CDC other than the synchronous change set SYNC_SET and the synchronous change source SYNC_SOURCE. Both of these objects are created as part of the database creation.

@show_cdc_env1.sql

SELECT * 
FROM   change_sources;

Rem see the change tables
Rem shouldn't show anything

SELECT * 
FROM   change_tables;

Rem see the change sets SYNC_SET

SELECT decode(to_char(end_date,'dd-mon-yyyy hh24:mi:ss'),null,
              'No end date set.') end_date,
       decode(to_char(freshness_date,'dd-mon-yyyy hh24:mi:ss'),
              null, 'No freshness date set.') freshness_date
FROM   change_sets 
WHERE  set_name='SYNC_SET';

All changes are stored in Change Tables. Change Tables have a one-to-one dependency to a source table; they consist of several fixed metadata columns and a dynamic set of columns equivalent to the identified source columns of interest.

1.

You create a change table by using the provided package DBMS_LOGMNR_CDC_PUBLISH.

@cr_cdc_ct.sql

EXEC DBMS_LOGMNR_CDC_PUBLISH.CREATE_CHANGE_TABLE 
   (OWNER => 'SH', -
    CHANGE_TABLE_NAME => 'PROD_PRICE_CT', -
    CHANGE_SET_NAME => 'SYNC_SET', -
    SOURCE_SCHEMA => 'SH', -
    SOURCE_TABLE => 'PRODUCTS', -
    COLUMN_TYPE_LIST =>  
       'prod_id number(6), prod_min_price number(8,2), ' ||
       'prod_list_price number(8,2)', -
    CAPTURE_VALUES => 'both', -
    RS_ID => 'y', -
    ROW_ID => 'n', -
    USER_ID => 'n', -
    TIMESTAMP => 'n', -
    OBJECT_ID => 'n', -
    SOURCE_COLMAP => 'y', -
    TARGET_COLMAP => 'y',-
    OPTIONS_STRING => null
   );

This creates a change table called PROD_PRICE_CT and the necessary trigger framework to track all subsequent changes on PRODUCTS. Note that the tracking is done as part of the atomic DML operation against PRODUCTS. Please see the documentation for a discussion of the parameters.

2.

Describe the change table PROD_PRICE_CT:

Desc prod_price_ct

See the static meta data columns as well as the columns PROD_ID, PROD_MIN_PRICE, and PROD_LIST_PRICE (equivalent to source table PRODUCTS).

3.

To see the meta data of the published source table and the change tables in the system, issue the following SQL commands:

@show_cdc_env2.sql

prompt see the published source tables
SELECT * 
FROM   dba_source_tables;

prompt see published source columns
SELECT source_schema_name,source_table_name, column_name
FROM   dba_source_tab_columns;

prompt see the change tables
SELECT * 
FROM   change_tables;

Now anybody who’s interested in the change information can subscribe to a change set for controlled access to the published change data for analysis. Note that the logical entity for a subscription handle is a change set and not a change table. A change set can consist of several change tables and guarantees logical consistency for all of its change tables. After subscribing to a change set, we also have to subscribe to all source table columns of interest.

1.

You must get a unique subscription handle that is used throughout the session and to tell the system what columns you are interested in. You are using the existing change set SYNC_SET. This functionality is provided by the package DBMS_LOGMNR_CDC_SUBSCRIBE.

@subs_cdc_ct.sql

variable subhandle number;
exec DBMS_LOGMNR_CDC_SUBSCRIBE.GET_SUBSCRIPTION_HANDLE ( -
   CHANGE_SET => 'SYNC_SET', -
   DESCRIPTION => 'Change data PRODUCTS for BOYS', -
   SUBSCRIPTION_HANDLE => :subhandle);

PROMPT see the unique subhandle number
PRINT subhandle

EXEC DBMS_LOGMNR_CDC_SUBSCRIBE.SUBSCRIBE ( -
   SUBSCRIPTION_HANDLE => :subhandle, -
   SOURCE_SCHEMA => 'sh', -
   SOURCE_TABLE => 'products', -
   COLUMN_LIST => 'prod_id, prod_min_price, prod_list_price');

2.

You can query the meta information about your subscription.

@show_cdc_env3.sql

SELECT handle, description,
       decode(status,'N','Not activated.','Activated.') status,
       earliest_scn, latest_scn,
       decode(last_purged,null,'Never purged.',
       to_char(last_purged,'dd-mon-yyyy hh24:mi:ss')) last_purged,
       decode(last_extended, null,'Never extended.',
       to_char(last_extended,'dd-mon-yyyy hh24:mi:ss')) last_extended
FROM   user_subscriptions;

SELECT * 
FROM   user_subscribed_tables;

SELECT source_table_name, column_name 
FROM   dba_subscribed_columns;

So far you accomplished the necessary steps to set up a CDC environment:

On the publisher site you prepared a source system for CDC.

On the subscriber site, you identified and subscribed to all source tables (and columns) of interest. Now you are ready to use CDC.

The first step after setting up the publish and subscribe framework is to activate a subscription and to extend the subscription window to see all changes that have taken place since either the last subscription window extension or the activation of a subscription.

1.

Activate and extend a subscription window. You will also have a look into the meta data.

@sub_cdc1.sql

rem now activate the subscription since we are ready to receive 
rem change data the ACTIVATE_SUBSCRIPTION procedure sets 
rem subscription window to empty initially
rem At this point, no additional source tables can be added to the 
rem subscription

EXEC DBMS_LOGMNR_CDC_SUBSCRIBE.ACTIVATE_SUBSCRIPTION -
   (SUBSCRIPTION_HANDLE => :subhandle);

rem now recheck the subscriptions and see that it is active
rem the view is still not created ...

SELECT handle, description,
       decode(status,'N','Not activated.','Activated.') status,
       earliest_scn, latest_scn,
       decode(last_purged,null,'Never purged.',
       to_char(last_purged,'dd-mon-yyyy hh24:mi:ss')) last_purged,
       decode(last_extended, null,'Never extended.',
       to_char(last_extended,'dd-mon-yyyy hh24:mi:ss')) last_extended
from   user_subscriptions;

2.

Any changes we are applying now to the source table PRODUCTS will be reflected in the change table. The changes are transparently maintained with triggers on the source table.

@dml_cdc1.sql

Rem now do some changes

UPDATE products
   SET prod_list_price=prod_list_price*1.1
   WHERE prod_name='Kids Cargo Shorts' and prod_min_price > 11.20;

COMMIT;

Rem you will see entries in the change table
Rem note that we have entries for the old and the new values

SELECT * 
FROM   prod_price_ct;

If you haven’t modified the standard SALES HISTORY data set, you will select 20 rows. Note that we have two records for each source row, one representing the old, and the other the new value set for the subscribed columns.

Again, please never use the change table itself for identifying the changes on a source table. Use the subscriber view that will be created for you. This is the one and only supported way to guarantee that every change is delivered only once to a subscribing application.

3.

You are now preparing for consuming the changes. Create a subscriber view by using the DBMS_LOGMNR_CDC_SUBSCRIBE package.You will also create a more meaningful name for it.

@sub_view1.sql

rem Set upper boundary (high-water mark) for the subscription 
rem window. At this point, the subscriber has created a new 
rem window that begins where the previous window ends.
rem The new window contains any data that was added to the change 
rem table.

EXEC DBMS_LOGMNR_CDC_SUBSCRIBE.EXTEND_WINDOW -
   (SUBSCRIPTION_HANDLE => :subhandle);

rem To access the new change data, the subscriber must call 
rem the CREATE_SUBSCRIBER_VIEW procedure, and select from 
rem the new subscriber view that is generated by Change 
rem Data Capture.

variable viewname varchar2(4000);
exec DBMS_LOGMNR_CDC_SUBSCRIBE.PREPARE_SUBSCRIBER_VIEW -
   (SUBSCRIPTION_HANDLE => :subhandle, -
    SOURCE_SCHEMA => 'SH', -
    SOURCE_TABLE => 'products', -
    VIEW_NAME => :viewname);

Rem you will see the viewname we saw before ...
PRINT viewname;

REM see what has been created
SELECT object_name,object_type
FROM   user_objects 
WHERE  created > SYSDATE - 5/1440;

Rem let's get a more proper name for it
DECLARE
   old_name varchar2(30);
BEGIN
   SELECT view_name INTO old_name FROM user_subscribed_tables;
   EXECUTE IMMEDIATE 
     'CREATE SYNONYM my_prod_price_change_view FOR ' || old_name;
END;
/

4.

You can now select from this system-generated change view, and you will see only the changes that have happened for your time window.

@sel_change_view1.sql

Rem changes classified for specific product groups

SELECT p1.prod_id, p2.prod_category, p1.prod_min_price,
       p1.prod_list_price, commit_timestamp$
FROM   my_prod_price_change_view p1, products p2
WHERE  p1.prod_id=p2.prod_id
AND    operation$='UN';

PROMPT and especially the BOYS' ones

SELECT p1.* 
FROM   prod_price_ct p1, products p2
WHERE  p1.prod_id=p2.prod_id 
AND    p2.prod_category='Boys' 
AND    operation$='UN';

If you haven’t modified the standard SALES HISTORY data set, you will select 10 rows, 9 rows for product category GIRLS, one for BOYS.

In the example shown above, we’re joining back to the source table PRODUCTS. When you set up a CDC environment, please ensure that such an operation should not be necessary; if possible, a change table should be usable as standalone source for synchronizing your target environment. Joining back to the source table might reduce the data stored on disk, but it creates additional workload on the source site. Furthermore, when asynchronous CDC will be supported in a future release, the change table will no longer be necessarily stored in the source system itself but in an different system (potentially an ODS environment). Join-back cannot be accomplished on a different system than the source system.

5.

We can now consume the changes on our target system.

@consume_cdc1.sql

Prompt ... you can now consume the changes
rem arbitrary example, where we only track changes for BOYS. 
rem This shall demonstrate the flexibility as well as the 
rem responsibility of the client site (consumer) to deal
rem appropriately with the changes

INSERT into my_prod_price_change_for_boys
   SELECT p1.prod_id, p1.prod_min_price, 
          p1.prod_list_price, commit_timestamp$
   FROM   my_prod_price_change_view p1, products p2
   WHERE  p1.prod_id=p2.prod_id
     AND  p2.prod_category='Boys' 
     AND  operation$='UN';

COMMIT;

SELECT prod_id, prod_min_price, prod_list_price,
       to_char(when,'dd-mon-yyyy hh24:mi:ss')
FROM   my_prod_price_change_for_boys;

So far you exercised your first consumption with the synchronous CDC framework. Now investigate how to handle such an environment over time when additional changes will happen and old changes are consumed by all subscribers.

1.

Other DML operations will take place on the source table PRODUCTS

@dml_cdc2.sql

PROMPT other changes will happen

UPDATE products
   SET prod_list_price=prod_list_price*1.1
   WHERE prod_name='Kids Cargo Shorts' 
   AND   prod_min_price < 6.5;

COMMIT;

2.

The synchronous CDC framework tracks these DML operations transparently in the change table.

@show_cdc_ct2.sql

PROMPT ... which will be reflected in the change table
PROMPT REMEMBER THE NUMBER OF ROWS !!!

SELECT count(*) 
FROM   prod_price_ct ;

PROMPT and especially the BOYS' ones

SELECT COUNT(*) 
FROM   prod_price_ct p1, products p2
WHERE  p1.prod_id=p2.prod_id 
AND    p2.prod_category='Boys';

If you haven’t modified the standard SALES HISTORY data set, you will select 48 rows, and 18 rows for product category BOYS.

3.

However, the change view still shows 10 records only, one record for product category BOYS.

@sel_change_view1.sql

Rem changes classified for specific product groups

SELECT p1.prod_id, p2.prod_category, p1.prod_min_price,
       p1.prod_list_price, commit_timestamp$
FROM   my_prod_price_change_view p1, products p2
WHERE  p1.prod_id=p2.prod_id
AND    operation$='UN';

PROMPT and especially the BOYS' ones

SELECT p1.* 
FROM   prod_price_ct p1, products p2
WHERE  p1.prod_id=p2.prod_id 
AND    p2.prod_category='Boys' 
AND    operation$='UN';

We haven’t extended the time window for the change view yet.

4.

To tell the CDC framework that we are done with a set of changes, we have to do the following:

1. Drop the subscriber view

2. Purge the subscription window. No data will be deleted from the change table; it is only reflected in the meta data of the user subscription.

@purge_sub_window1.sql

PROMPT drop the subscriber view as they are finished with it

exec DBMS_LOGMNR_CDC_SUBSCRIBE.drop_subscriber_view -
   (subscription_handle => :subhandle, -
    SOURCE_SCHEMA => 'SH', -
    SOURCE_TABLE => 'PRODUCTS');
PROMPT ... and purge old data from the subscription window
Rem change view must be deleted before you can purge
Rem this will NOT delete any records from the change table. 
Rem It just tells the CDC Rem system that the changed data
Rem used so far is no longer needed

EXEC DBMS_LOGMNR_CDC_SUBSCRIBE.PURGE_WINDOW -
   (SUBSCRIPTION_HANDLE => :subhandle);

PROMPT still the same number of records in the change table
PROMPT REMEMBER THE NUMBER OF ROWS !!!

SELECT COUNT(*) 
FROM   prod_price_ct;

PROMPT recreate an re-query the change view will show you
PROMPT ZERO records, although there are changes you haven't 
PROMPT consumed - you need to extend your window

exec DBMS_LOGMNR_CDC_SUBSCRIBE.PREPARE_SUBSCRIBER_VIEW -
   (SUBSCRIPTION_HANDLE => :subhandle, -
    SOURCE_SCHEMA => 'sh', -
    SOURCE_TABLE => 'products', -
    VIEW_NAME => :viewname);

PROMPT ... change view is empty
SELECT COUNT(*) 
FROM   my_prod_price_change_view p1, products p2
WHERE  p1.prod_id=p2.prod_id 
  AND  p2.prod_category='Boys';

PROMPT drop the subscriber view as they are finished with it

exec DBMS_LOGMNR_CDC_SUBSCRIBE.drop_subscriber_view -
   (subscription_handle => :subhandle, -
    SOURCE_SCHEMA => 'SH', -
    SOURCE_TABLE => 'PRODUCTS');

5.

To get the new changes reflected in the change view, you have to extend the time window for the change view.

@extend_sub_window1.sql
          
PROMPT let's get the new change
Rem 'do it again Sam'
Rem first extend the window you want to see

EXEC DBMS_LOGMNR_CDC_SUBSCRIBE.EXTEND_WINDOW -
   (SUBSCRIPTION_HANDLE => :subhandle);

Rem note that the view name is fix for a subhandle

variable viewname varchar2(4000);
exec DBMS_LOGMNR_CDC_SUBSCRIBE.PREPARE_SUBSCRIBER_VIEW -
   (SUBSCRIPTION_HANDLE => :subhandle, -
    SOURCE_SCHEMA => 'sh', -
    SOURCE_TABLE => 'products', -
    VIEW_NAME => :viewname);

Rem ... now you will see exactly the new changed data since the 
Rem last consumption not more, not less - 16 entries for 8 changed 
Rem records (old and new values)

SELECT COUNT(*) 
FROM   my_prod_price_change_view p1, products p2
WHERE  p1.prod_id=p2.prod_id 
AND    p2.prod_category='Boys';

The new change view shows exactly the changes that were not consumed yet. This is different than the content in the change table.

6.

Now consume the new changes. Since the incremental changes you are interested in are stored in a change table (a ‘normal’ Oracle table), youcan use any language or SQL construct the database supports.

@consume_cdc2.sql

PROMPT the new changes will be used

MERGE INTO my_prod_price_change_for_boys t
  USING (
          SELECT p1.prod_id, p1.prod_min_price, p1.prod_list_price, 
                 commit_timestamp$
          FROM   my_prod_price_change_view p1, products p2
          WHERE  p1.prod_id=p2.prod_id 
          AND    p2.prod_category='Boys' 
          AND    operation$='UN') cv
   ON (cv.prod_id = t.prod_id)
   WHEN MATCHED THEN
      UPDATE SET t.prod_min_price=cv.prod_min_price,
                 t.prod_list_price=cv.prod_list_price
   WHEN NOT MATCHED THEN
      INSERT VALUES (cv.prod_id, cv.prod_min_price, 
                     cv.prod_list_price,
                     commit_timestamp$);

COMMIT;

rem look at them

SELECT prod_id, prod_min_price, prod_list_price,
       to_char(when,'dd-mon-yyyy hh24:mi:ss')
FROM   my_prod_price_change_for_boys;

The Publisher is responsible for maintaining the CDC framework and to guarantee that the change tables are purged regularly as soon as all subscribers have consumed a specific set of change information.

1.

From a SQL*Plus session logged on to the SH schema, run purge_ct.sql, or copy the following SQL statements into your SQL*Plus session:

@purge_ct.sql

PROMPT what to do to avoid overflow of change table ? PURGE IT !
Rem only the rows where we have no potential subscribers will 
Rem be purged !!!

exec DBMS_LOGMNR_CDC_PUBLISH.purge

PROMPT REMEMBER THE NUMBER OF ROWS !!!

SELECT COUNT(*) 
FROM   prod_price_ct;

PROMPT and especially the BOYS' ones
Rem this is exactly twice the number of changes we made with the DML
Rem operation (one row for the old, one for the new values)

SELECT COUNT(*) 
FROM   prod_price_ct p1, products p2
WHERE  p1.prod_id=p2.prod_id 
  AND  p2.prod_category='Boys';

If you haven’t modified the standard SALES HISTORY data set, you will select 36 rows, representing the second DML operation (one row for the old, and one row for the new values). Although we haven’t specified any specific time window for the purge operation of the change table, the CDC framework ensures that only the changes will be purged that are no longer needed by a subscriber.

 

1.

Since you consumed the second set of change already, you can drop our used change view and purge our subscription window. You will also purge the change table.

@purge_sub_window2.sql

PROMPT now drop the view and purge the window again
rem (which means that we consumed all the accessible changes)

exec DBMS_LOGMNR_CDC_SUBSCRIBE.drop_subscriber_view -
   (subscription_handle => :subhandle, -
    SOURCE_SCHEMA => 'sh', -
    SOURCE_TABLE => 'products');

PROMPT ... and purge old data from the subscription window
EXEC DBMS_LOGMNR_CDC_SUBSCRIBE.PURGE_WINDOW -
   (SUBSCRIPTION_HANDLE => :subhandle);

PROMPT purge all change tables again
exec DBMS_LOGMNR_CDC_PUBLISH.purge

PROMPT ... and you will see an empty change table emp_ct
SELECT * 
FROM prod_price_ct;

The change table is now empty. All changes are consumed and could be purged.

Let’s now clean up the CDC environment.

1.

From a SQL*Plus session logged on to the SH schema, run cleanup_cdc.sql, or copy the following SQL statements into your SQL*Plus session:

@cleanup_cdc.sql

exec DBMS_LOGMNR_CDC_SUBSCRIBE.drop_subscription -
   (subscription_handle => :subhandle);

exec DBMS_LOGMNR_CDC_PUBLISH.DROP_CHANGE_TABLE (OWNER => 'sh', -
   CHANGE_TABLE_NAME => 'prod_price_CT', -
   FORCE_FLAG => 'Y');

DROP SYNONYM my_prod_price_change_view;
DROP TABLE my_prod_price_change_for_boys;

UPDATE products SET prod_min_price=prod_min_price*0.909
   WHERE prod_name='Kids Cargo Shorts' 
   AND   prod_min_price < 7;

UPDATE products SET prod_list_price=prod_list_price*0.909
   WHERE prod_name='Kids Cargo Shorts' 
   AND   prod_min_price > 11.50;

COMMIT;


Propagation from a Data Warehouse to a Data Mart Overview

Besides its central data warehouse, MyCompany is running several small divisional data marts. For example, the products department wants to get all transactional SALES data partitioned by its main product categories for marketing campaign analysis; only the SALES data of 2000 is relevant. You will address the business problem with Oracle’s Transportable Tablespace capabilities and Oracle’s new LIST partitioning, introduced in Oracle9i. Furthermore, to guarantee a successful completion of the generation process of this new table, you will run this statement in RESUMABLE mode, thus ensuring that any space problem will not cause the creation process to fail.

Note: To demonstrate the benefit of resumable statements, you will need TWO sessions and therefore TWO windows. Please read the following section CAREFULLY before exercising.

To propagate from a data warehouse to a data mart, you perform the following steps:

1.

Enable a RESUMABLE session.

2.

Create a new tablespace as potential transportable tablespace.

3.

Create a LIST partitioned table in the new tablespace.

4.

Leverage the new RESUMABLE statement capabilities for efficient error detection and handling.

5. Create a new RANGE_LIST partitioned table; this is Oracle9i Release 2 functionality.
6. Prepare the metadata export for a transportable tablespace.
7. Import the tablespace into another database.

1. Enable a RESUMABLE Session

With resumable statements, Oracle9i provides the ability to suspend and resume execution of large database operations in the event of repairable failure. Currently the types of errors from which a statement can resume are space limit and out-of-space errors. When an operation suspends, you have the opportunity to take the corrective steps to resolve the error condition. Alternatively, a procedure can be registered to automate the error correction. Once the error condition is solved, the suspended statement will automatically resume and continues operation. If the error is not corrected within an optionally specified suspension time limit, the statement will finally fail. You will choose a manual error correction for this exercise.

To see how this is done, perform the following:

1.

This SQL statement uses two table functions, nested in each other. From a SQL*Plus session logged on to the SH schema, execute the following SQL script:

@set_resumable.sql

ALTER SESSION ENABLE RESUMABLE TIMEOUT 1200 
   NAME 'create list partitioning';

This brings your session in the so-called resumable mode, names it, and enables a maximum suspension time of 1200 seconds.


2. Create a New Tablespace as Potential Transportable Tablespace

Now create an additional tablespace for storing our new LIST partitioned fact table.

1.

First, make sure that the potential tablespace TRANSFER does not exist. From a SQL*Plus session logged on to the SH schema, execute the following SQL script:

@drop_ts.sql

DROP TABLESPACE transfer INCLUDING CONTENTS AND DATAFILES;

The tablespace is dropped. If you get an ORA-959 message, you can ignore it; this means that the tablespace does not exist.

2.

From a SQL*Plus session logged on to the SH schema, execute the following SQL script:

NOTE: If you are using a different drive or path for your data files, the script will need to be edited accordingly.

@create_ts.sql

CREATE TABLESPACE transfer 
   DATAFILE 'D:\oracle\oradata\orcl\transfer.dbf' SIZE 2M REUSE;

The tablespace is intentionally defined too small for the target table to enforce a space error during table creation.


3. Create a LIST Partitioned Table in the New Tablespace

In Window 1, you will create a LIST partitioned table with a CREATE TABLE AS SELECT command in the newly created tablespace transfer.

1.

From a SQL*Plus session logged on to the SH schema, execute the following SQL script:

NOTE: This statement will hang due to space problems in the TRANSFER tablespace and show up in the alert log. Once it hangs move to the next step.

@create_list_part_table.sql

DROP TABLE sales_prod_dept;

CREATE TABLE sales_prod_dept
   ( prod_category, prod_subcategory, cust_id, 
     time_id,channel_id,promo_id, quantity_sold, amount_sold
   ) 
   NOLOGGING TABLESPACE transfer
   PARTITION BY LIST (prod_category)
      ( PARTITION boys_sales values ('Boys'),
        PARTITION girls_sales values ('Girls'),
        PARTITION men_sales values ('Men'),
        PARTITION women_sales values ('Women')
      )
   AS
   SELECT p.prod_category, p.prod_subcategory, s.cust_id, s.time_id,
          s.channel_id, s.promo_id, SUM(s.amount_sold) amount_sold,
          SUM(s.quantity_sold) quantity_sold 
   FROM   sales s, products p, times t
   WHERE  p.prod_id=s.prod_id
   AND    s.time_id = t.time_id
   AND    t.fiscal_year=2000
   GROUP BY prod_category, prod_subcategory,cust_id, s.time_id,
            channel_id, promo_id
;


4. Leverage the New RESUMABLE Statement Capabilities for Efficient Error Detection and Handling

While the CREATE TABLE AS SELECT is running in Window 1, you can control the status of all sessions running in RESUMABLE mode in Window 2.

1.

From a SQL*Plus session logged on to the SH schema, execute the following SQL script:

@show_resumable_status.sql

SELECT name, status, error_msg FROM dba_resumable;

As long as no error occurs, the session stays in a normal status. As soon as an error occurs, your session status will change, and the ORA-error causing this problem will be displayed. The current statement in the RESUMABLE session will be suspended until either (a) the problem is fixed, or (b) the timeout limit is reached. The error will also show up in the alert.log file of the running instance.

2.

Now fix the problem manually. Issue the following commands for the appropriate operating system and in the appropriate directory where the external files reside.

From a SQL*Plus session logged on to the SYSTEM Schema, execute the following SQL script:

@fix_ts.sql

ALTER DATABASE DATAFILE 'D:\oracle\oradata\orcl\transfer.dbf' 
   AUTOEXTEND ON;

The tablespace can now be autoextended by the database itself without any further interaction.

3. As soon as the error is fixed, the suspended session automatically resumes and continues processing. Check the data dictionary view for change of status. From a SQL*Plus session logged on to the SH schema, execute the following SQL script:
@show_resumable_status.sql

select name, status, error_msg 
FROM dba_resumable;

The resumption of the statement also shows up in the alert.log file of the running instance and in the previously hung window.


5. Create a range-list partitioned table in the transfer tablespace

Oracle9i Release 2 enhances Oracle’s mature Partitioning technology even more, by adding new partitioning strategies to address more business scenarios and by enhancing existing functionality. One of the enhancements, the transparent FAST SPLIT operation is discussed in Module I.

With the additional partitioning strategy RANGE-LIST, Oracle offers a second composite partitioning method to subdivide a partitioned table based on two logical attributes. The main partitioning strategy is the well-known and most used Range partitioning strategy. You’ll find this implementation in nearly every data warehouse implementation dealing with Rolling Window Operations (for details about Rolling Window Operations, please refer to Module I), Furthermore, every Range partition is partitioned based on a List Partitioning strategy, enabling a finer partition granule for addressing your business needs.

A typical example leveraging Range-List Partitioning is a global Retail environment, having time range partitions (Rolling Window) and a region-oriented list partition underneath, so you can maintain every time window for a specific region independent of each other.

List partitioned tables, introduced in Oracle9i Release 1, enable you to group sets of distinct unrelated values together in one partition. Consequently, all values not being covered with an existing partition definition will raise an Oracle error message. Although this enforces a business constraint (you should not see a non-existing region in your data warehouse), you cannot always be sure to not having such violations. Oracle9i Release 2 therefore offers the capability to create a DEFAULT List Partition, a kind of ‘catch-all’ partition for all undefined partition key values.

Oracle9i Release 2 also introduces the so-called subpartition template technology, a common optional element of both range-hash and range-list composite partitioning. The template provides an easy and convenient way to define default subpartitions for each table partition. Oracle will create these default subpartitions in any partition for which you do not explicitly define subpartitions. This clause is useful for creating symmetric partitions.

Let’s now create a Range-List partitioned tables, leveraging all of the above-mentioned new functionality:

1.

From a SQL*Plus session logged on to the SH schema, run the following SQL statements into your SQL*Plus session:

@create_new_range_list.sql

CREATE TABLE sales_rlp
   COMPRESS
   TABLESPACE transfer
   PARTITION BY RANGE (time_id)
   SUBPARTITION BY LIST (channel_id)
   SUBPARTITION TEMPLATE
   ( SUBPARTITION direct values ('S'),
     SUBPARTITION internet values ('I'),
     SUBPARTITION partner values ('P'),
     SUBPARTITION other values (DEFAULT)
   )
    (PARTITION SALES_before_1999 VALUES LESS THAN (
                                 TO_DATE('01-JAN-1999','DD-MON-YYYY')),
     PARTITION SALES_Q1_1999 VALUES LESS THAN (
                             TO_DATE('01-APR-1999','DD-MON-YYYY')),
     PARTITION SALES_Q2_1999 VALUES LESS THAN (
                             TO_DATE('01-JUL-1999','DD-MON-YYYY')),
     PARTITION SALES_Q3_1999 VALUES LESS THAN (
                             TO_DATE('01-OCT-1999','DD-MON-YYYY')),
     PARTITION SALES_Q4_1999 VALUES LESS THAN (
                             TO_DATE('01-JAN-2000','DD-MON-YYYY')),
     PARTITION SALES_Q1_2000 VALUES LESS THAN (
                             TO_DATE('01-APR-2000','DD-MON-YYYY')),
     PARTITION SALES_Q2_2000 VALUES LESS THAN (
                             TO_DATE('01-JUL-2000','DD-MON-YYYY')),
     PARTITION SALES_Q3_2000 VALUES LESS THAN (
                             TO_DATE('01-OCT-2000','DD-MON-YYYY')),
     PARTITION SALES_Q4_2000 VALUES LESS THAN (MAXVALUE) NOCOMPRESS)
   AS
   SELECT * 
   FROM   sales
;

This example also shows how you can easily create compressed and uncompressed partitions as part of an initial table creation. See Module I for details about the new feature of table compression.

This example also shows how you can easily create compressed and uncompressed partitions as part of an initial table creation. See Module I for details about the new feature of table compression.

As you can see, the partition and template names are inherited to all of the subpartitions, thus making the naming – and identification – of subpartitions much easier and more convenient.

1.

From a SQL*Plus session logged on to the SH schema, run the following SQL statements into your SQL*Plus session:

@show_range_list_names.sql

SELECT partition_name, subpartition_name, high_value
FROM   user_tab_subpartitions 
WHERE  table_name='SALES_RLP';

A List and Range-List partitioned table group sets of distinct unrelated values together in one partition. Consequently, all values not being covered with an existing partition definition will raise an Oracle error message. We will now use a new Oracle9i Release 2 capability and create a DEFAULT List Partition for the previously created List partitioned table sales_prod_dept, a kind of ‘catch-all’ partition for all undefined partition key values.

1.

From a SQL*Plus session logged on to the SH schema, run the following SQL statements into your SQL*Plus session:

@cr_default_list_part.sql

PROMPT add a new partition to sales_prod_dept
Rem it is added just like for a range partitioned table

ALTER TABLE sales_prod_dept ADD 
   PARTITION alien_sales VALUES ('Aliens');

PROMPT now add another one, covering the DEFAULT value

ALTER TABLE sales_prod_dept 
   ADD PARTITION other_sales VALUES (DEFAULT);
PROMPT control the data dictionary

select partition_name, high_value 
from   user_tab_partitions 
where  table_name='SALES_PROD_DEPT';

All records having an undefined partitioning key will be stored in this new partition other_sales. However, having a DEFAULT partition changes the way you’re adding new partitions to this table. Conceptually, we cover all possible values for the partitioning key – the defined ones and ‘all others’ – as soon as we have created a default partition.

So, to ADD a new partition to this table, we logically have to SPLIT the default partition into a new partition with a set of defined keys and a new default partition, still covering ‘all other values’ (now reduced by the keys we have specified for the new partition). Any attempt to add a new partition like we did before will raise an ORA error message.

1.

From a SQL*Plus session logged on to the SH schema, run the following SQL statements into your SQL*Plus session:

@split_default_list_part.sql

PROMPT Unlike the first time, we cannot simply add a new partition
PROMPT raises ORA-14323: cannot add partition when DEFAULT 
PROMPT partition exists

ALTER TABLE sales_prod_dept 
   ADD PARTITION undefined_sales VALUES ('Undefined');

This will raise an Oracle error.

PROMPT so we have to SPLIT the default partition to ensure that 
PROMPT all potential values of 'Undefined' are in the new partition

ALTER TABLE sales_prod_dept 
   SPLIT PARTITION other_sales VALUES ('Undefined') 
   INTO (PARTITION undefined_sales, PARTITION other_sales);

PROMPT control the data dictionary
PROMPT Note that without specifying any tablespace, the default 
PROMPT tablespace of the partitioned table is used

select partition_name, tablespace_name, high_value 
from   user_tab_partitions 
where  table_name='SALES_PROD_DEPT';


6. Prepare the Metadata Export for a Transportable Tablespace

To prepare a tablespace for transportation, you simply have to export the metadata information of the tablespace. This export, together with a physical copy of the tablespace can be used for importing the tablespace into another database instance. Before 9i, the target database must be running on the same operating system with the same database block size. Oracle9i lifts the restricition of the same block size.

To show this capability, you perform the following steps:

1.

From a SQL*Plus session logged on to the SH schema, execute the following SQL script:

@make_ts_ro.sql

ALTER TABLESPACE transfer READ ONLY;

This guarantees that no further changes can happen on the data in the tablespace.

2.

You can now export the data dictionary information of tablespace transfer as follows:

@export_metadata.sql

host exp userid='sys/oracle@orcl.world as sysdba' 
   FILE=meta_transfer.dmp TRANSPORT_TABLESPACE=y TABLESPACES=transfer 
   LOG=meta_transfer.log

Note: The same export syntax can be used at the DOS prompt by dropping the SQL*Plus 'host' command.

3.

Before you import the tablespace information into another database, you will want to clean up and reset the session status by executing the SQL script:

@cleanup_tts.sql

DROP TABLESPACE transfer INCLUDING CONTENTS AND DATAFILES;

ALTER SESSION DISABLE RESUMABLE;

4.

Trying to access table sales_prod_dept raises an error. The table does not longer exist

SELECT count(*) 
FROM   sales_prod_dept;


7. Importing the Tablespace into Another Database

To import the tablespace into another database, you simply have to copy the file to its target destination, and to import the dictionary metadata. You can simulate this by importing the recently dropped tablespace into the same instance again. Issue the following commands for the appropriate operating system and in the appropriate directory where the external files reside.

1.

You can now import the metadata of tablespace as follows:

@import_metadata.sql

host imp userid='sys/change_on_install@orcl.world as sysdba' 
   FILE=meta_transfer.dmp transport_tablespace=y buffer=100000000 
   datafiles='D:\oracle\oradata\orcl\transfer.dbf' tablespaces=transfer 
   log=import_metadata.log

The same export syntax can be used at the DOS prompt by dropping the SQL*Plus 'host' command.

2.

After a successful import, you will see the SALES_PROD_DEPT table again. From a SQL*Plus session logged on to the SH schema, execute the following SELECT statement:

SELECT count(*) FROM sales_prod_dept;


Module Summary

In this module, you should have learned how to:

Perform a multi-table Insert
Compare the new Upsert functionality, implemented as the SQL MERGE command, with its manual implementation prior to Oracle9i
Create and use a table function
Use synchronous Change Data Capture to track - and consume - incremental data changes
Combine components of the ETL infrastructure from Oracle9i into an efficient, parallel, and seamless transformation process without staging
Propagate information for a data mart by leveraging new LIST partitioning from Oracle9i, the transportable tablespace mechanism, and the new RESUMABLE statement capabilities
Use Oracle9i capabilities of DEFAULT List partitions and understand its business value

Copyright © 2002 Oracle Corporation. All Rights Reserved.

Close Window

E-mail this page
Printer View Printer View
Oracle Is The Information Company About Oracle | Oracle RSS Feeds | Careers | Contact Us | Site Maps | Legal Notices | Terms of Use | Privacy