SQLLDR default character length & ways to fix it - oracle

I faced with a situation where one the columns data to be uploaded exceeded 255 and it threw Error Field in data file exceeds maximum length error.
I found a way to fix it. Please find link here explaining the logic.
The question is that in a control file, is there any difference between
Comments CHAR(255) "TRIM(:Comments)" ,
and
Comments CHAR "TRIM(:Comments)" ,
when it comes to the internal workings of sqlldr or it means the same right ?
Also while uploading, because the integrity of the data file cannot be trusted, we create a table with all columns as 255 [which i will fix for columns > 255 length] and I never specify the CHAR length in the control file.
I would like to know if the difference between
using default 255 for all columns
vs
keeping little extra than expected length [actual target table column length values] eg. actual expected length [which might range from 10 to 150 etc.] + 50/100
is very significant if i use sqlldr atleast 2000 times a day on small data files with average of 250 records.
Thanks in advance for the clarification.

I think they are the same as internally that is what the buffer size is as well.
After dealing with this enough times I created a real rough utility script that generates a skeleton control file that uses the column's actual size. It gets me 90% of the way there and no column sizing issues. It may be better on memory as its not going to use the full 255 chars available if the column is smaller. Give it a try if you want. It may give you some ideas anyway.
/********************************************************************************************************
Name: GEN_CTL_FILE
Desc: Generates a skeleton control file for loading data via SQL*Loader.
Args: tablename_in IN VARCHAR2, delim_in IN VARCHAR2 DEFAULT '|'
Returns: None.
Usage: utilities.gen_ctl_file('tablename');
Notes: Prints a skeleton control file.
If a template for a fixed-length data file is desired, use 'FIXED' for the delim_in string.
FIXED needs more work to put actual lengths in. For now just placeholders.
Example usage:
set serveroutput on;
execute utilities.gen_ctl_file('test', utilities.TAB);
REVISIONS:
Ver Date Author Description
--------- ---------- --------------- ------------------------------------
1.1 6/6/2013 gary_w - Created procedure.
1.2 10/8/2013 gary_w - Fixed decode statement.
- Added option to generate a fixed-length template.
************************************************************************************************************************/
PROCEDURE GEN_CTL_FILE(tablename_in IN VARCHAR2, delim_in VARCHAR2 DEFAULT '|') IS
ERRNULLTABLENAME CONSTANT NUMBER := -20103; -- User-defined error numbers and messages.
ERRNULLTABLENAMEMSG CONSTANT VARCHAR2(100) := 'A table name is required.';
USAGE CONSTANT VARCHAR2(100) := '* USAGE: UTILITIES.GEN_CTL_FILE(tablename_in IN VARCHAR2, fieldsep_in VARCHAR2 DEFAULT ''|'')';
v_delim VARCHAR2(20) := NVL(delim_in, '|');
CURSOR COL_CUR IS
SELECT COLUMN_NAME,
DECODE(COLUMN_ID, 1, ' ', ',') || RPAD(COLUMN_NAME, 32) || case upper(v_delim)
when 'FIXED' then 'POSITION(99:99) '
else NULL
end|| DECODE(DATA_TYPE,
'VARCHAR2', 'CHAR('||DATA_LENGTH||') NULLIF(' || COLUMN_NAME || '=BLANKS)',
'FLOAT', 'DECIMAL EXTERNAL NULLIF(' || COLUMN_NAME || '=BLANKS)',
'NUMBER', DECODE( DATA_PRECISION,
0, 'INTEGER EXTERNAL NULLIF (' || COLUMN_NAME || '=BLANKS)',
DECODE(DATA_SCALE, 0, 'INTEGER EXTERNAL NULLIF (' || COLUMN_NAME || '=BLANKS)', 'DECIMAL EXTERNAL NULLIF (' || COLUMN_NAME || '=BLANKS)')),
'DATE', 'DATE "MM/DD/YYYY" NULLIF (' || COLUMN_NAME || '=BLANKS)',
data_type)
AS COL_DATA
FROM USER_TAB_COLUMNS
WHERE TABLE_NAME = UPPER(tablename_in)
ORDER BY COLUMN_ID;
BEGIN
IF tablename_in IS NULL THEN
RAISE_APPLICATION_ERROR(ERRNULLTABLENAME, ERRNULLTABLENAMEMSG || CR || USAGE);
END IF;
DBMS_OUTPUT.PUT_LINE('--');
DBMS_OUTPUT.PUT_LINE('-- NOTE - When using DIRECT=TRUE to perform block inserts to a table,');
DBMS_OUTPUT.PUT_LINE('-- the table''s triggers will not be used! Plan accordingly to');
DBMS_OUTPUT.PUT_LINE('-- manually perform the trigger actions after loading, if needed.');
DBMS_OUTPUT.PUT_LINE('--');
DBMS_OUTPUT.PUT_LINE('OPTIONS (DIRECT=TRUE)');
DBMS_OUTPUT.PUT_LINE('UNRECOVERABLE');
DBMS_OUTPUT.PUT_LINE('LOAD DATA');
DBMS_OUTPUT.PUT_LINE('APPEND');
DBMS_OUTPUT.PUT_LINE('INTO TABLE ' || UPPER(tablename_in));
DBMS_OUTPUT.PUT_LINE('EVALUATE CHECK_CONSTRAINTS');
if upper(v_delim) != 'FIXED' then
DBMS_OUTPUT.PUT_LINE('FIELDS TERMINATED BY ' || '''' || v_delim || '''');
DBMS_OUTPUT.PUT_LINE('OPTIONALLY ENCLOSED BY ''"'' ');
DBMS_OUTPUT.PUT_LINE('TRAILING NULLCOLS');
end if;
DBMS_OUTPUT.PUT_LINE('(');
-- The cursor for loop construct implicitly opens and closes the cursor.
FOR COL IN COL_CUR
LOOP
IF COL.COLUMN_NAME != 'LOAD_DATE' THEN
IF COL.COLUMN_NAME = 'LOAD_SEQ_ID' THEN
dbms_output.put_line(','||RPAD('LOAD_SEQ_ID', 32)||'CONSTANT 0');
ELSE
DBMS_OUTPUT.PUT_LINE(COL.COL_DATA);
END IF;
END IF;
END LOOP;
DBMS_OUTPUT.PUT_LINE(')' || CHR(10));
EXCEPTION
WHEN OTHERS THEN
RASIE;
END; -- GEN_CTL_FILE
The output looks like this:
--
-- NOTE - When using DIRECT=TRUE to perform block inserts to a table,
-- the table's triggers will not be used! Plan accordingly to
-- manually perform the trigger actions after loading, if needed.
--
OPTIONS (DIRECT=TRUE)
UNRECOVERABLE
LOAD DATA
APPEND
INTO TABLE TEST
EVALUATE CHECK_CONSTRAINTS
FIELDS TERMINATED BY '|'
OPTIONALLY ENCLOSED BY '"'
TRAILING NULLCOLS
(
COLA CHAR(200) NULLIF(COLA=BLANKS)
,COLB CHAR(100) NULLIF(COLB=BLANKS)
,COLC CHAR(500) NULLIF(COLC=BLANKS)
,COLD DECIMAL EXTERNAL NULLIF (COLD=BLANKS)
,COLE CLOB
)
If you tweak it, please share your changes.

Related

Handle NULL within Oracle PL/SQL

Data is imported into data_import table through sql insert statements. These insert statements may not be the most accurate so I want to let all data in to the table (hence no PK to prevent duplicates)
The code checks the contents of the data_import table for duplicate instances using a count on occurrences any occurrences are mark as in error.
DECLARE
CURSOR C_DUPLICATE_IMPORT_IDS
IS
SELECT COUNT (D.LEARNER_ID), D.LEARNER_ID
FROM ILA500.DATA_IMPORT D
WHERE D.USER_ID IN (SELECT S.OSUSER
FROM V$SESSION S
WHERE S.SID IN (SELECT DISTINCT V.SID
FROM V$MYSTAT V))
AND D.ERROR_FLAG = 'N'
AND D.LEARNER_ID <> NULL
HAVING COUNT (D.LEARNER_ID) > 1
GROUP BY D.LEARNER_ID;
V_DUPLICATE_IMPORT_IDS C_DUPLICATE_IMPORT_IDS%ROWTYPE;
BEGIN
OPEN C_DUPLICATE_IMPORT_IDS;
LOOP
FETCH C_DUPLICATE_IMPORT_IDS INTO V_DUPLICATE_IMPORT_IDS;
EXIT WHEN C_DUPLICATE_IMPORT_IDS%NOTFOUND;
UPDATE ILA500.DATA_IMPORT D
SET D.ERROR_FLAG = 'Y'
WHERE D.LEARNER_ID = V_DUPLICATE_IMPORT_IDS.LEARNER_ID;
UPDATE ILA500.DATA_IMPORT D
SET D.IMPORT_NOTIFICATION =
'DUPLICATE LEARNER_ID IDENTIFIED ('
|| V_DUPLICATE_IMPORT_IDS.LEARNER_ID
|| '). LEARNER_IDS ERROR_FLAG SET, THIS CASE WILL NOT IMPORT UNTIL CORRECTED.'
WHERE D.LEARNER_ID = V_DUPLICATE_IMPORT_IDS.LEARNER_ID;
IF V_DUPLICATE_IMPORT_IDS.LEARNER_ID IS NOT NULL
THEN
DBMS_OUTPUT.PUT_LINE (
'THE FOLLOWING LEARNER WAS IDENTIFIED AS A DUPLICATE '
|| V_DUPLICATE_IMPORT_IDS.LEARNER_ID);
ELSE
DBMS_OUTPUT.PUT_LINE (
CHR (10)
|| 'THERE ARE NO DUPLICATE LEARNER_IDS WITHIN THIS UPLOAD.');
END IF;
END LOOP;
CLOSE C_DUPLICATE_IMPORT_IDS;
DBMS_OUTPUT.PUT_LINE (CHR (10) || 'STEP 1 COMPLETED');
COMMIT;
END;
My problem comes within the IF statement
IF V_DUPLICATE_IMPORT_IDS.LEARNER_ID IS NOT NULL
THEN
DBMS_OUTPUT.PUT_LINE (
'THE FOLLOWING LEARNER WAS IDENTIFIED AS A DUPLICATE '
|| V_DUPLICATE_IMPORT_IDS.LEARNER_ID);
ELSE
DBMS_OUTPUT.PUT_LINE (
CHR (10)
|| 'THERE ARE NO DUPLICATE LEARNER_IDS WITHIN THIS UPLOAD.');
If the count is greater than 1 then data is returned and the
DBMS_OUTPUT.PUT_LINE ('THE FOLLOWING LEARNER WAS IDENTIFIED AS A DUPLICATE '|| DUPLICATE_IMPORT_IDS.LEARNER_ID);
statement successfully outputs the line .
However if a null count is returned within the query the
ELSE (DBMS_OUTPUT.PUT_LINE (
CHR (10)
|| 'THERE ARE NO DUPLICATE LEARNER_IDS WITHIN THIS UPLOAD.');)
doesn't output the line.
How to get a dmbs_output to produce data from a null?
However if a null count is returned ...
A null count can't be returned. The COUNT (D.LEARNER_ID) in your cursor query can't evaluate to null; it could produce zero but you're filtering any such results with your HAVING clause (which would also exclude null, if it could happen). And a null ID can't be returned because of how you are counting the values.
Your cursor query includes:
AND D.LEARNER_ID <> NULL
which isn't right; null isn't equal or unequal to anything, so this excludes all rows; you could do:
AND D.LEARNER_ID IS NOT NULL
but it's redundant anyway because you're doing COUNT(D.LERARNER_ID), which won't count nulls. It would make a difference if you were doing COUNT(*) though. db<>fiddle with just the cursor query, showing that effect.
Anyway... if you wanted to display a message for each ID if that had no duplicates you could remove the HAVING clause and test for non-zero instead of not-null; but from the text it looks like you want a single message if your cursor finds no rows at all. You can handle that with a 'found' flag which you set to true if you go into the cursor loop, and then test after the loop:
DECLARE
CURSOR C_DUPLICATE_IMPORT_IDS
IS
...
V_DUPLICATE_IMPORT_IDS C_DUPLICATE_IMPORT_IDS%ROWTYPE;
V_FOUND BOOLEAN := FALSE;
BEGIN
OPEN C_DUPLICATE_IMPORT_IDS;
LOOP
FETCH C_DUPLICATE_IMPORT_IDS INTO V_DUPLICATE_IMPORT_IDS;
EXIT WHEN C_DUPLICATE_IMPORT_IDS%NOTFOUND;
...
DBMS_OUTPUT.PUT_LINE (
'THE FOLLOWING LEARNER WAS IDENTIFIED AS A DUPLICATE '
|| V_DUPLICATE_IMPORT_IDS.LEARNER_ID);
V_FOUND := TRUE;
END LOOP;
CLOSE C_DUPLICATE_IMPORT_IDS;
IF NOT V_FOUND THEN
DBMS_OUTPUT.PUT_LINE (
CHR (10)
|| 'THERE ARE NO DUPLICATE LEARNER_IDS WITHIN THIS UPLOAD.');
END IF;
DBMS_OUTPUT.PUT_LINE (CHR (10) || 'STEP 1 COMPLETED');
COMMIT;
END;
db<>fiddle with and without matching data.
You could also simplify this a bit with an implicit cursor loop but the 'found' logic would be the same.
In the past, when a DML statement fails the whole statement is rolled back, regardless of how many rows were processed successfully before the error was detected. the only way around this problem was to process each row individually. There's a DBMS_ERRLOG package that provides a procedure that enables you to create an error logging table so that DML operations can continue after encountering errors rather than abort and rollback. This enables you to save time and system resources.
You can use this package and set the primary key in your table. For example:
-- First, create the error logging table.
BEGIN
DBMS_ERRLOG.create_error_log (dml_table_name => 'dest_table');
END;
Then in your insert staement do this:
INSERT INTO dest_table
SELECT *
FROM source_table
LOG ERRORS INTO err$_dest_table ('INSERT') REJECT LIMIT UNLIMITED;
Finally, You don't need these codes.
For more information read here and here.

ORA-00907: missing right parenthesis and ORA-06512: at line 16

For the below procedure I am getting the ora error as mentioned in the title while running in Oracle SQL developer
DECLARE
sqlstr VARCHAR2(1000);
CURSOR TabSubPartitions IS
SELECT TABLE_NAME, PARTITION_NAME
FROM USER_TAB_PARTITIONS
WHERE TABLE_NAME = 'PART_TABLE'
ORDER BY PARTITION_NAME;
BEGIN
FOR aSubPart IN TabSubPartitions LOOP
IF TRUNC(LAST_DAY(SYSDATE)) = '31-07-2020' THEN
sqlstr := 'ALTER TABLE '||aSubPart.TABLE_NAME||' MODIFY PARTITION '||aSubPart.PARTITION_NAME|| ' ADD SUBPARTITION ' ||aSubPart.PARTITION_NAME||'_'||TO_CHAR(TRUNC(LAST_DAY(SYSDATE))+1, 'MON_YYYY')||' VALUES LESS THAN ( '||TO_DATE(TRUNC(LAST_DAY(SYSDATE))+2, 'SYYYY-MM-DD HH24:MI:SS' , 'NLS_CALENDER=GREGORIAN')||')' ;
EXECUTE IMMEDIATE sqlstr;
END IF;
END LOOP;
END;
Could anyone please help me. Many thanks in advance.
The main issue in the block itself is is here:
'NLS_CALENDER=GREGORIAN'
where it must be
'NLS_CALENDAR=GREGORIAN'
Moreover, you should not rely on implicite date conversions.
But there is also an issue with your resulting ALTER TABLE statement, which looks something like
ALTER TABLE MODIFY PARTITION ... VALUES LESS THAN ( 02.08.2020 00:00:00)
depending on session date settings (again because of implict date conversion). I doubt that Oracle accepts a timestamp like this. Make sure you produce a proper date literal (like DATE '2020-08-02') instead.
The whole corrected code:
DECLARE
sqlstr VARCHAR2(1000);
CURSOR TabSubPartitions IS
SELECT TABLE_NAME, PARTITION_NAME
FROM USER_TAB_PARTITIONS
WHERE TABLE_NAME = 'PART_TABLE'
ORDER BY PARTITION_NAME;
BEGIN
FOR aSubPart IN TabSubPartitions LOOP
IF TRUNC(LAST_DAY(SYSDATE)) = DATE '2020-07-31' THEN
sqlstr :=
'ALTER TABLE ' || aSubPart.TABLE_NAME || ' MODIFY PARTITION ' || aSubPart.PARTITION_NAME ||
' ADD SUBPARTITION ' || aSubPart.PARTITION_NAME || '_' || TO_CHAR(TRUNC(LAST_DAY(SYSDATE))+1, 'MON_YYYY') ||
' VALUES LESS THAN (DATE ''' || TRIM(TO_CHAR(TRUNC(LAST_DAY(SYSDATE))+2, 'SYYYY-MM-DD', 'NLS_CALENDAR=GREGORIAN')) || ''')' ;
EXECUTE IMMEDIATE sqlstr;
END IF;
END LOOP;
END;
I have several pieces of advice.
First, modify your code to output the value of sqlstr, so you can see exactly what the SQL statement is that you are trying to execute. This should make it easier to understand where the syntax error is.
Second, change TRUNC(LAST_DAY(SYSDATE)) = '31-07-2020' to use an explicit date format when converting from date to string. Something like TO_CHAR(TRUNC(LAST_DAY(SYSDATE)), 'DD-MM-YYYY') .... I don't think this is related to your error, but it is better practice than relying on the implicit conversion format.
Third, look carefully at the TO_DATE call. As it is you appear to be calling TO_DATE with date parameter, then implicitly converting that back to a string. At best that's not necessary, and at worst it will cause unexpected behavior. I suspect you may simply mean to use TO_CHAR where you currently have TO_DATE.

Workaround for %ROWTYPE in Object type

I'm new to PL/SQL and trying to create an object with a reference to a table row:
CREATE OR REPLACE TYPE my_object AS OBJECT(
table_row my_table%ROWTYPE
);
The compiler gives me the error message PLS-00329.
Ok, now I know I'm not allowed to reference a table like this. But is there a workaround?
You have to list all of the fields in the object type declaration. You mentioned you're concerned about that being error-prone. You can semi-automate the object creation via a PL/SQL block (or procedure that you pass the table and object name into).
Say you have a table defined as;
CREATE TABLE my_table (
ID NUMBER(38),
col_1 DATE,
col_2 NUMBER(3, 2),
col_3 VARCHAR2(10),
col_4 CLOB
);
You can extract the column names and data types from the data dictionary and build up your object creation statement:
DECLARE
v_stmt VARCHAR2(4000);
BEGIN
v_stmt := 'CREATE OR REPLACE TYPE my_object AS OBJECT(';
FOR r IN (
SELECT column_name,
CAST (data_type || CASE
WHEN data_type IN ('VARCHAR', 'VARCHAR2', 'NVARCHAR2', 'RAW', 'CHAR')
THEN '(' || data_length || ')'
WHEN data_type IN ('NUMBER')
AND (data_precision IS NOT NULL OR data_scale IS NOT NULL)
THEN '(' || data_precision || CASE
WHEN data_scale > 0 THEN ',' || data_scale
END || ')'
END AS VARCHAR2(30)) AS data_type,
CASE WHEN column_id < MAX(column_id) OVER () THEN ',' END AS comma
FROM user_tab_columns
WHERE table_name = 'MY_TABLE'
ORDER BY column_id
)
LOOP
v_stmt := v_stmt || r.column_name || ' ' || r.data_type || r.comma;
END LOOP;
v_stmt := v_stmt || ')';
dbms_output.put_line(v_stmt); -- just for debugging
EXECUTE IMMEDIATE v_stmt;
END;
/
You can extract and use the nullable flag too if you want, but it may not be useful here. (This is adapted from a describe replacement. There may be some data types that aren't handled properly but it covers the common ones; if you have columns with UDTs or other oddities it will need to be extended to include those properly.)
The dbms_output just shows the generated statement to make it easier to debug:
CREATE OR REPLACE TYPE my_object AS OBJECT(ID NUMBER(38),COL_1 DATE,COL_2 NUMBER(3,2),COL_3 VARCHAR2(10),COL_4 CLOB)
PL/SQL procedure successfully completed.
As that statement was also executed the object was created:
desc my_object;
Name Null? Type
----- ----- ------------
ID NUMBER(38)
COL_1 DATE
COL_2 NUMBER(3,2)
COL_3 VARCHAR2(10)
COL_4 CLOB
If you want to add functions you can use the generated create statement as a starting point, and edit it to add whatever you need before running it manually instead of using execute immediate.
Convenient as it sounds, I think there is unlikely ever to be any such syntax for types, as types must present a SQL interface - you have to be able to describe the type and see its attribute list, the attributes need to be published in user_type_attrs, you have to be able to create subtypes and object-relational tables and columns from it, use it in a query and see the column projection etc.
When you create a view using select *, the column list is expanded on creation but does not get rebuilt if you later change the table. So even if Oracle did provide a way to do something similar with types, I suspect they would have to follow the same pattern of simply generating the list on creation and then leaving it to you to maintain, to avoid the complication of managing the dependencies and cascade invalidations, especially given the restrictions around type evolution when types themselves have complex dependencies.

01735. 00000 - "invalid ALTER TABLE option"

I am getting the following error:
00000 - "missing right parenthesis"
when I execute my procedure:
CREATE OR REPLACE PROCEDURE ALTER_TABLE_COLUMN_NOT_NULL(
var_tabname IN VARCHAR2,
var_clname IN VARCHAR2,
var_defvalue IN VARCHAR2 )
IS
l_isnull VARCHAR2(1);
BEGIN
SELECT isnull INTO l_isnull FROM USER_TAB_COLUMNS
WHERE TABLE_NAME = var_tabname AND COLUMN_NAME = var_clname;
IF l_isnull = 'Y' THEN
EXECUTE IMMEDIATE 'ALTER TABLE ' || var_tabname ||
' MODIFY COLUMN (' || var_clname ||
' DEFAULT ' || var_defvalue || ' NOT NULL)';
END IF;
END;
I know that according to the error, the right parenthesis is missing. I tried many ways of rewriting it, but I can't manage to fix it.
I am executing my procedure the following way:
BEGIN
ALTER_TABLE_COLUMN_NOT_NULL('FIRSTNAME', 'PRICE', '-');
END;
Writing dynamic SQL is hard, because compilation errors become runtime errors.
In this case I think the problem is that MODIFY COLUMN is wrong syntax. It's just MODIFY.
You may also run into some problems with your default of '-'. If price is a number that will fail because - is an invalid number. If price is a string you'll need to escape the passed value with additional quotes.
But probably you want to make this generic, so you need to write some more sophisticated handling which tests for datatype of the target column and formats default value appropriately.
"Can u give me a hint or any link how one can determine the datatype of a passed value in plsql?"
It's not the passed value which matters, it's the datatype of the modified column. You can get that from the USER_TAB_COLUMNS view which you're already querying.
Print your query to make sure it written correctly
DBMS_OUTPUT.PUT_LINE('ALTER TABLE ' || var_tabname || ' MODIFY COLUMN (' || var_clname || ' DEFAULT ' || var_defvalue || ' NOT NULL)');

PLS-00103 creating an external table with dynamic SQL

I'm trying to dynamically create an external table but I'm getting error message PLS-00103: Encountered the symbol "EXTERNAL". I'm also using ora_hash in the external table definition; please let me know it is the right way to get the ora_hash value.
create or replace procedure CHECKTABLEEXIST1 (p_tab_name in varchar2,DATAFILE in varchar2) --user_tables.table_name%type)
is
tab_name varchar2(100) := p_tab_name;
n Number(3);
ext_table varchar(100) := tab_name|| ' as select * from xyz WHERE 1=0';
begin
select count(*) into n from tab where TName=upper(tab_name);
--dbms_output.put_line(n);
if n=0 then
execute immediate 'create table ' || ext_table ;
else
execute immediate 'drop table ' || tab_name;
execute immediate 'create table ' || ext_table;
ORGANIZATION EXTERNAL
(
TYPE ORACLE_LOADER
DEFAULT DIRECTORY DE_DUBFILE
ACCESS PARAMETER
(
RECORDS DELIMITED BY NEWLINE
CHARACTERSET US7ASCII
BADFILE 'UPLOAD':'p_tab_name.bad'
DISCARDFILE 'UPLOAD':'p_tab_name.dis'
LOGFILE 'UPLOAD':'p_tab_name.log'
FILEDS TERMINATED BY ','
optionally enclosed by '"'
TRAILING NULLCOLS
MISSING FIELD VALUES ARE NULL
(
t1 ,t2,t3,t4,t5 date "YYYYMMDD" ,t6,t7,
t8 ,t9 ,
DETL_CLMNS_HASH "ORA_HASH( :t4||:t7 )",
KEY_CLMNS_HASH "ORA_HASH(:t1||:t2||:t5)", t10,t11)
)
LOCATION (DATAFILE)
);
end if;
end;
I'm getting error message :
LINE/COL ERROR
-------- -----------------------------------------------------------------
16/14 PLS-00103: Encountered the symbol "EXTERNAL" when expecting one o
f the following:
:= . ( # % ;
Everything from ORGANIZATION onwards is being seen as PL/SQL code, not part of your dynamic SQL statement. You're appending the table name to the create table but then not appending the rest as part of that statement string. You need to do something like:
execute immediate 'create table ' || p_tab_name || '
( /* put column names and types here */ )
ORGANIZATION EXTERNAL
(
TYPE ORACLE_LOADER
DEFAULT DIRECTORY DE_DUBFILE
ACCESS PARAMETERS
(
RECORDS DELIMITED BY NEWLINE
CHARACTERSET US7ASCII
BADFILE UPLOAD:''' || p_tab_name || '.bad''
DISCARDFILE UPLOAD:''' || p_tab_name || '.dis''
LOGFILE UPLOAD:''' || p_tab_name || '.log''
FIELDS TERMINATED BY '',''
optionally enclosed by ''"''
MISSING FIELD VALUES ARE NULL
(
t1 ,t2,t3,t4,t5 date mask "YYYYMMDD" ,t6,t7,
t8 ,t9, t10,t11
)
LOCATION (''' || DATAFILE || ''')
)';
In the first line the terminating semicolon has been replaced with concatenation of a new string literal. The references to variables p_tab_name and DATAFILE have to be broken out from that literal too, requiring more single quotes and concatenation; and the single quotes that are actually part of the statement need to be escaped by doubling them up. There were various other quotes missing as well. What's shown should now run.
I've also change the table name being used to just p_tab_name, but you need to specify the column names and data types explicitly. It doesn't make sense to use as select * ... for an external table. That isn't legal syntax, either before organization or after the rest if the current statement. I suppose you could extract that information from all_tab_columns and build that part dynamically too, but if you're basing it on a fixed table you ought to know those anyway.
Your logic for dropping/creating is off too - I think you just want:
if n>0 then
execute immediate 'drop table ' || p_tab_name;
end if;
execute immediate 'create table ' || p_tab_name || '
...
... so you don't have to repeat the create statement in both branches.
I've also corrected a couple of other mistakes; PARAMETERS rather then PARAMETER; FIELDS rather then FILEDS; removed TRAILING NULLCOLS. Try to execite the command as static SQL before converting it to dynamic. There may still be other issues.
And I've removed the last two calculated columns:
DETL_CLMNS_HASH "ORA_HASH( :t4||:t7 )",
KEY_CLMNS_HASH "ORA_HASH(:t1||:t2||:t5)")
The ORACLE_LOADER driver doesn't allow manipulations like that; SQL*Loader does but they are not exactly the same. You also can't define virtual columns on an external table. If you're using this as a staging table to load data into another (real) table then you can calculate those hashes during the transfer; otherwise you can create a view over this external table which includes the calculated columns.

Resources