Trigger pl/sql insert data on table - sql

i'm new in pl/sql. I'm trying to create a trigger that insert datas in specific tables.
I have datas that arrives in real-time on my table EV_48h. To know on which table I have to insert the data i have to know it Ref_equip (Ref_equip is on an other table named C_Equip).
I've made quickly this littre merise to be more understandable:
merise
As I said I have data that arrives on real-time on the table EV_48H and I have to put them automatically on the tables that are named 'EVV_'+Ref_equip.
So, here is my code. I don't have any error but it don't work. I know i missed of forget something but i don't know what.
TRIGGER "SIVO"."NEWtrigger3EV_48H"
BEFORE INSERT
ON SIVO.EV_48H
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
declare
clef_var number(4,0);
ref_equip varchar2(40);
V_Nom_table varchar2(1000) ;
V_nom_seq Varchar2(2000) ;
stmt varchar2(200);
begin
SELECT clef_var
INTO :New.Clef_Var
FROM sivo.c_variable
WHERE Ref_Var= :new.Ref_Var;
-- Conversion des formats Date-Heure en DateHeure oracle
:New.EV_DATEAUTO := to_date(:New.EV_DATE || ' ' || :New.EV_HEURE, 'DD/MM/YY HH24:MI:SS');
stmt:='begin select clef_var into :New.Clef_Var From sivo.C_variable; end';
EXECUTE IMMEDIATE stmt using out clef_var;
IF clef_var is not null then
stmt :='begin select Ref_equip into :New.Ref_Equip FROM sivo.C_Equip WHERE Ref_var= :New.Ref_Var; end';
EXECUTE IMMEDIATE Stmt USING OUT Ref_Equip;
V_nom_table := 'EVV_'||Ref_Equip;
stmt :='insert into' ||V_nom_table || '(:New.Clef_Var, :New.Ev_DateAuto, :New.Ev_Valeur )';
EXECUTE IMMEDIATE stmt USING Ref_Equip;
ELSE
INSERT INTO SIVO.EV_48H_VAR_INCONNUES (REF_VAR, EV_DATE, EV_HEURE, EV_VALEUR)
VALUES ( :New.REF_VAR, :New.EV_DATE, :New.EV_HEURE, :New.EV_VALEUR);
end if;
END;
If someone can help me or put me on the right way. I don't know if I give all informations so tell me if I missed something.
Thanks

In your execute immediate you are missing the end of statement indicator colon after END
this should be
begin select clef_var into :New.Clef_Var From sivo.C_variable; end;
However there are other design choices that you should be aware of:
using execute immediate is handy but if you don't have to use it you shouldn't. The work can be done by a cursor or even a simple select statement if only one value will come back. In fact it appears you do the work twice. First you insert into the :new.clef_var then you do the same thing again with the execute immediate. Try commenting out the execute immediate.
by using execute immediate any errors are harder to track
using a trigger means the real time data source cannot end the transaction until the trigger completes. Why not run a scheduled job every minute to check for new data and process it? This breaks the transaction into two parts: data entry and data processing
is there any update of records that your process needs to capture?

Related

Using string inside a procedure inside a dynamic SQL inside a cursor declaration

I have a problem with correct pointing a specific parameter for my select inside the cursor.
Here's what I wrote:
create or replace procedure copy_data
is
ds1 varchar2(50) :='string1';
ds2 varchar2(50) :='string2';
seq1 number;
seq2 number;
BEGIN
select NEXT_ID into seq1 from UNIQUE_KEYS where TABLE_NAME='DATA1';
select NEXT_ID into seq2 from UNIQUE_KEYS where TABLE_NAME='DATA2'; -
execute immediate 'CREATE SEQUENCE data1_seq START WITH '||seq1||' INCREMENT BY 1';
execute immediate 'CREATE SEQUENCE data2_seq START WITH '||seq2||' INCREMENT BY 1 CACHE 300';
execute immediate 'CREATE TABLE DA1_IDS (OLD_ID NUMBER(10), NEW_ID NUMBER(10))';
execute immediate
'
Insert into DATA1 (ID,NAME,DESCRIPTION)
select data1_seq.nextval,:ds1,DESCRIPTION
from DATA1 where NAME=:ds2
'
USING ds1, ds2
;
execute immediate
'
DECLARE
v_oldid DATA2.ID%type;
v_newid number;
v_dsfield DATA2%rowtype;
cursor dsc1 is
select dsf.ID, data2_seq.nextval from DATA2 dsf left join DATA1 ds on dsf.DATA1_ID=ds.ID
where ds.NAME='||'string2'||';
cursor dsc2 is
select dsfid.NEW_ID,dsf.FIELD_NAME,dsf.DESCRIPTION,data1_seq.currval
from DATA2 dsf
left join DA1_IDS dsfid on dsf.ID=dsfid.OLD_ID;
begin
open dsc1;
loop
fetch dsc1 into v_oldid,v_newid;
IF dsc1%FOUND THEN
insert into DA1_IDS values (v_oldid,v_newid);
else
exit;
end if;
end loop;
close dsc1;
open dsc2;
loop
fetch dsc2 into v_dsfield;
IF dsc2%FOUND THEN
Insert into DATA2 values v_dsfield;
else
exit;
end if;
end loop;
close dsc2;
END;'
;
END;
And now, the error is that "string2": invalid identifier.
I don't know how to tell my script that there should be a string value there.
Or maybe I just got too far and maybe I should turn everything around?
I used the dynamic SQL for the cursors part because they need to use sequences and the sequences are also created via dynamic SQL, because it's all inside a procedure.
So when using references to sequences in the cursors, I need to hide it inside the dynamic SQL to properly launch it.
But then I don't how to pass a string value inside the select in the cursor.
Please help.
For the immediate error you are getting, you just need to use escpaed single quotes around the string2 literal value; not sure why you have concatenation at the moment but that isn't right. Instead of
where ds.NAME='||'string2'||';
use
where ds.NAME=''string2'';
You could also use a bind variable and pass that literal in, as you do in the first dynamic statement.
I know it's been some time since the original question, but came back to just summarize how it finished. After many iterations, many struggles with the syntax, the script looks something like this:
create or replace procedure copy_data
AUTHID CURRENT_USER
as
ds1 varchar2(50) :='new_label';
ds2 varchar2(50) :='source_label';
dsid varchar2(200);
seq1 number;
seq2 number;
BEGIN
execute immediate 'CREATE TABLE DSID (DSID NUMBER(10))';
dsid := 'insert into DSID (DSID) select ID from DATA1 where NAME= :ds';
execute immediate dsid USING ds2;
select NEXT_ID into seq1 from UNIQUE_KEYS where TABLE_NAME='DATA1';
select NEXT_ID into seq2 from UNIQUE_KEYS where TABLE_NAME='DATA2';
execute immediate 'CREATE SEQUENCE data1_seq START WITH '||seq1||' INCREMENT BY 1';
execute immediate 'CREATE SEQUENCE data2_seq START WITH '||seq2||' INCREMENT BY 1 CACHE 300';
execute immediate 'CREATE TABLE DA1_IDS (OLD_ID NUMBER(10), NEW_ID NUMBER(10))';
execute immediate
'Insert into DATA1 (ID,NAME,DESCRIPTION,...)
select data1_seq.nextval,:ds1,DESCRIPTION,...
from DATA1 where NAME=:ds2' USING ds1, ds2;
execute immediate
'insert into DA1_IDS (OLD_ID, NEW_ID)
select dsf.ID, data2_seq.nextval from DATA2 dsf inner join DSID ds on dsf.DS_ID=ds.DSID';
execute immediate '
DECLARE
v_dsfield DATA2%rowtype;
cursor dsfields2 is
select dsfid.NEW_ID,dsf.FIELD_NAME,dsf.DESCRIPTION,...,data1_seq.currval,...
from DATA2 dsf
inner join DA1_IDS dsfid on dsf.ID=dsfid.OLD_ID
where dsfid.NEW_ID is not NULL;
begin
open dsfields2;
loop
fetch dsfields2 into v_dsfield;
EXIT WHEN dsfields2%NOTFOUND OR dsfields2%NOTFOUND IS NULL;
if dsfields2%ROWCOUNT > 0 THEN
Insert into DATA2 values v_dsfield;
end if;
end loop;
close dsfields2;
END;'
;
In reality it has like 10 cursors, built analogically, they all propagate the same IDs of the same key objects in all related tables, and more related tables can be attached to be filled analogically on the fly with the same related IDs
When I was starting it, the general idea of the topic automatically suggested in my head, that it would be nice to have it as a pretty piece of code, like a pl/sql procedure, with loops(cursors), so I could also learn or practise a few things.
In the following month I wrote a script doing exactly the same thing, but with a plain sql, without any cursors, loops, not even sequences, just simple inserts to tables :)
But still, what I wrote was used a few times, working perfectly, also on the customer's side. So I'm pasting the "pretty" version as a closure :)

How do I fix auto increment in oracle after a data refresh

Every time my team does a data refresh to our UAT environment we have issues with the 'auto incremented' columns in oracle They hold onto the old value and therefore cause errors when a new insert happens. The only solution I have found is to use
select test_SEQ.nextval from test_table;
Until the next sequence is bigger then the max seq number in the table. I have over 200 tables to update, is there an easier why to do this?
Thanks
Erin
One better way to do this would be to drop the sequences and create new ones with the desired START WITH value. You could generate the DDL to do this dynamically.
Check the following sqlfiddle http://sqlfiddle.com/#!4/17345/1
It doesn't completely work due to limitations in sqlfiddle, but here's the function that makes it happen:
create or replace function
reset_sequence(p_sequence_name varchar,
p_table_name varchar,
p_column_name varchar)
return integer is
v_temp integer;
v_sql varchar(2000);
begin
v_sql := 'select nvl(max('||p_column_name||'),0)+1 col_name from '||p_table_name;
execute immediate v_sql INTO v_temp;
v_sql := 'drop sequence '||p_sequence_name;
execute immediate v_sql;
v_sql := 'create sequence '||p_sequence_name||' start with '||v_temp;
execute immediate v_sql;
return v_temp;
end;
Basically you call this function and pass a schema name, table name and column name and it will set the function to the correct value. You can put a begin/exception/end block to ignore errors when dropping the sequence, in case it doesn't exist, but all that is just icing. You could also have it detect the column that is the primary key if you wanted, but no real way in Oracle to detect the sequence name.
You could also make a procedure version of this, but I tend to prefer functions for whatever reason.

'insert into' in 'execute immediate' clause

Can you check this and tell me why I've got an error, please? How should it look? I have no idea what's wrong here. I need to create a table in a function, and in the same function insert data into this table:
create or replace
function new_tab ( pyt IN varchar2) return number
IS
a number;
b varchar2(20);
begin
a:= ROUND(dbms_random.value(1, 3));
b:='example';
-- This works perfect
execute immediate 'CREATE TABLE create_tmp_table'|| a ||'(i VARCHAR2(50))';
-- Here`s the problem
execute immediate 'insert into create_tmp_table'||a|| 'values ('|| b ||')';
exception
when others then
dbms_output.put_line('ERROR-'||SQLERRM);
return 0;
end;
My result is:
ERROR-ORA-00926: missing VALUES keyword. Process exited.
Where is the mistake?
As you are creating a dynamic insert command you have to create it as is. So you are missing the single quotes for the value that is varchar:
execute immediate
'insert into create_tmp_table'||a|| ' values ('''|| b ||''');';
^here ^and here
And a good suggestion for this type of error is to do some debug. On your case you could create a varchar2 variable and put your insert on it then you use the dbms_output.put_line to this variable. Then you will have your sql command that you can test direct on your database. Something like:
declare
sqlCommand varchar2(1000);
--define a and b
begin
--put some values in a and b
sqlCommand := 'insert into create_tmp_table'||a|| ' values ('''|| b ||''');';
dbms_output.put_line(sqlCommand);
end;
Then you will know what is wrong with the dynamic command.
execute immediate 'insert into TABLE'||a||' (COLUMN_NAME) values (:val)' using b;
This way you don't have to bother with escaping your values (SQL-injection hack) and correctly casting the type.
http://docs.oracle.com/cd/B13789_01/appdev.101/b10807/13_elems017.htm

SQL - Oracle Functions using variables for schema names

I am writing some oracle stored procedures where we have conditional logic which effects which schema we are working from and I am not sure how to do this in the sql for the stored proc. If I am working with prepared statements then its fine but in the scenario where I am just executing a query to say populate another variable then I dont know how to do this. For example
PROCEDURE register (
incustomer_ref in VARCHAR2,
incustomer_type in VARCHAR2,
outreturn_code out VARCHAR2
)
IS
customer_schema varchar2(30);
record_exists number(1);
BEGIN
if incustomer_type='a' then
customer_schema:='schemaA';
elsif incustomer_type='b' then
customer_schema:='schemaB';
end if;
--This type of command I cant get to work
select count(*) into record_exists from **customer_schema**.customer_registration where customer_ref=incustomer_ref
--but a statement like this i know how to do
if record_exists = 0 then
execute immediate 'insert into '||customer_schema||'.customer_registration
values ('||incustomer_ref||','Y',sysdate)';
end if;
Can anyone shine some light on what I am missing here.
Cheers
you can use execute immediate also for select statment:
execute immediate 'select count(*) from '|| customer_schema
|| '.customer_registration where customer_ref= :b1'
into record_exists using incustomer_ref;

is there any way to log all failed sql statements in oracle 10g

is there any way to log all failed sql statements in oracle 10g to a table or file?
By failed I mean bad formated sql statement or sql statements that do not have permission for a table or object.
You may want to use Auditing like:
AUDIT SELECT TABLE, INSERT TABLE, DELETE TABLE, EXECUTE PROCEDURE
BY ACCESS
WHENEVER NOT SUCCESSFUL;
By ACCESS is for each statement (which seems like what you want). By SESSION would record one record per session (high volume environment).
Oracle's built in auditing has less overhead then a trigger. A trigger, which other answers contain, allows you to log the exact information you want. Auditing will also only catch hits on existing objects. If someone selects on a non-existent table (misspelled or whatnot) auditing will not catch it. The triggers above will.
A lot more info in the security guide: http://download.oracle.com/docs/cd/B19306_01/network.102/b14266/auditing.htm#i1011984
Rather than hit the system views, as in Demge's answer, there is an ora_sql_txt function that gives the relevant statement.
create or replace TRIGGER log_err after servererror on schema
DECLARE
v_stack VARCHAR2(2000) := substr(dbms_utility.format_error_stack,1,2000);
v_back VARCHAR2(2000);-- := substr(dbms_utility.format_error_backtrace,1,2000);
v_num NUMBER;
v_sql_text ora_name_list_t;
procedure track(p_text in varchar2) is
begin
insert into .... values (p_text);
end;
begin
v_stack := translate(v_stack,'''','"');
track(v_stack);
v_back := translate(v_back,'''','"');
if v_back is not null then track(v_back); end if;
v_num := ora_sql_txt(v_sql_text);
BEGIN
FOR i IN 1..v_num LOOP
track(to_char(i,'0000')||':'||v_sql_text(i));
END LOOP;
EXCEPTION
WHEN VALUE_ERROR THEN NULL;
END;
end;
In my own environment, I actually have 'TRACK' as a separate procedure that uses an autonomous transaction, rather than a block as above.
create or replace procedure track (p_text IN VARCHAR2) IS
PRAGMA AUTONOMOUS_TRANSACTION;
cursor c_user is
select sys_context('USERENV','CLIENT_INFO') client_info,
sys_context('USERENV','CURRENT_SCHEMA') curr_schema,
sys_context('USERENV','CURRENT_USER') curr_user,
sys_context('USERENV','DB_NAME') db_name,
sys_context('USERENV','HOST') host,
sys_context('USERENV','IP_ADDRESS') ip,
sys_context('USERENV','OS_USER') osuser,
sys_context('USERENV','SESSIONID') sessid,
sys_context('USERENV','SESSION_USER') sess_user,
sys_context('USERENV','TERMINAL') terminal
from dual;
user_rec c_user%rowtype;
v_mod VARCHAR2(48);
v_act VARCHAR2(32);
v_cli_info varchar2(64);
begin
open c_user;
fetch c_user into user_rec;
close c_user;
DBMS_APPLICATION_INFO.READ_MODULE (v_mod, v_act);
--DBMS_APPLICATION_INFO.READ_CLIENT_INFO(v_cli_info);
insert into track_detail
(id, track_time, detail, client_info, curr_schema, curr_user, db_name,
host, ip, osuser, sessid, sess_user, terminal, module, action)
values (track_seq.nextval, systimestamp, p_text,
user_rec.client_info, user_rec.curr_schema, user_rec.curr_user,
user_rec.db_name, user_rec.host, user_rec.ip,
user_rec.osuser, user_rec.sessid, user_rec.sess_user,
user_rec.terminal, v_mod, v_act);
commit;
end;
You can do this with a system trigger.
I directly copied this code from http://www.psoug.org/reference/system_trigger.html.
CREATE TABLE servererror_log (
error_datetime TIMESTAMP,
error_user VARCHAR2(30),
db_name VARCHAR2(9),
error_stack VARCHAR2(2000),
captured_sql VARCHAR2(1000));
CREATE OR REPLACE TRIGGER log_server_errors
AFTER SERVERERROR
ON DATABASE
DECLARE
captured_sql VARCHAR2(1000);
BEGIN
SELECT q.sql_text
INTO captured_sql
FROM gv$sql q, gv$sql_cursor c, gv$session s
WHERE s.audsid = audsid
AND s.prev_sql_addr = q.address
AND q.address = c.parent_handle;
INSERT INTO servererror_log
(error_datetime, error_user, db_name,
error_stack, captured_sql)
VALUES
(systimestamp, sys.login_user, sys.database_name,
dbms_utility.format_error_stack, captured_sql);
END log_server_errors;
/