Hung up on For In Loop and variable for external table loader - oracle

I have to load a number of files every day into our database system. My solution was to use a java procedure to generate a table of all the files in the directory folder and loop through each of them through the external table loader. I'm running into two hangups with this
Declare
what_to_load VARCHAR2(255);
CURSOR folder_contents
IS
select filename
from database.DIR_LIST
where filename like 'DCOpenOrders_%'
and filename like '%.csv';
BEGIN
DELETE FROM database.DIR_LIST;
database.GET_DIR_LIST( 'directory_path_files_are_in' );
FOR each_record IN folder_contents
LOOP
what_to_load := each_record.filename;
EXECUTE IMMEDIATE 'DROP table database.my_table';
execute immediate 'CREATE table database.my_table
(Region VARCHAR2(10),
District VARCHAR2(10),
Originating_Store VARCHAR2(80),
Order_Date VARCHAR2(30),
Ship_Location VARCHAR2(10),
Orig_Ord_No VARCHAR2(30),
Field_G VARCHAR2(30),
Line_No VARCHAR2(10),
POS_UPC VARCHAR2(30),
Item_Descr VARCHAR2(80),
Ord_Qty VARCHAR2(10),
Line_Status VARCHAR2(30),
Report_Date VARCHAR2(30),
Ship_Type VARCHAR2(30),
ERR_FLAG VARCHAR2(10),
ERR_LOG VARCHAR2(800)
)
ORGANIZATION EXTERNAL
( type oracle_loader
default directory WORK_DIR
access parameters
( records delimited by NEWLINE
skip 1
fields terminated by '',''
optionally enclosed by ''"''
missing FIELD VALUES are NULL)
location ('''||each_record.filename||''')
)
reject limit unlimited';
Execute Immediate 'Grant All on database.my_table to USER';
* merge statement goes here*
End Loop;
commit;
end;
Again, the idea is that every time this runs it will get the new list of csv files in the dir_list table with the java procedure get_dir_list, then for every file name I set as equal to the variable and use the variable in the external table loader to load up the file.
I'm running into [s]two[/s] problems
EDIT: Ok, making the corrections below to cursor row identification, now I hit the point where when I go to the second pass through my cursor appears to be wrong or missing - it will go through a loop just fine if the only action is to do a put_line. But with an execute immediate statement in there such as the "Grant All" then as soon as it completes one pass it throws ORA-08103 at the top of the loop and refuses to go on
3) I'm aware of an ask tom on this (https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:37593123416931) that says to use the alter table command. However when I try that it doesn't accept my attempt at that
execute immediate 'alter table database.my_table location('''||filename||''')';
throws out an error (plus I'd still need to get it to do another loop there to put the name of the current file into the external loader)
Any suggestions or help? I should note that we are on windows, not unix (since most solutions people offer on these places assume the latter) and I can't grab another program or module to do the job due to approval restrictions (since that seems to be another common solution)
Thanks!

For your first problem, your cursor loop variable is confusingly called filename. Your are referring to that record directly, instead of the column from the cursor. Changing the name slightly to make it a little clearer:
FOR filenames IN folder_contents
LOOP
what_to_load := filesnames.filename;
The rest is less obvious, but it isn't going to be happy that you're dropping and recreating the table in the middle of a block that refers to it statically. You need to make all references dynamic:
execute immediate 'Grant All on database.my_table ...';
-- grant to who/what? and why?
And your merge will have to be dynamic too. At least unless you can get the alter table to work, but you haven't said what the problem is with that. Actually, from what you posted, that's the same cursor variable reference problem:
execute immediate 'alter table database.my_table location('''||filenames.filename||''')';
If you aren't dropping/creating the table in the block, and create it once statically and just alter it, then you can use a static merge - just the alter needs to be dynamic.
A simpler approach might be to create the external table once, with a specific fixed name; loop through the list of real files; and for each of those in turn, rename or copy that to the fixed file name and perform the merge. Each time you query the external table it rereads the file anyway, so changing its contents in the background is OK. Dropping/recreating or even altering the table then wouldn't be necessary.
You could also, as that Ask Tom pst mentions, supply all the file names to the external table at once, as they have the same structure, either with the drop/create or with the alter approach.

Related

How can I use my generated triggeres from sqlplus console into the oracle forms bulider?

I've created the following table:
create table Citizens_lic
(
No NUMBER(10) ,
ID NUMBER(10)
constraint Citizens_ID_pk_1 PRIMARY KEY,
F_Name VARCHAR2(32) ,
M_Name VARCHAR2(32) ,
L_Name VARCHAR2(32) ,
DOB DATE ,
POF VARCHAR2(32) ,
GENDER VARCHAR2(32) ,
Soc_status VARCHAR2(32) ,
work_status VARCHAR2(32) ,
ISS_DATE date ,
EXP_Date date
)
Then, I've generated some triggers
for id, no, iss_date Exp_date
from the sqlplus command as like the following pics
after that, all triggers work fine from the sqlplus command, all inserted values like id and no columns are generated itself automatically once a row is created
Now I want to show you where I got stuck!
I went to the oracle forms builder
I've made this form
and deleted all the ID, No, Iss_date, Exp_date item boxes because there's no need for it, each one must be already generated by a trigger.
then I ran it
unable to insert!
Now lets get it over the flow, anyone help :)
Part of the problem is that you have written four triggers for one event (well, strictly three triggers, because you have two scripts with the same trigger name but I assume that is just a cut'n'paste bloomer and you really intended the fourth script to create a trigger called citizens_lic_trigg_4). Another part of the problem is that you have two triggers populating :new.no and no trigger populating :new.id, which being the primary must be populated.
Four triggers firing on insert causes four times as much overhead as one trigger firing. So it's better to have just one trigger, for performance reasons. But it also makes it easier to avoid the errors in your code, because scanning one script is simpler than scanning four. Particularly when you're just editing the cached statement (ed afiedt.buf) so you can't eyeball all four scripts (*).
So, a better implementation would be:
create or replace trigger citizens_lic_trigg
before insert on citizens_lic
for each row
begin
/* or maybe these two assignments should be the other way round??? */
:new.id := citizens_lic_seq_1.nextval;
:new.no := round(dbms_random.value(1000500000,1099999999));
:new.iss_date := sysdate;
:new.exp_date := sysdate + (365*5);
end;
(*) Unless you take a screenshot after each edit, as you have done here. But that's really inefficient: in the long run you will find it beneficial to have separate named files for each script, so you can save them in source control.
two thoughts: both your triggers _1 and _2 insert into new.no. in your second screenshot you twice create the trigger _3. I'd say your problem is that you provide no value for your PK, the id. HTH

PL/SQL Stored Procedure create tables

I've been tasked with improving old PL/SQL and Oracle SQL legacy code. In all there are around 7000 lines of code! One aspect of the existing code that really surprises me is the previous coder needlessly created hundreds of lines of code by not writing any procedures or functions - instead the coder essentially repeats the same code throughout.
For example, in the existing code there are literally 40 or more repetitions of the following SQL:
CREATE TABLE tmp_clients
AS
SELECT * FROM live.clients;
CREATE TABLE tmp_customers
AS
SELECT * FROM live.customers;
CREATE TABLE tmp_suppliers
AS
SELECT * FROM live.suppliers WHERE type_id = 1;
and many, many more.....
I'm very new to writing in PL/SQL, though I have recently purchased the excellent book "Oracle PL/SQL programming" by Steven Feuerstein. However, as far as I can tell, I should be able to write a callable procedure such as:
procedure create_temp_table (new_table_nme in varchar(60)
source_table in varchar(60))
IS
s_query varchar2(100);
BEGIN
s_query := 'CREATE TABLE ' + new_table_nme + 'AS SELECT * FROM ' + source_table;
execute immediate s_query;
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE = -955 THEN
NULL;
ELSE
RAISE;
END IF;
END;
I would then simply call the procedure as follows:
create_temp_table('tmp.clients', 'live.clients');
create_temp_table('tmp.customers', 'live.customers');
Is my proposed approach reasonable given the problem as stated?
Are the datatypes in the procedure call reasonable, ie should varchar2(60) be used, or is it possible to force the 'source_table' parameter to be a table name in the schema? What happens if the table name is more than 60 characters?
I want to be able to pass a third non-required parameter in cases where the data has to be restricted in a trivial way, ie to deal with cases "WHERE type_id = 1". How do I modify the procedure to include a parameter that is only used occasionally and how would I modify the rest of the code. I would probably add some sort of IF/ELSE statement to check whether the third parameter was not NULL and then construct the s_query accordingly.
How would I check that the table has actually been created successfully?
I want to trap for two other exceptions, namely
The new table (eg 'tmp.clients') already exists; and
The source table doesn't exist.
Does the EXCEPTION as written handle these cases?
More generally, from where can I obtain the SQL error codes and their meanings?
Any suggested improvements to the code would be gratefully received.
You could get rid of a lot of code (gradually!) by using GLOBAL temporary tables.
Execute immediate is not a bad practice but if there are other options then they should be used. Global temp tables are common where you want to extract and transform data but once processed you don't need it anymore until the next load. Each user can only see the data they insert and no redo logs are generated. You can index the data for faster querying if required.
Something like this
-- Create table
create global temporary table GT_CLIENTS
(
id NUMBER(10) not null,
Client_id NUMBER(10) not null,
modified_by_id NUMBER(10),
transaction_id NUMBER(10),
local_transaction_id VARCHAR2(30) not null,
last_modified_date_tz TIMESTAMP(6) WITH TIME ZONE not null
)
on commit preserve rows;
I recommend the on commit preserve rows option so that you can debug your procedure and see what went into the table.
Usage would be
INSERT INTO GT_CLIENTS
SELECT * FROM live.clients;
If this is the route you want to take to minimize changes, then the error for source table does not exist is -942 which you will want to stop for rather than continuing as your temp table would not have been created. Similarly, just continuing if you get an object already exists error will be problematic as you will not have reloaded it with the new data - the create failed so the table still has the data from the last run. So I would definitely do some more thinking about your exception handler.
That said, I also concur that this is generally not the best way to do things. Creating and dropping objects in a multi-user environment is a disaster in the making, and seems a silly waste of resources when there are more appropriate options available.

Oracle: Dynamically create package scripts without writing to a file

I have built my own repository of tables in my oracle database.
I use this repository to create standardazied packages per table.
Up to now I write a bunch of scripts into a utitlity folder.
I don't want to go that extra way though the file system anymore.
This is an example file:
CREATE OR REPLACE PACKAGE vpk_0003_produkte
AS
TYPE t_cursor IS REF CURSOR;
PROCEDURE p_insert (p_rc OUT NUMBER,
p_rc_text OUT VARCHAR2,
p_logsql IN NUMBER,
p_logperformance IN NUMBER,
p_user_id IN NUMBER,
p_mandant IN NUMBER,
-- PK Columns
p_id IN OUT x_0003_produkte.id%TYPE
-- Other Columns
, p_requestid IN x_0003_produkte.requestid%TYPE);
PROCEDURE p_update (p_rc OUT NUMBER,
p_rc_text OUT VARCHAR2,
p_logsql IN NUMBER,
p_logperformance IN NUMBER,
p_user_id IN NUMBER,
p_mandant IN NUMBER,
p_where IN VARCHAR2,
-- PK Columns
p_id IN OUT x_0003_produkte.id%TYPE
-- Other Columns
, p_requestid IN x_0003_produkte.requestid%TYPE);
END vpk_0003_produkte;
Is there a way to execute these lines directly without writing a file?
I know of "OPEN CURSOR FOR" in conjunction with a variable that contains a SELECT to get data. But how to execute/write?
ADDITIONAL INFO: One package code easily has 27.000 characters! and around 1000 lines.
Thanks.
If you can assemble the entire source of the package DDL into a single VARCHAR2(32000) local variable, you should be able to use EXECUTE IMMEDIATE to run the DDL
EXECUTE IMMEDIATE l_your_ddl_stmt;
Depending on the Oracle version, you may be able to call EXECUTE IMMEDIATE with a CLOB if the DDL statement might exceed 32000 bytes. You can also use dbms_sql passing in a collection of VARCHAR2(4000) strings that are combined into the DDL that you want to execute.
Assuming that you are checking your generated code into source control, though, rather than just checking in the code that generates the package, it may well be easier to generate the files on the file system as an intermediate step since that allows you to both commit to source control and to create the object in the database.

Double quotes inside single quotes in oracle statement

I'm having the following problem with an old database. We migrated from sql to oracle recently and I'm having trouble with an insert statement where the column name is "default". Any ideas (I'm not allowed to change the column name, that would be by far the best solution!)?
It looks somehow like this, only with a billion more columns and it's inside a large if-when-else construction for validating issues, so I can't drop the execute immediate.
EXECUTE IMMEDIATE 'INSERT INTO trytable (ID, "DEFAULT") VALUES (''monkey1'', 0)'
I don't think the problem comes from your column name, as shown in this working example:
SQL> CREATE TABLE trytable (ID VARCHAR2(10), "DEFAULT" NUMBER);
Table created
SQL> BEGIN
2 EXECUTE IMMEDIATE
3 'INSERT INTO trytable (ID, "DEFAULT") VALUES (''monkey1'', 0)';
4 END;
5 /
PL/SQL procedure successfully completed
Technically, you can have a table column name named DEFAULT, even if it's generally a bad idea that will lead to confusion. You will only be able to interact with it through the double-quote " syntax because DEFAULT is a reserved word.
If you specify double quotes around identifiers, Oracle will treat them as case-sensitive, so you have to make sure that they match the table specs.
In your case, it would help to have the specific error message.
The below will executes if your column name is "DEFAULT":
BEGIN
EXECUTE IMMEDIATE 'INSERT INTO TRYTABLE(ID, "DEFAULT")VALUES(''monkey1'',0)';
END;
"DEFAULT" and "default" makes difference.

Creating table before creating a cursor in Oracle

I have a PL/SQL procedure which creates a temporary table and then extracts the data from this temporary table using cursors, processes the data and then drops the temporary table. However Oracle doesn't allow the usage of cursor if the table doesn't exist in the database.
Please help me handle this.
Your statement is not quite correct. You can use a cursor for pretty much arbitrary queries. See below:
create or replace procedure fooproc
IS
type acursor is ref cursor;
mycur acursor;
mydate date;
BEGIN
execute immediate 'create global temporary table footmp (bar date) on commit delete rows';
execute immediate 'insert into footmp values (SYSDATE)';
open mycur for 'select * from footmp';
loop
fetch mycur into mydate;
exit when mycur%notfound;
dbms_output.put_line(mydate);
end loop;
close mycur;
execute immediate 'drop table footmp';
END fooproc;
/
(More details here - especially this short proc is not safe at all since the table name is fixed and not session-dependent).
It is (quite) a bit ugly, and I'm not suggesting you use that - rather, you should be thinking whether you need that procedure-specific temporary table at all.
See this other article:
DO NOT dynamically create them [temp tables], DO NOT dynamically create them, please -- do NOT dynamically create them.
Couldn't you use a global temporary table? Do you actually need a temporary table at all? (i.e. doesn't using a cursor on the select statement you'd use to fill that table work?)
Or, if you wish to avoid differences between global temporary tables and "regular" permanent tables you may be used to (see Oracle docs on temp table data availability, lifetime etc), simply create the table first (nologging). Assuming nobody else is using this table, your procedure could truncate before/after your processing.

Resources