- External tables do not reside in the database, and can be in any format for which an access driver is provided.
- You can select, join, or sort external table data.
- You can also create views and synonyms for external tables.
- You CANNOT execute DML operations (UPDATE, INSERT, or DELETE) , and CANNOT create indexes on external tables.
External tables and Data Warehouse
- Exporting data from a database:
- External tables provide a framework to unload the result of an arbitrary SELECT statement into a platform-independent Oracle-proprietary format that can be used by Oracle Data Pump.
- External tables provide a valuable means for performing basic extraction, transformation, and loading (ETL) tasks that are common for data warehousing.
Creating External tables
CREATE TABLE...ORGANIZATION EXTERNAL
statement.
- This statement creates only metadata in the data dictionary.
- External tables can be thought of as views that allows running any SQL query against external data without requiring that the external data first be loaded into the database.
Accessing External Data
- An access driver is the actual mechanism used to read the external data in the table.
- When you use external tables to unload data, the metadata is automatically created based on the data types in the SELECT statement.
Oracle Database provides
two access drivers for external tables:
- ORACLE_LOADER driver (default)
- allows the reading of data from external files using the Oracle loader technology.
- provides data mapping capabilities which are a subset of the control file syntax of SQL*Loader utility.
- ORACLE_DATAPUMP driver
- lets you unload data: read data from the database and insert it into an external table, represented by one or more external files—and then reload it into an Oracle Database.
Scenario 1:
Unload data to flat files and create External Table.
ORACLE_LOADER driver
Step 1 - Export data from table sh.customers
/* Step 1 - Export data from table sh.customers
Procedure UNLOAD_DATA receives table_name and table_owner as parameter and
unloads the table data to a flat file */
set serveroutput on
DECLARE
procedure unload_data(
p_table_name in varchar2,
p_owner in varchar2)
IS
sqltext varchar2(1000);
v_textrecord varchar2(2000);
cv sys_refcursor;
fdest utl_file.file_type;
type FileAttrRec is Record (
vfilexists BOOLEAN,
vfilelength number,
vblocksize binary_integer);
vfilerec fileattrrec;
vfilename varchar2(30);
procedure get_sql (
p_sqltext in out varchar2,
p_table_name in varchar2,
p_owner in varchar2)
IS
cursor c is
select column_name
from dba_tab_columns
where table_name = p_table_name and owner = p_owner
order by column_id;
i integer;
sql_stmt varchar2(2000);
from_clause varchar2(100);
begin
i :=0;
sql_stmt := 'SELECT ';
from_clause := 'FROM ' || p_owner ||'.' || p_table_name;
for r in c loop
if i = 0 then
sql_stmt := sql_stmt || ' ' || r.column_name;
i := 1;
else
sql_stmt := sql_stmt || '|| ''|''|| ' || r.column_name;
end if;
end loop;
sql_stmt := sql_stmt || ' as qrystr ' || chr(10) || from_clause;
p_sqltext := sql_stmt;
-- dbms_output.put_line( sql_stmt);
end get_sql;
begin
-- 1. Get Query SQL
dbms_output.enable(20000);
sqltext := '';
get_sql(sqltext, p_table_name, p_owner);
--dbms_output.put_line('Select stmt:' || sqltext);
-- 2. Open cursor for query. Write each row in the OS file
vfilename := 'unload_' || p_owner ||'_'|| p_table_name || '.dat';
utl_file.fgetattr('DATA_DUMP_DIR', vfilename, vfilerec.vfilexists,
vfilerec.vfilelength, vfilerec.vblocksize);
if vfilerec.vfilexists then
fdest := utl_file.fopen('DATA_PUMP_DIR', vfilename, 'a', 2048);
dbms_output.put_line('Destination file exists. Appending..');
else
fdest := utl_file.fopen('DATA_PUMP_DIR', vfilename, 'w', 2048);
end if;
open cv for sqltext;
LOOP
fetch cv into v_textrecord;
EXIT WHEN cv%NOTFOUND;
utl_file.put_line(fdest, v_textrecord, true);
END LOOP;
utl_file.fclose(fdest);
close cv;
END unload_data;
BEGIN
unload_data('CUSTOMERS' , 'SH');
END;
Step 2 - Split unloaded file and create external table using the resulting files
$ ls -ltr
total 482928
...
-rw-r--r-- 1 oracle oinstall 10661903 2012-03-20 22:01 unload_SH_CUSTOMERS.dat
-- spliting the file in two smaller files
$ split -30000 unload_SH_CUSTOMERS.dat unload_SH_CUSTOMERS.dat
$ ls -ltr
total 503760
...
-rw-r--r-- 1 oracle oinstall 10661903 2012-03-20 22:01 unload_SH_CUSTOMERS.dat
-rw-r--r-- 1 oracle oinstall 5768182 2012-03-20 22:15 unload_SH_CUSTOMERS.dataa
-rw-r--r-- 1 oracle oinstall 4893721 2012-03-20 22:15 unload_SH_CUSTOMERS.datab
Step 2 - Create external table using unloaded file(s)
-- create the external table
SQL> Create table ext_customers1
(
"CUST_ID" NUMBER,
"CUST_FIRST_NAME" VARCHAR2(20 BYTE),
"CUST_LAST_NAME" VARCHAR2(40 BYTE),
"CUST_GENDER" CHAR(1 BYTE),
"CUST_YEAR_OF_BIRTH" NUMBER(4,0),
"CUST_MARITAL_STATUS" VARCHAR2(20 BYTE),
"CUST_STREET_ADDRESS" VARCHAR2(40 BYTE),
"CUST_POSTAL_CODE" VARCHAR2(10 BYTE),
"CUST_CITY" VARCHAR2(30 BYTE),
"CUST_CITY_ID" NUMBER,
"CUST_STATE_PROVINCE" VARCHAR2(40 BYTE),
"CUST_STATE_PROVINCE_ID" NUMBER,
"COUNTRY_ID" NUMBER,
"CUST_MAIN_PHONE_NUMBER" VARCHAR2(25 BYTE),
"CUST_INCOME_LEVEL" VARCHAR2(30 BYTE),
"CUST_CREDIT_LIMIT" NUMBER,
"CUST_EMAIL" VARCHAR2(30 BYTE),
"CUST_TOTAL" VARCHAR2(14 BYTE),
"CUST_TOTAL_ID" NUMBER,
"CUST_SRC_ID" NUMBER,
"CUST_EFF_FROM" DATE,
"CUST_EFF_TO" DATE,
"CUST_VALID" VARCHAR2(1 BYTE)
)
ORGANIZATION EXTERNAL
(
TYPE ORACLE_LOADER
DEFAULT DIRECTORY data_pump_dir
ACCESS PARAMETERS
(
records delimited by newline
badfile data_pump_dir:'cust1ext%a_%p.bad'
logfile data_pump_dir:'cust1ext%a_%p.log'
fields terminated by '|'
missing field values are null
)
LOCATION ('unload_SH_CUSTOMERS.dataa', 'unload_SH_CUSTOMERS.datab')
)
PARALLEL
REJECT LIMIT UNLIMITED;
table EXT_CUSTOMERS1 created.
SQL> select table_name, type_name, default_directory_name, reject_limit, access_parameters, property
from dba_external_tables;
TABLE_NAME TYPE_NAME DEFAULT_DIRECTORY_NAME REJECT_LIMIT ACCESS_PARAMETERS
--------------- --------------- ------------------------ --------------- ------------------------------------------
EXT_CUSTOMERS1 ORACLE_LOADER DATA_PUMP_DIR UNLIMITED records delimited by newline
badfile data_pump_dir:'cust1ext%a_%p.bad'
logfile data_pump_dir:'cust1ext%a_%p.log'
fields terminated by '|'
missing filed values are null
SQL> select count(*) from ext_customers1;
COUNT(*)
----------
55500
Scenario 2: Unloading and Loading Data with the ORACLE_DATAPUMP Access Driver
Step 1 - (a) Use CREATE TABLE ... ORGANIZATION EXTERNAL ... AS SELECT ...
- This will create a file with the data resulting from the specified query.
- The file is created with a binary format that can only be read by the ORACLE_DATAPUMP access driver
- The example below creates an external table and populates the dump file for the external table with the data from table sh.customers.
connect sh/**;
SQL> create table customers1_xt
organization external
(
type oracle_datapump
default directory data_pump_dir
location ('customers1_xt.dmp')
)
as select * from sh.customers;
table created.
SQL>
(b) Check the OS files created:
$ ls -ltr
total 60672
-rw-r----- 1 oracle oinstall 10334208 2012-03-21 00:33 customers1_xt.dmp
-rw-r--r-- 1 oracle oinstall 123 2012-03-21 00:36 CUSTOMERS1_XT_7165.log
(c) Compare descriptions of customers and customers1_xt tables.
SQL> desc customers;
Name Null Type
---------------------- -------- ------------
CUST_ID NOT NULL NUMBER
CUST_FIRST_NAME NOT NULL VARCHAR2(20)
CUST_LAST_NAME NOT NULL VARCHAR2(40)
CUST_GENDER NOT NULL CHAR(1)
CUST_YEAR_OF_BIRTH NOT NULL NUMBER(4)
CUST_MARITAL_STATUS VARCHAR2(20)
CUST_STREET_ADDRESS NOT NULL VARCHAR2(40)
CUST_POSTAL_CODE NOT NULL VARCHAR2(10)
CUST_CITY NOT NULL VARCHAR2(30)
CUST_CITY_ID NOT NULL NUMBER
CUST_STATE_PROVINCE NOT NULL VARCHAR2(40)
CUST_STATE_PROVINCE_ID NOT NULL NUMBER
COUNTRY_ID NOT NULL NUMBER
CUST_MAIN_PHONE_NUMBER NOT NULL VARCHAR2(25)
CUST_INCOME_LEVEL VARCHAR2(30)
CUST_CREDIT_LIMIT NUMBER
CUST_EMAIL VARCHAR2(30)
CUST_TOTAL NOT NULL VARCHAR2(14)
CUST_TOTAL_ID NOT NULL NUMBER
CUST_SRC_ID NUMBER
CUST_EFF_FROM DATE
CUST_EFF_TO DATE
CUST_VALID VARCHAR2(1)
SQL> desc customers1_xt;
Name Null Type
---------------------- -------- ------------
CUST_ID NOT NULL NUMBER
CUST_FIRST_NAME NOT NULL VARCHAR2(20)
CUST_LAST_NAME NOT NULL VARCHAR2(40)
CUST_GENDER NOT NULL CHAR(1)
CUST_YEAR_OF_BIRTH NOT NULL NUMBER(4)
CUST_MARITAL_STATUS VARCHAR2(20)
CUST_STREET_ADDRESS NOT NULL VARCHAR2(40)
CUST_POSTAL_CODE NOT NULL VARCHAR2(10)
CUST_CITY NOT NULL VARCHAR2(30)
CUST_CITY_ID NOT NULL NUMBER
CUST_STATE_PROVINCE NOT NULL VARCHAR2(40)
CUST_STATE_PROVINCE_ID NOT NULL NUMBER
COUNTRY_ID NOT NULL NUMBER
CUST_MAIN_PHONE_NUMBER NOT NULL VARCHAR2(25)
CUST_INCOME_LEVEL VARCHAR2(30)
CUST_CREDIT_LIMIT NUMBER
CUST_EMAIL VARCHAR2(30)
CUST_TOTAL NOT NULL VARCHAR2(14)
CUST_TOTAL_ID NOT NULL NUMBER
CUST_SRC_ID NUMBER
CUST_EFF_FROM DATE
CUST_EFF_TO DATE
CUST_VALID VARCHAR2(1)
(d) Check data on customers1_xt and compare with original table
SQL> select count(*) from customers1_xt;
COUNT(*)
----------------------
55500
SQL> select * from customers minus select * from customers1_xt;
no rows selected
(e) You CANNOT perform DML on the external table:
SQL> delete from customers1_xt where cust_id = 100055;
delete from customers1_xt where cust_id = 100055
*
ERROR at line 1:
ORA-30657: operation not supported on external organized table
(f) You can also Create an external table with multiple dump files and with a degree of parallelism higher than one.
SQL> create table customers1_xtp3
organization external
(
type oracle_datapump
default directory data_pump_dir
location('customers1_xtp31.dmp' ,'customers1_xtp32.dmp' ,'customers1_xtp33.dmp' )
)
parallel 3
as select * from sh.customers;
$ ls -ltr
total 60672
-rw-r----- 1 oracle oinstall 10334208 2012-03-21 00:33 customers1_xt.dmp
-rw-r--r-- 1 oracle oinstall 123 2012-03-21 00:36 CUSTOMERS1_XT_7165.log
-rw-r--r-- 1 oracle oinstall 41 2012-03-21 10:13 CUSTOMERS1_XTP3_2690.log
-rw-r--r-- 1 oracle oinstall 41 2012-03-21 10:13 CUSTOMERS1_XTP3_2737.log
-rw-r--r-- 1 oracle oinstall 41 2012-03-21 10:13 CUSTOMERS1_XTP3_2739.log
-rw-r--r-- 1 oracle oinstall 41 2012-03-21 10:13 CUSTOMERS1_XTP3_2741.log
-rw-r----- 1 oracle oinstall 2990080 2012-03-21 10:13 customers1_xtp32.dmp
-rw-r----- 1 oracle oinstall 3792896 2012-03-21 10:13 customers1_xtp33.dmp
-rw-r----- 1 oracle oinstall 3592192 2012-03-21 10:13 customers1_xtp31.dmp
(g) You can transfer or copy the dump file(s) and use them for another external table either in the same or in a different database.
SQL> Create table customers2_xt
(
"CUST_ID" NUMBER,
"CUST_FIRST_NAME" VARCHAR2(20 BYTE),
"CUST_LAST_NAME" VARCHAR2(40 BYTE),
"CUST_GENDER" CHAR(1 BYTE),
"CUST_YEAR_OF_BIRTH" NUMBER(4,0),
"CUST_MARITAL_STATUS" VARCHAR2(20 BYTE),
"CUST_STREET_ADDRESS" VARCHAR2(40 BYTE),
"CUST_POSTAL_CODE" VARCHAR2(10 BYTE),
"CUST_CITY" VARCHAR2(30 BYTE),
"CUST_CITY_ID" NUMBER,
"CUST_STATE_PROVINCE" VARCHAR2(40 BYTE),
"CUST_STATE_PROVINCE_ID" NUMBER,
"COUNTRY_ID" NUMBER,
"CUST_MAIN_PHONE_NUMBER" VARCHAR2(25 BYTE),
"CUST_INCOME_LEVEL" VARCHAR2(30 BYTE),
"CUST_CREDIT_LIMIT" NUMBER,
"CUST_EMAIL" VARCHAR2(30 BYTE),
"CUST_TOTAL" VARCHAR2(14 BYTE),
"CUST_TOTAL_ID" NUMBER,
"CUST_SRC_ID" NUMBER,
"CUST_EFF_FROM" DATE,
"CUST_EFF_TO" DATE,
"CUST_VALID" VARCHAR2(1 BYTE)
)
ORGANIZATION EXTERNAL
(
TYPE ORACLE_DATAPUMP
DEFAULT DIRECTORY data_pump_dir
LOCATION ('customers1_xtp31.dmp' ,'customers1_xtp32.dmp' ,'customers1_xtp33.dmp')
);
SQL> select count(*) from customers2_xt;
COUNT(*)
----------------------
55500
SQL> Select * from sh.customers minus select * from customers2_xt;
no rows selected.