Insert Into from another table causing Can't update 'table' in stored function/trigger because it is already used by statement which invoked - insert

I'm pretty new to SQL so if you can keep it as as simple as possible that would be appreciated.
Currently trying to add all the contact IDs from the contact table (civicrm_contacts) to a new custom field table which is a button to show/hide a contact from the main website. At the moment whenever I run my query I get back the following:
"SQL Error [1442] [HY000]: Can't update table 'civicrm_contact' in stored function/trigger because it is already used by statement which invoked this stored function/trigger."
My query is going something like this:
INSERT INTO civicrm_value_appears_on_we_69 (entity_id)
SELECT cc.id
FROM civicrm_contact cc
WHERE contact_type='Organization'
There are 3 columns in total for the 'civicrm_value_appears_on_we_69': 'ID', 'entity_id', and 'appears_on_website_45' which is a 0 or 1 for the button value. All I want is to insert the contact IDs for all the Organisation contacts, I don't mind about nulls for now as long as it makes the rows.
I've looked up a bit about the error and I'm unsure why it's happening. As far as I can gather this errors happpens when you're using a trigger that using the same table as you're inserting into/from. I'm not using a trigger at the moment, or perhaps I have this wrong.
Any suggestions?

Related

Auditing from a trigger in oracle

I am working with a big database and it had many generators of data basically have alot of data being inserted and updated per day. I have a trigger that updates each row every time there is an update or insert and i use the following code to input the person's name from the apex application (the user from apex)
NVL(v('APP_USER'),USER)
My problem comes when there is heavy data entry, for example 500,000 records are being generated by one person (John) and when john generated this data, each row is audited but as john generated more than one person who are users in the apex application shows up in the audit.
So scenario is that john clicks a button to generate data and in the audit fields, more than one users name show up (Mary, John, Peter)
Does anybody have any idea why this is happening?
the entire code, it is very generic
TRIGGER trg_tableA before insert or update
on tableA REFERENCING OLD AS OLD NEW AS NEW
FOR EACH ROW
begin
:new.insert_date:=sysdate;
:new.inserted_by:= nvl(V('APP_USER'),USER);
:new.modified_date:=sysdate;
:new.modified_by:= nvl(V('APP_USER'),USER);
end trg_tableA;
Thank you in advance
As per this link Use v('APP_USER') as default value for column in Oracle Apex There are other options than V('APP_USER'). Since Apex 5, the APP_USER is stored in the sys_context and that is a lot more performant than the V() function. It is available as SYS_CONTEXT('APEX$SESSION','APP_USER').
Please try the below and see if your issue is getting resolved.
TRIGGER trg_tableA before insert or update
on tableA REFERENCING OLD AS OLD NEW AS NEW
FOR EACH ROW
begin
:new.insert_date:=sysdate;
:new.inserted_by:= nvl(sys_context('APEX$SESSION','APP_USER'),user);
:new.modified_date:=sysdate;
:new.modified_by:= nvl(sys_context('APEX$SESSION','APP_USER'),user);
end trg_tableA;

Oracle Apex Apex_application.G_fNN processing not pulling all the values

I'm very much new to oracle apex and have been constantly failing on creating tabular form for inserting data into my table. My scenario is as follows.
i have two tables
1. tasks(task_id, task_Name)
2. Efforts (Eff_id,Task_id,Hours_spent,NOtes).
I want to create a tabular form to insert data into Efforts table with the tasks that I've in Tasks table. I created the tabular form with the query below.
SELECT APEX_ITEM.CHECKBOX(1,TASK_ID) "TASK_ID",
TASK_NAME,
APEX_ITEM.TEXT(2,'')"HOURS_SPENT",
APEX_ITEM.TEXT(3,'')"NOTES"
FROM TASKS;
but when I create the "after Submit" process to read the values of this report and tried to insert into my table effortS with the following code, something strange is happening. When i select the checkbox of row-1,2 and 3 then only row 1 is getting inserted into my efforts table but for the rest, only task id values are getting inserted not the whole row. I was missing "hours_spent and NOtes". in my efforts table for rows-2 and 3..here is my plsql block
BEGIN
FOR i in APEX_APPLICATION.G_F01.COUNT LOOP
INSERT INTO EFFORTS(EFF_ID,TASK_ID,HOURS_SPENT,NOTES)
VALUES (SEQ_EFFORTS.NEXTVAL,APEX_APPLICATION.G_F01(i),APEX_APPLICATION.G_F02(i),APEX_APPLICATION.G_F03(i))
END LOOP;
commit;
END;
Can Someone help me! if there's another way of doing my requirement, then I'm more than happy to do it..
Welcome to the APEX community.
This is a classic problem, due to the nature of HTML
Get the values of the selected rows with checked checkbox
Note, the usage of tabular forms is also waning in favour of Interactive Grids.

Populate a column on update (create too?), and why "FOR EACH ROW"?

I have a table of people who belong to various sites. These sites can change, but don't very often. So when we create an attendance record (a learner_session object) we don't store the site. But this has cause a problem in reporting how many training hours a site has, because some people have changed sites over the years. Not by much, but we'd like to get this right.
So I've added a site_at_the_time column to the learner_session table. I want to auto-populate this with the site the person was at when they attended the session. But I'm not sure how to reference this. For some reason (I'm guessing to speed development or something) the learner_id is allowed to be null. So I'm currently planning to do an update trigger. The learner_id shouldn't ever get updated, and if it ever did somehow, the entire record would be junk so I'm not worried about it overwriting it.
The trigger I have now is
create trigger set_site_at_the_time
after update of learner_id on lrn_session
begin
:new.site_at_the_time:= (select site_id from learner who where :new.learner_id = who.learner_id);
end;
which leads me to the following error:
ORA-04082: NEW or OLD references not allowed in table level triggers
Now, I've done some research and found I need to use a FOR EACH ROW - and I'm wondering what exactly this FOR EACH ROW does - is it every row captured by the trigger? Or is it every row in the table?
Also, will this trigger when I create a record too? So if I do insert into learner_session(id,learner_id,...) values(learner_session_id_seq.nextval,1234,...) will this capture that appropriately?
And while I'm here, I might as well see if there's something else I'm doing wrong with this trigger. But I'm mainly asking to figure out what the FOR EACH ROW is supposed to do and if it triggers properly. =)
FOR EACH ROW means that the trigger will fire once for each row that is updated by your SQL statement. Without this clause, the trigger will only fire once, no matter how many rows are affected. If you want to change values as they're being inserted, you have to use FOR EACH ROW, because otherwise the trigger can't know which :new and :old values to use.
As written, the trigger only fires on update. To make it also fire upon insert, you'd need to change the definition:
CREATE TRIGGER set_site_at_the_time
BEFORE INSERT OR UPDATE OF learner_id
ON lrn_session
FOR EACH ROW
BEGIN
SELECT site_id into :new.site_at_the_time
FROM learner who
WHERE :new.learner_id = who.learner_id);
END set_site_at_the_time;

trigger insert and update oracle error

Friend, I have question about cascade trigger.
I have 2 tables, table data that has 3 attributes (id_data, sum, and id_tool), and table tool that has 3 attributes (id_tool, name, sum_total). table data and tool are joined using id_tool.
I want create trigger for update info sum_total. So , if I inserting on table data, sum_total on table tool where tool.id_tool = data.id_tool will updating too.
I create this trigger, but error ora-04090.
create or replace trigger aft_ins_tool
after insert on data
for each row
declare
v_stok number;
v_jum number;
begin
select sum into v_jum
from data
where id_data= :new.id_data;
select sum_total into v_stok
from tool
where id_tool=
(select id_tool
from data
where id_data= :new.id_data);
if inserting then
v_stok := v_stok + v_jum;
update tool
set sum_total=v_stok
where id_tool=
(select id_tool
from data
where id_data= :new.id_data);
end if;
end;
/
please give me opinion.
Thanks.
The ora-04090 indicates that you already have an AFTER INSERT ... FOR EACH ROW trigger on that table. Oracle doesn't like that, because the order in which the triggers fire is unpredictable, which may lead to unpredictable results, and Oracle really doesn't like those.
So, your first step is to merge the two sets of code into a single trigger. Then the real fun begins.
Presumably there is only one row in data matching the current value of id_data (if not your data model is rally messed up and there's no hope for your situation). Anyway, that means the current row already gives you access to the values of :new.sum and :new.id_tool. So you don't need those queries on the data table: removing those selects will remove the possibility of "mutating table" errors.
As a general observation, maintaining aggregate or summary tables like this is generally a bad idea. Usually it is better just to query the information when it is needed. If you really have huge volumes of data then you should use a materialized view to maintain the summary, rather than hand-rolling something.

Oracle Apex - Updating a view with instead-of trigger

Apex beginner here. I have a view in my Oracle database of the form:
create or replace view vw_awkward_view as
select unique tab1.some_column1,
tab2.some_column1,
tab2.some_column2,
tab2.some_column3
from table_1 tab1,
table_2 tab2
WHERE ....
I need the 'unique' clause on 'tab1.some_column1' because it has many entries in its underlying table. I also need to include 'tab1.some_column1' in my view because the rest of the data doesn't make much sense without it.
In Apex, I want to create a report on this view with a form for editing it (update only). I do NOT need to edit tab1.some_column1. Only the other columns in the view need to be editable. I can normally achieve this using an 'instead-of' trigger, but this doesn't look possible when the view contains a 'distinct', 'unique' or 'group by' clause.
If I try to update a row on this view I get the following error:
ORA-02014: cannot select FOR UPDATE from view with DISTINCT, GROUP BY, etc.
How can I avoid this error? I want my 'instead-of' trigger to kick in and perform the update and I don't need to edit the column which has the 'unique' clause, so I think it should be possible to do this.
I think that you should be able to remove the "unique".
if tab2.some_column1, tab2.some_column2, tab2.some_column3 are not unique, then how do you want to update them ?
if they are unique then the whole result: tab1.some_column1, tab2.some_column1, tab2.some_column2, tab2.some_column3 is unique.
When you state in a sql query "unique" or "distinct" it's for all columns not only 'tab1.some_column1'
Hope i'm in the correct direction of your question here ;)
Your query could be achieved by doing something like:
select a.some_column1, tab2.some_column1, tab2.some_column2, tab2.some_column3
from table_2 tab2
join (select distinct some_column1 from table_1) a
on tab2.column_in_tab1 = a.some_column1
The reason you get the ORA-02014 error is because of the automatically generated ApplyMRU process. This process will attempt to lock a (the) changed row(s):
begin
for r in (select ...
from vw_awkward_view
where <your first defined PK column>= 'value for PK1'
for update nowait)
loop
null;
end loop;
end;
That's a bummer, and means you won't be able to use the generated process. You'll have to write your own process which does the updating.
For this, you'll have to use the F## arrays in apex_application.
If this sounds totally unfamiliar, take a look at:
Custom submit process, and on using the apex_application arrays.
Also, here is a how-to for apex from 2004 from Oracle itself. It still uses lots of htmldb references, but the gist of it is there.
(it might be a good idea to use the apex_item interface to build up your form, and have control over what is generated and what array it takes.)
What it comes down to is: loop over the array containing your items and do an UPDATE on your view with the submitted values.
Of course, you don't have locking this way, nor a way to prevent unnecessary updates.
Locking you can do yourself, with for example using the select for update method. You'd have to lock the correct rows in the table(s) you want to alter, before you update them. If the locking fails, then your process should fail.
As for the 'lost update' story: here you'd need to check the MD5-checksums. A checksum is generated from the editable columns in your form and put in the html-code. On submit, this checksum is then compared to a newly generated checksum from those same columns, but with values from the database at that time of submit. If the checksums differ, it means the record has changed between the page load and the page submit. Your process should fail because the record has been altered, and you don't want to have those overwritten. (if you go the apex_item way, then don't forget to include an MD5_CHECKSUM call (or MD5_HIDDEN).
Important note though: checksums generated by either using apex_item or simply the standard form functionality build up a string to be hashed. As you can see in apex_item.md5_hidden, checksums are generated using DBMS_OBFUSCATION_TOOLKIT.MD5.
You can get the checksum of the values in the DB in 2 ways: wwv_flow_item.md5 or using dbms_obfuscation.
However, what the documentation fails to mention is this: OTN Apex discussion on MD5 checksums. Pipes are added in the generated checksums! Don't forget this, or it'll blow up in your face and you'll be left wondering for days what the hell is wrong with it.
Example:
select utl_raw.cast_to_raw(dbms_obfuscation_toolkit.md5(input_string=>
"COLUMN1" ||'|'||
"COLUMN2" ||'|'||
"COLUMN5" ||'|'||
"COLUMN7" ||'|'||
"COLUMN10" ||'|'||
"COLUMN12" ||'|'||
"COLUMN14" ||
'|||||||||||||||||||||||||||||||||||||||||||'
)) md5
from some_table
To get the checksum of a row of the some_table table, where columns 1,2,5,7,10,12,14 are editable!
In the end, this is how it should be structured:
loop over array
generate a checksum for the current value of the editable columns
from the database
compare this checksum with the submitted checksum
(apex_application.g_fcs if generated) if the checksums match,
proceed with update. If not, fail process here.
lock the correct records for updating. Specify nowait, and it
locking fails, fail the process
update your view with the submitted values. Your instead-of trigger
will fire. Be sure you use correct values for your update statement so that only this one record will be updated
Don't commit inbetween. It's either all or nothing.
I almost feel like i went overboard, and it might feel like it is all a bit much, but when you know the pitfalls it's actually not so hard to pull this custom process off! It was very knowledgable for me to play with it :p
The answer by Tom is a correct way of dealing with ths issue but I think overkill for your requirements if I understand correctly.
The easiest way may be to create a form on the table you want to edit. Then have the report edit link take the user to this form which will only update the needed columns from the one table. If you need the value of the column from the other table displayed it is simple when you create the link to pass this value to the form which can contain a display only item to show this.

Resources