I have an Interactive Grid on my page, the idea is that every time the user inserts / updates rows an email will be send to the Admins for audit checks (manual back-office process).
This is the code (Trigger) I have so far for the insert, however, it is not the best option, since it only captures the last - new - Id:
create or replace trigger "my_email_trigger"
after insert on "my_table"
for each row
DECLARE
l_body CLOB;
v_user varchar2(300);
V_REQUESTER varchar2(300);
v_max number;
begin
l_body := 'Hi ' || INITCAP(replace(regexp_replace(:NEW."User_email",'#[a-zA-z0-9.]*',''),
'.', ' ')) ||',' || utl_tcp.crlf;
l_body := l_body || utl_tcp.crlf;
l_body := l_body || 'Please review new Row ID: '|| :NEW."ID" ||utl_tcp.crlf;
l_body := l_body || utl_tcp.crlf;
l_body := l_body || utl_tcp.crlf;
l_body := l_body || 'Thank you' || utl_tcp.crlf;
apex_mail.send(
p_to => :NEW."User_email",
p_from => 'noreply#mail.com',
p_body => l_body,
p_subj => 'Please review new Row Id' );
END;
If the user adds 3 new rows, this trigger would send 3 different emails.
Is there a way to capture all 3 inserted rows (for example) on a single email?
Thanks!
Instead of using a trigger on the table - a trigger will fire for every affected row and that will not work - I suggest you use a page process.
Check this post which is pretty similar. Note that the page process is also fired for every row so you'll probably need 2 page processes
One page process on the IG Editable region to populate an item with the list of affected row ids. Since this is executed for every affected row you could just concatenate values and this process cannot send the email because you'd have the same issue as the trigger: one mail per row.
Another page process that just sends the email if the page item from the first process is not null.
Another advantage over trigger is that this keeps you business functionality in the application code. If you move that to triggers isn't that visible anymore.
Related
I have a trigger that calls a webservice link.
This link read a View, and that view is composing a XML.
The problema is:
The trigger is executed when I have a cod_situation = 6 like this:
CREATE OR REPLACE TRIGGER trg_candidato_chama_link
AFTER INSERT OR UPDATE ON cand_proc_sel
FOR EACH ROW DECLARE
v_url VARCHAR2(4000);
req UTL_HTTP.REQ;
resp UTL_HTTP.RESP;
value VARCHAR2(1536);
--temp
v_count INTEGER;
v_alternativas VARCHAR2(1000);
v_error VARCHAR2(4000);
BEGIN
if (:new.cod_situation = 6 ) THEN
v_url := 'http://.../frameweb/amxv7/amx_new_employee';
req := UTL_HTTP.BEGIN_REQUEST(v_url);
UTL_HTTP.SET_HEADER(req, 'User-Agent', 'Mozilla/4.0');
resp := UTL_HTTP.GET_RESPONSE(req);
LOOP
UTL_HTTP.READ_LINE(resp, value, TRUE);
DBMS_OUTPUT.PUT_LINE(value);
END LOOP;
UTL_HTTP.END_RESPONSE(resp);
END IF;
EXCEPTION WHEN UTL_HTTP.END_OF_BODY THEN
UTL_HTTP.END_RESPONSE(resp);
INSERT INTO integratio_log(data, cod_integracao, status, rotina, obs) VALUES(SYSDATE, 7, 'SUCCESS', 'TEST DOM, TRIGGER URL', 'Url : ' || v_url || ' count test: '|| v_count); WHEN OTHERS THEN
v_error := To_Char(SQLERRM);
INSERT INTO integracao_log(data, cod_integracao, status, rotina, obs) VALUES(SYSDATE, 7, 'FAIL', 'TEST DOM, TRIGGER URL', 'Url : ' || v_url || ' # qtd: '|| v_count || ' # Erro: ' || v_error);
END;
But, the problem is:
the clause where in the View is just the condition cod_situation = 6, but when the trigger calls the webservice where we read the View, I do not have cod_situation = 6 yet.
So, my question is, how can I call the trigger link, but only after the commit is done in the table?
A post commit hook is not available. And this is for good reason: At the point where you access the web service the modified data has already been committed. What should the database do if the commit is successful but the post commit fails?
Generally speaking it is not a good idea to access other systems from a trigger: Your session is still not committed hence a rollback is possible. That would means that your web service has been informed about a transaction in the database that might never be written to disk. This may or may not be catastrophic. Additionally you make the functionality of your database dependent on the availability of the web service which is bad practice in itself.
If you absolutely have to do what you are doing you would do yourself a favor by moving the view reading into a stored procedure and call that from the trigger and the web service.
I need to create a trigger to control a link between tables. I have the following table structure (image below). an item is linked to a budget and a structure, and this structure should be linked to the same budget.
Sometimes the item receive wrong structures, receive structures different from its own budget budget. I need that when you insert a budget item or edit a budget item to be validated if the id_budget are equal, the id_budget of dg_budget_item is equal to the id_budget dg_budget_structure to which it is being linked
I started building this trigger, but do not know how to continue
CREATE TRIGGER T_BUDGETO_STRUCTURE_ITEM_ID_STRUCTURE
BEFORE INSERT OR UPDATE OF id_budget_structure ON dg_budget_structure_item
select id_budget from dg_budget_structure where id_budget_structure = new.id_budget_structure
FOR EACH ROW
WHEN (new.id_budget <> ???(result of select?))
pl/sql_block
This should get you close to what you want
CREATE OR REPLACE TRIGGER T_BUDGET_STRUCTURE_ITEM_ID_STRUCTURE
BEFORE INSERT OR UPDATE OF id_budget_structure ON dg_budget_item
FOR EACH ROW
DECLARE
v_id_budget_structure NUMBER;
Budgets_do_not_match EXCEPTION;
v_code NUMBER;
v_errm VARCHAR2(64);
BEGIN
SELECT id_budget INTO v_id_budget_structure FROM dg_budget_structure WHERE id_budget_structure = :new.id_budget_structure
IF :new.id_budget <> v_id_budget_structure THEN
RAISE Budgets_do_not_match;
END IF;
EXCEPTION
WHEN Budgets_do_not_match THEN
Raise_application_error (-20001,
'Budget '||TO_CHAR(:new.id_budget)||' for structure '
|| :new.id_budget_structure || 'does not match the budget trying to be linked';
WHEN NO_DATA_FOUND THEN
Raise_application_error(-20002,
'Invalid budget structure ' ||:new.id_budget_structure);
WHEN OTHERS THEN
v_code := SQLCODE;
v_errm := SUBSTR(SQLERRM, 1 , 64);
Raise_application_error(-20000,
'Unexpected error ' || v_code ': ' || v_errm;
END;
You cannot ROLLBACK within a trigger so your application will need to deal with that.
As the table is mutating the following trigger does not work as I believe the SQL statement within the trigger cannot be executed against a mutating table, however as I am not on 11g I cannot create a compound trigger. I have tried including PRAGMA AUTONOMOUS TRANSACTION; in the declaration section, however this would not compile. Could anyone provide me with the best solution?
create or replace
trigger fb_pers_id_check2_tr
--after insert on ifsapp.person_info_tab
before insert on ifsapp.person_info_tab
for each row
begin
declare
-- pragma autonomous_transaction;
v_pid_ person_info_tab.person_id%type;
format_name_ person_info_tab.name%type;
begin
v_pid_ := :new.person_id;
select regexp_replace(upper(:new.name), '\W')
into format_name_
from ifsapp.person_info_tab
where person_id = v_pid_;
if length(v_pid_) < 3 and (length(format_name_) < 21 and v_pid_ <> format_name_) then
raise_application_error(-20001, 'Person ID: ' || v_pid_ || 'is not valid, please enter a valid Person ID, e.g. "' || format_name_ || '".');
end if;
end;
end fb_pers_id_check2_tr;
N.B. In plain English this trigger is intended to stop users setting a person id that is less than 3 characters long and does not equal variable 'format_name_' if it is less than 21 characters long.
Oracle doesn't allow a row trigger to read or modify the table on which the trigger is defined. However, if PERSON_ID is a PRIMARY or UNIQUE key on PERSON_INFO_TAB (which seems to be the case given that it's used in a singleton SELECT) you don't really need to read the table - just use the OLD or NEW values where appropriate:
create or replace trigger fb_pers_id_check2_tr
before insert on ifsapp.person_info_tab
for each row
declare
v_pid_ person_info_tab.person_id%type;
format_name_ person_info_tab.name%type;
begin
v_pid_ := :new.person_id;
format_name_ := REGEXP_REPLACE(UPPER(:new.name), '\W');
if length(v_pid_) < 3 and
length(format_name_) < 21 and
v_pid_ <> format_name_
then
raise_application_error(-20001, 'Person ID: ' || v_pid_ ||
' is not valid, please enter a valid' ||
' Person ID, e.g. "' || format_name_ || '".');
end if;
end fb_pers_id_check2_tr;
Here the code is checking the NEW value of NAME (which I think is right, given that it appears to be validating input), but if the intent was to check the OLD value it's simple to change :NEW to :OLD in the REGEXP_REPLACE call.
Share and enjoy.
I am building stacked column 3d Flash chart in Oracle Apex. it is based on the PL/SQL returning SQL query. PL/sql is required to capture all types of properties and count number of instances in database connected to those properties. The code is :
DECLARE
l_qry VARCHAR2(32767);
v_id NUMBER;
v_resort VARCHAR2(80);
BEGIN
l_qry := 'SELECT ''fp=&APP_ID.:802::app_session::::P5_SEARCH_MONTH:''||TO_CHAR(E.ENQUIRED_DATE,''MON-YY'')||'':'' link,';
l_qry := l_qry || ''' ''||TO_CHAR(E.ENQUIRED_DATE,''MON-YY'')||'':'' label,';
--Loop through the resorts and add a sum(decode...) column with column alias
FOR r1 IN (SELECT DISTINCT a.resort_id FROM enquiry a, resort b where a.resort_id IS NOT NULL and a.resort_id = b.id and b.active =1)
LOOP
select name into v_resort
from resort
where id = r1.resort_id;
What happens now PLSQL is loping through all resorts and counts them. this solution does work to certain degree however after value returned I want to have label with name of resort taken from v_resort. It is where main difficulty is
l_qry := l_qry || 'sum(decode(resort_id,''' || r1.resort_id ||''',1,0)) test,';
l_qry := l_qry || 'sum(decode(resort_id,''' || r1.resort_id ||''',1,0)) '|| v_resort||',';
First line will display 'test' label when you hover over the columns just fine. However the the second one with '|| v_resort ||' will cause issue with not displaying back any results... it is not giving #no_data_found# message but just blank field..
The rest of the code :
END LOOP;
--Trim off trailing comma
l_qry := rtrim(l_qry, ',');
--Append the rest of the query
l_qry := l_qry || ' from ENQUIRY E,resort r
where
e.enquiry_type=''AVAILR''
and e.enquiry_channel like '''||:P5_CHANNEL||'''
and trunc(e.created) >= '''||:P5_DATE_FROM||'''
and trunc(e.created) <= '''||:P5_DATE_TO||'''
and e.ENQUIRED_DATE > '''||:P5_DATE_FROM||'''
and ((NVL(:P5_AVAILABLE,''A'')=''A'') or ('''||:P5_AVAILABLE||'''=AVAILABLE))
and e.resort_id = r.id
and ((resort_id = '''||:P6_RESORT||''') or ('''||:P6_RESORT||''' like ''0''))
group by To_Char(ENQUIRED_DATE,''MON-YY''),TO_CHAR(ENQUIRED_DATE,''YYMM'')
Order By TO_CHAR(ENQUIRED_DATE,''YYMM'')';
return(l_qry);
END;
I tired '|| v_resort ||' , '''||v_resort||''' . Any other ideas how to create value of label to be taken from v_resort which holds resort name ?
Try "'||v_resort||'"
You have to use a quoted identifier since v_resort can be any string and there are many rules for unquoted names.
I am writing a trigger for an Oracle DB to send an email then update a column for that record acknowledging that the email has been sent. I was advised to create a cursor to fetch each row, then gather the info for the email, send the email, update the record, then repeat in a loop. The code below is what I have so far.
CREATE OR REPLACE TRIGGER "SEND_EMAIL"
After INSERT OR UPDATE OF ISSUE_ADDED_TO_ALM ON DB_TABLE FOR EACH ROW
DECLARE
l_table DB_TABLE%rowtype;
l_body varchar2(4000);
l_to_address varchar2(2000);
l_from varchar2(200);
l_name varchar2(100);
l_summary varchar2(1000);
l_description varchar2(4000);
l_ALM_ID varchar2(100);
l_subject varchar2(400);
l_added_to_alm varchar2(200);
l_SID varchar2(200);
CURSOR cur_ADDED_TO_ALM IS
select * FROM DB_TABLE where ISSUE_ADDED_TO_ALM = '1' and EMAIL_NOTIFICATION = '0';
BEGIN
OPEN cur_ADDED_TO_ALM;
LOOP
Fetch cur_ADDED_TO_ALM into l_table;
Exit when cur_ADDED_TO_ALM%NOTFOUND;
l_from := 'Data Quality IMS Team';
select ISSUE_REQUESTER into l_SID from DB_TABLE;
select emp_email_name,concat(concat(emp_first_name,' '),emp_last_name) into l_to_address, l_name from telephone_book where emp_id = l_SID;
select ISSUE_SUMMARY,ISSUE_DESCRIPTION,ALM_ISSUE_ID into l_summary, l_description, l_ALM_ID from DB_TABLE where ISSUE_ADDED_TO_ALM = '1' and EMAIL_NOTIFICATION = '0';
l_subject := l_ALM_ID + 'Request has been created.';
l_body := '<style type="text/css">
p{font-family: Calibri, Arial, Helvetica, sans-serif;
font-size:12pt;
margin-left:30px;
}
</style>';
l_body := l_body || '<p>Your request has been created.</p>';
l_body := l_body || '<p>Data Quality Center received the following request:</p>';
l_body := l_body || '<p>Request ID: '|| l_ALM_ID ||'</p>';
l_body := l_body || '<p>Request Title: '|| l_summary||'</p>';
l_body := l_body || '<p>Request Description: '|| l_description||'</p>';
HTMLDB_MAIL.SEND(
P_TO => l_to_address,
P_FROM => l_from,
P_BODY => l_body,
P_BODY_HTML => l_body,
P_SUBJ => l_subject);
wwv_flow_mail.push_queue(
P_SMTP_HOSTNAME => 'mail.sever_name.net',
P_SMTP_PORTNO => '5'
);
END LOOP;
update DB_TABLE set EMAIL_NOTIFICATION = '1' where ALM_ISSUE_ID = l_ALM_ID
IF cur_ADDED_TO_ALM%ISOPEN then
CLOSE cur_ADDED_TO_ALM;
END IF;
end;
I get the following error:
ORA-04091: table server.DB_Table is mutating, trigger/function may not see it ORA-06512: at "server.SEND_EMAIL", line 15 ORA-06512: at "server.SEND_EMAIL", line 18 ORA-04088: error during execution of trigger 'server.SEND_EMAIL'
I offer these solutions.
You cannot UPDATE the table upon which the trigger is activating. You should instead set the value using :new.EMAIL_NOTIFICATION := 1;
You cannot SELECT the table upon which the trigger is activating. This logically doesn't make sense. That's the reason you're getting a "mutating table" error. The table is changing (mutatiing) and you want to read it, which would produce ambiguous results. DO NOT GO AROUND THIS WITH AN AUTONOMOUS TRANSACTION. Someone somewhere is going to give you this suggestion. That would be digging yourself even deeper into a hole.
There is no need for the explicit cursor. Use an implicit one for clarity and simplicity.
Overall, this is a terrible idea. For a purpose such as this you really don't want to do it this way. Think about it, the user does an insert or update and must wait until the database sends all the necessary emails. If there are any exceptions raised in the trigger the transaction fails and you have to roll back.
Better plans
Have the trigger place the email requests into a table. Have a job that runs frequently to send these emails to the email server in the background so the user doesn't wait.
Have the trigger place email-like messages into a queue. Have a job that ....
Avoid the trigger completely. Just have a job which identifies these records and ....