Creating external tables in Oracle procedure - oracle

I'm using Oracle 11g and I'm having in issue with creating an external table in a procedure. It gets created with no errors but when I execute the procedure I'm having errors.
The first parameter is the name of the file and the second is a comma because I was having issues with using single quotations to surround the comma where I specify the fields terminated by section. DATA_DIR was declared.
Here's what I tried.
CREATE OR REPLACE PROCEDURE LOADTABLE
(
FILENAME VARCHAR2,
COMMA VARCHAR
)
AS
BEGIN
EXECUTE IMMEDIATE 'CREATE TABLE LOAD
(
USERNAME VARCHAR2(30)
)
ORGANIZATION EXTERNAL
(
TYPE ORACLE_LOADER
DEFAULT DIRECTORY DATA_DIR
ACCESS PARAMETERS
( FIELDS TERMINATED BY :COMMA)
LOCATION (:FILENAME)
)' USING IN COMMA, FILENAME;
END;
This is how I call the procedure
EXEC LOADTABLE('username.csv',',');
This is the error I get
ERROR at line 1:
ORA-00931: missing identifier
ORA-06512: at "DATA_ADMIN.LOADTABLE", line 9
ORA-06512: at line 1
Any help will be appreciated.

You can only bind variables, and the external table creation syntax requires text literals for the elements you're trying to bind.
You'll have to use concatenation instead:
CREATE OR REPLACE PROCEDURE LOADTABLE
(
FILENAME VARCHAR2,
COMMA VARCHAR
)
AS
BEGIN
EXECUTE IMMEDIATE 'CREATE TABLE LOAD
(
USERNAME VARCHAR2(30)
)
ORGANIZATION EXTERNAL
(
TYPE ORACLE_LOADER
DEFAULT DIRECTORY DATA_DIR
ACCESS PARAMETERS
(FIELDS TERMINATED BY ''' || COMMA || ''')
LOCATION (''' || FILENAME || ''')
)';
END;
/
Procedure LOADTABLE compiled
EXEC LOADTABLE('username.csv',',');
PL/SQL procedure successfully completed.
In that I've escaped the single quotes around the concatenated string values. In the question you mentioned that you're only passing the comma because you were "having issues with using single quotations to surround the comma"; escaping them by doubling them up is the way to do that, so if you do always want a comma separator you can instead do:
CREATE OR REPLACE PROCEDURE LOADTABLE
(
FILENAME VARCHAR2
)
AS
BEGIN
EXECUTE IMMEDIATE 'CREATE TABLE LOAD
(
USERNAME VARCHAR2(30)
)
ORGANIZATION EXTERNAL
(
TYPE ORACLE_LOADER
DEFAULT DIRECTORY DATA_DIR
ACCESS PARAMETERS
(FIELDS TERMINATED BY '','')
LOCATION (''' || FILENAME || ''')
)';
END;
/
EXEC LOADTABLE('username.csv');
However, creating (and presumably dropping) objects on the fly isn't generally a good idea. It would be better to create the external table once, which would be done with static DDL:
CREATE TABLE LOAD
(
USERNAME VARCHAR2(30)
)
ORGANIZATION EXTERNAL
(
TYPE ORACLE_LOADER
DEFAULT DIRECTORY DATA_DIR
ACCESS PARAMETERS
(FIELDS TERMINATED BY ',')
LOCATION ('dummy')
);
and then just alter the table to have a new file name statically too:
alter table load location ('username.csv');
or if you really want a procedure to do it:
CREATE OR REPLACE PROCEDURE LOADTABLE
(
FILENAME VARCHAR2
)
AS
BEGIN
EXECUTE IMMEDIATE 'ALTER TABLE LOAD LOCATION (''' || FILENAME || ''')';
END;
/
EXEC LOADTABLE('username.csv');

Related

load when (1:5) in select with external inline view asks for bind variables

I don't find the switch to turn the binds of in SQL Developer:
I've created an csv file:
declare
v_file utl_file.file_type;
begin
v_file := utl_file.fopen('DATA_FILE_DIR', 'example2.csv', 'W');
utl_file.put_line(v_file, 'colid|colstring|colnumber|coldate');
utl_file.put_line(v_file, '1|"nothing"|231.12|2019-01-03 23:43:32');
utl_file.put_line(v_file, '2|"not more"|121|2020-10-05 14:33:15');
utl_file.FFLUSH(v_file);
utl_file.fclose(v_file);
end;
Which I want to read with an inline select:
select c_001,c_002, c_003, c_004 from external
(( c_001 varchar2(200)
, c_002 varchar2(200)
, c_003 varchar2(200)
, c_004 varchar2(200)
)
TYPE oracle_loader
default directory DATA_FILE_DIR
access parameters (
RECORDS DELIMITED BY newline
load when (1:5) != 'colid'
LOGFILE DATA_FILE_DIR:'inline_ext_tab_%a_%p.log'
FIELDS CSV WITH EMBEDDED TERMINATED BY '|' OPTIONALLY ENCLOSED BY '"'
MISSING FIELD VALUES ARE NULL(
c_001
, c_002
, c_003
, c_004
)
)
location ('example2.csv')
reject limit unlimited
)
Issue I get is load when (1:5) != 'colid' is asking for Bind substitution.
set define off;
set escape off;
didn't help in my case. Any ideas to prevent this behavior?
Using skip 1 is because of Bug 29420254 no option.
Regression Bug 29420254 with load when text != 'xxxxxxxxxxxxxx' instead of skip 1:
ERROR at line 1:
ORA-29913: error in executing ODCIEXTTABLEFETCH callout
ORA-30653: reject limit reached
select filename,text
from external(( FILENAME VARCHAR2(255 CHAR),text VARCHAR2(10 CHAR))
TYPE ORACLE_LOADER
DEFAULT DIRECTORY "DATA_FILE_DIR"
ACCESS PARAMETERS
( RECORDS DELIMITED BY newline
preprocessor DATA_FILE_DIR:'add_filename_semikolon.sh'
load when text != 'xxxxxxxxxxxxxx'
FIELDS TERMINATED BY ';' OPTIONALLY ENCLOSED BY '"'
MISSING FIELD VALUES ARE NULL
(
FILENAME CHAR(255),
text CHAR (10)))
LOCATION
( 'Testfall_s1.csv')
);
Testfall_s1.csv:
"xxxxxxxxxxxxxx"
"abcd"
add_filename_semikolon.sh
#!/bin/bash
# Separator: ;
FILENAME='"'${1##*/}'"'
/usr/bin/sed -e 's/^/'$FILENAME';/' $1
But when reevaluating the bug (where patches are available, but don't want to patch every VM ...) I've missed a important requirement preprocessor which I currently don't use ...

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.

how to use "like" command in below query

I am importing text file through SQL*Loader into an Oracle table but i don't want to give the specific name of the file, I want to import only the .txt file extensions file.
look the below code :
create or replace
PROCEDURE EXT_TABLE
AS
A1 NUMBER ;
L_QUERY VARCHAR2(1000) := NULL;
L_DROP VARCHAR2(10000) := NULL;
BEGIN
EXECUTE IMMEDIATE 'CREATE TABLE IMPORT_TEST
( EMP_ID NUMBER (10)
)
ORGANIZATION EXTERNAL
( TYPE ORACLE_LOADER
DEFAULT DIRECTORY IMPORT
ACCESS PARAMETERS
( RECORDS DELIMITED BY NEWLINE
FIELDS TERMINATED BY '',''
MISSING FIELD VALUES ARE NULL
)
LOCATION ('file with .txt extension')
)reject limit unlimited';
L_QUERY:= 'INSERT INTO MPRN SELECT * FROM IMPORT_TEST';
EXECUTE IMMEDIATE L_QUERY;
L_DROP := 'drop table IMPORT_TEST ';
execute immediate L_DROP;
--execute immediate 'DROP IMPORT_TEST';
commit;
END EXT_TABLE;
At the location, LOCATION ('file with .txt extension'), I don't want to give the name of the file as in the directory only one txt file is there. I don't want to use the IN parameter. I want to search from the directory only. The user will run the procedure and it will import the txt file automatically without selecting manually.
For the most part you aren't going to be able to do this in a pure PL/SQL fashion. There is a workaround listed here: Listing files in a specified directory using PL/SQL but considering the requirement for SYS that may not be exactly what you are looking for. After that a Java Stored Procedure would be your best bet.
If you are able to determine the filename, you can redefine the location for your external table on the fly with an execute immediate call. You could put it in a procedure like this and make use of it before querying your external table:
procedure alterExtTableFileName(a_tableName varchar2, a_filename varchar2) is
pragma autonomous_transaction;
begin
dbms_output.put_line('alterExtTableFileName(TableName=' || a_tableName || ' FileName=' || a_filename || ')');
execute immediate 'alter table ' || a_tableName || ' LOCATION (''' || a_filename || ''')';
commit;
exception when others then
rollback;
raise;
end alterExtTableFileName;

Execute Immediate Alter User Bind Variable

The following procedure:
create or replace
PROCEDURE ChangePassword
(
p_Name VARCHAR2,
p_Password VARCHAR2
)
AS
EXECUTE IMMEDIATE 'ALTER USER :a IDENTIFIED BY :b' USING p_Name, p_Password;
END;
compiles successfuly, but when executed:
exec ChangePassword('TestUser', 'newPassword');
results in error:
01935. 00000 - "missing user or role name"
*Cause: A user or role name was expected.
*Action: Specify a user or role name.
Why?
You can't use bind variables in place of identifiers, such as the user name in this statement. The value of the identifier needs to be known when the statement is parsed, whereas a bind value is incorporated after parsing, before execution.
I think that the following would work
create or replace PROCEDURE ChangePassword(p_Name IN VARCHAR2,
p_Old_password IN VARCHAR2,
p_New_password IN VARCHAR2)
AS
EXECUTE IMMEDIATE 'ALTER USER ' || p_name ||
' IDENTIFIED BY ' || p_New_password ||
' REPLACE ' || p_Old_password;
END;
Share and enjoy.

Searching data from a table using stored procedure in oracle by passing tablename as a parameter

This procedure is not working properly.
create or replace procedure bank_search_sp
(
p_tablename in varchar2,
p_searchname in varchar2,
p_bankcode out varchar2,
p_bankname out varchar2,
p_dist_code out number
)
as
v_tem varchar2(5000);
begin
v_tem :='select bankcode,bankname,dist_code from ' || UPPER (p_tablename) || '
where bankname like '''|| p_searchname||'';
execute immediate v_tem into p_bankcode,p_bankname,p_dist_code using p_searchname ;
commit;
end bank_search_sp;
the Procedure is getting created but i dont know what actually happens when it was executed ,This is the error shown
ORA-01756: quoted string not properly terminated
ORA-06512: at "PENSIONS.BANK_SEARCH_SP", line 14
ORA-06512: at line 1
Trailing commas are missing from this line:
v_tem :='select bankcode,bankname,dist_code from ' || UPPER (p_tablename) || '
where bankname like '''|| p_searchname||'''';
Notice the 4 commas at the end of the string fragment. The first opens the string, the last closes it, the middle two insert a single comma that will be the closing comma for the search expression).
The UPPER() function is not necessary; Oracle does not care the casing of the object names.
I am not sure if having multiple tables with the same structure is the best solution. Would not it be better to have one table only with an indexed column indicating the difference between banks?

Resources