Change inserted value with trigger - oracle

I've just started to learn SQL a few weeks ago and I'm trying to make a trigger which changes the inserted value into 10 if it's smaller than 10. I searched for 4h now and I've found a lot of answers but none was good(for me). I really don't understand where the problem is.
Here is the code:
CREATE OR REPLACE TRIGGER NumberOfBooks
BEFORE INSERT
ON Book
FOR EACH ROW
BEGIN
IF new.nobook < 10
THEN
SET new.nobook = 10;
END IF;
END;

In Oracle's trigger syntax the newly inserted record is referred to by :new, not new (notice the colon). Additionally, SET is a part of an update statement, not a way to set field values - those are done by simple assignments, but note that these are done with := rather than =.
So, your trigger should read:
CREATE OR REPLACE TRIGGER NumberOfBooks
BEFORE INSERT
ON book
FOR EACH ROW
BEGIN
IF :new.nobook < 10
THEN
:new.nobook := 10;
END IF;
END;

Related

How to use checkbox in a report in oracle apex to insert values in a table

I have a table call OUTGOING which has many fields but the ones to be populated in this situation is:
FILENUMBER
OUTGOINGDATE
DEPARTMENT
now i have a report which whas an sql
SELECT APEX_ITEM.CHECKBOX2(1,registry.filenumber) "Select",
INCOMINGREQUESTNOTIFICATION.REQUESTEDFILE as REQUESTEDFILE,
INCOMINGREQUESTNOTIFICATION.FILENUMBER as FILENUMBER,
INCOMINGREQUESTNOTIFICATION.REQUESTEDDEPARTMENT as REQUESTEDDEPARTMENT,
INCOMINGREQUESTNOTIFICATION.REQUESTDATE as REQUESTDATE,
REGISTRY.STATUS as STATUS
from REGISTRY REGISTRY,
INCOMINGREQUESTNOTIFICATION INCOMINGREQUESTNOTIFICATION
where REGISTRY.FILENUMBER(+) =INCOMINGREQUESTNOTIFICATION .FILENUMBER
and INCOMINGREQUESTNOTIFICATION.STATUS ='PENDING'
which is fine .. what i need is for
INCOMINGREQUESTNOTIFICATION.FILENUMBER as FILENUMBER
INCOMINGREQUESTNOTIFICATION.REQUESTEDDEPARTMENT as REQUESTEDDEPARTMENT
and sysdate
to be inserted in the outgoing table under the relevant names of course.
I have a pl/sql
DECLARE
L_FILENUMBER WWV_FLOW_GLOBAL.VC_ARR2;
BEGIN
L_FILENUMBER := APEX_APPLICATION.G_F01;
FOR IDX IN 1 .. L_FILENUMBER.COUNT
LOOP
IF L_FILENUMBER(IDX) IS NOT NULL THEN
INSERT INTO OUTGOING
(FILENUMBER,OUTGOINGDATE,DEPARTMENT)
VALUES
((to_number(APEX_APPLICATION.G_F01(1)))
,SYSDATE
,to_char(APEX_APPLICATION.G_F02(2)) )
;
END IF;
END LOOP;
END;
which is not working.. However if i leave only filenumber
DECLARE
L_FILENUMBER WWV_FLOW_GLOBAL.VC_ARR2;
BEGIN
L_FILENUMBER := APEX_APPLICATION.G_F01;
FOR IDX IN 1 .. L_FILENUMBER.COUNT
LOOP
IF L_FILENUMBER(IDX) IS NOT NULL THEN
INSERT INTO OUTGOING
(FILENUMBER)
VALUES
((to_number(APEX_APPLICATION.G_F01(1)))
;
END IF;
END LOOP;
END;
its inserting only the file number fine . This is all being done via a submit button.
NB: i also tried putting outgoing date and department in the declare statement but it still doesnt work
I'd suggest you to learn how to use table aliases. SELECT you wrote is difficult to read due to VERY long table & column names; alias would certainly help.
As of your question: saying that "it is not working" doesn't help at all. What exactly doesn't work? Is there any error? If so, which one? Did you run the page in debug mode and check what's going on? If not, do that.
Apart from that, code you wrote doesn't make much sense - you're trying to insert the same values all over again. E.g. shouldn't APEX_APPLICATION.G_F01(1) be APEX_APPLICATION.G_F01(IDX)? Something like this:
begin
for idx in 1 .. apex_application.g_f01.count
loop
if apex_application.g_f01(idx) is not null then
insert into outgoing
(filenumber,
outgoingdate,
department
)
values
(apex_application.g_f01(idx),
sysdate,
apex_application.g_f02(idx)
);
end if;
end loop;
end;
Check (by inspecting the page) whether (tabular form?) items really are those that you've used (G_F01 and G_F02). If not, you'll have to fix that.
I didn't use any TO_NUMBER nor TO_CHAR functions; I don't know whether your really need them. Even if you don't have them, Oracle will perform implicit datatype conversion when possible, but it'll fail if you try to put e.g. 'abc123' into a NUMBER datatype column. You didn't share that information so - I left those out. Apply them if necessary.

Oracle SQL Developer Triggers IF

The question is asking me to update the Reorder to either "Y" or "N" depending on whether what the store has on hand is less then the Minimum requirement.
Here is what I have so far, but it keeps giving me error. This is my first time writing triggers.
CREATE OR REPLACE TRIGGER TRG_REORDER
AFTER INSERT OR UPDATE OF ON_HAND, MINIMUM ON PART
FOR EACH ROW
BEGIN
IF NEW.ON_HAND <= NEW.MINIMUM THEN
SET NEW.REORDER = 'Y';
ELSE
SET NEW.REORDER = 'N';
END IF;
END;
Found a solution to my problems, AFTER has its limitation, so from reading this little doc about BEFORE and AFTER triggers and this other doc about triggers as well.
CREATE OR REPLACE TRIGGER TRG_REORDER
BEFORE INSERT OR UPDATE OF ON_HAND, MINIMUM ON PART
FOR EACH ROW
BEGIN
IF :NEW.ON_HAND < :NEW.MINIMUM THEN
:NEW.REORDER := 'Y';
ELSE
:NEW.REORDER := 'N';
END IF;
END;

PL SQL Compund Triggers on Batch Inserts

I've written a compound trigger to fire on inserts. Multiple inserts are batched together and sent to the DB where the compound trigger picks it up. My problem is that i need to perform an update query on the same table for certain inserts depending on the data provided by the query. I can't run a row level action since that would result in a mutating trigger table error (ORA-4091). Best thing i could think of was to have the update query in the before or after statement blocks. i cannot have it on the before statement block since each update is dependent on individual inserts and there's no way of knowing the values before actually reaching that query. so i created a "Type" table and updated it before each row is modified and then later at the after statement block i iterate through the Type table and perform update queries using the data on the table. No matter what i tried the After statement block will only perform update queries for the last insert only.
TYPE apple IS RECORD ( v_size apple_t.size%Type, v_color apple_t.color%Type);
TYPE t_apple IS TABLE OF apple INDEX BY VARCHAR2(20);
BEFORE ROW
t_apple(key).v_size := :New.size;
t_apple(key).v_color := :New.color;
END BEFORE ROW
AFTER STATEMENT
Iterator := t_apple.First;
LOOP EXIT WHEN ITERATOR IS NULL;
UPDATE apple_t SET SIZE = 10
WHERE color = t_apple(Iterator).color;
Iterator := t_apple.Next(Iterator);
END LOOP
END AFTER STATEMENT
This basically is how the trigger is designed. Using a second table is out of the question since trigger cost is a major factor. Any Pointers? Please and Thankyou
I dont fully understand but I think you can get your keys after each row ,then update data in after statament block as follows.
declare
idx number := 1 ;
type array_t is varray(10000) of varchar2(100) ;
colorArr array_t := array_t();
AFTER EACH ROW IS
BEGIN
if inserting then
colorArr (idx) := :new.color;
idx := idx + 1 ;
end if;
END
AFTER EACH ROW;
AFTER STATEMENT IS
BEGIN
for i in 1..sicilNoCol.count
loop
-- update here
end loop;
END AFTER STATEMENT;
or why dont you write a simple before insert trigger that you can manuplate :new.size in it? Does it give table mutable error?

Trigger in Oracle: No data found

Please check my code, I seldom use trigger with AFTER clause, thanks so much:
set serveroutput on;
create or replace trigger tvideo_2 after insert on video for each row
declare
pragma autonomous_transaction;
v_title video.title%type; /* Declare a variable to check title that contain '18+' */
begin
select title into v_title from video where video.videoid=:new.videoid;
if v_title like '%18+%' then
update video
set age=18
where videoid=:new.videoid;
dbms_output.put_line('Video '||:new.videoid||' has been updated age to 18+');
else
dbms_output.put_line('Video '||:new.videoid||' is not 18+!');
end if;
end;
/
insert into video values('V5', '18+', 240, 19);
VIDEO properties: (videoid, title, duration, age)
Using pragma autonomous_transaction means that the trigger cannot see the row that has just been inserted and which cause it to be fired.
When you removed the primary key and pre-inserted a record with the same ID, it's that row that will be updated by your trigger, not the new one you are inserting - so it still won't do what you want, and duplicating the PK value isn't sensible anyway.
If you have to do this as an after insert trigger then you can remove the for each row, which avoids the mutating table issue you're presumably trying to avoid, but then you have to query the whole table to find rows to update:
create or replace trigger tvideo_2 after insert on video
begin
update video
set age = 18
where title like '%18+%'
and age != 18;
end;
/
Or if you want to print a message:
create or replace trigger tvideo_2 after insert on video
begin
for rec in (
select videoid, title
from video
where title like '%18+%'
and age != 18
)
loop
update video
set age = 18
where video.videoid = rec.videoid;
dbms_output.put_line('Video '||rec.videoid||' has been updated age to 18+');
end loop;
end;
/
But using dbms_output in a trigger, or really in any stored PL/SQL, isn't a good idea - if the client session that does the insert doesn't look at the output buffer than that message is lost, and it's likely there will nothing (or nobody) to see it anyway.
You could also use an explicit cursor with for update and then update .. where current of.
Either way you're having to query the whole table, which isn't very efficient - you're checking all existing rows every time you insert a new one, instead of just checking that single new value. A trigger isn't really suitable to do that much work - even if it only actually updates a single row (or none!) it has to query everything for every insert. That isn't going to scale very well.
There are other way to avoid the mutating table issue but anything is going to be a hack. The sensible way to do this is with a before-insert row level trigger:
create or replace trigger tvideo_2 before insert on video for each row
begin
if :new.title like '%18+%' then
:new.age := 18;
dbms_output.put_line('Video '||:new.videoid||' has been updated age to 18+');
else
dbms_output.put_line('Video '||:new.videoid||' is not 18+!');
end if;
end;
/
(with the same caveat about using dbms_output) but that seems to be forbidden for some reason.

Oracle 11g: In PL/SQL is there any way to get info about inserted and updated rows after MERGE DML statement?

I would like to know is there any way to receive information in PL/SQL how many rows have been updated and how many rows have been inserted while my PL/SQL script using MERGE DML statement.
Let's use Oracle example of merge described here: MERGE example
This functionality is used in my function but also I'd like to log information how many rows has beed updated and how many rows have been inserted.
There is a not a built-in way to get separate insert and update counts, no. SQL%ROWCOUNT would tell you the number of rows merged, as you probably already know, but there is no equivalent to get separate values for the inserts and updates.
This article by Adrian Billington shows a way to get the information by including a function call in the merge, which might add a little overhead.
There's a similar, and perhaps simpler, trick from MichaelS on the Oracle forums, which I can't take any credit for at all either, of course. I'm tempted to reproduce it here but I'm not sure if that's allowed, but essentially it's using sys_context to maintain a count, in much the same way that Adrian's solution did with a package variable. I'd use that one, as it's cleaner and I think it's easier to follow and maintain.
Still perilously close to a link-only answer but I don't want to plagiarise others' work either...
Workarounds pointed by #AlexPoole works, but for me it's strange why don't count updates, inserts and even possible deletes by more natural way with triggers.
Suppose we have simple test table:
create table test_table (id number, col number)
Define simple package for counters
create or replace package pkg_test_table_counter as
procedure reset_counter;
procedure count_insert;
procedure count_update;
procedure count_delete;
function get_insert_count return number;
function get_update_count return number;
function get_delete_count return number;
end;
... and package body:
create or replace package body pkg_test_table_counter as
vUpdateCount number := 0;
vInsertCount number := 0;
vDeleteCount number := 0;
procedure reset_counter is
begin
vUpdateCount := 0;
vInsertCount := 0;
vDeleteCount := 0;
end;
procedure count_insert is
begin
vInsertCount := vInsertCount + 1;
end;
procedure count_update is
begin
vUpdateCount := vUpdateCount + 1;
end;
procedure count_delete is
begin
vDeleteCount := vDeleteCount + 1;
end;
function get_insert_count return number is
begin
return vInsertCount;
end;
function get_update_count return number is
begin
return vUpdateCount;
end;
function get_delete_count return number is
begin
return vDeleteCount;
end;
end;
To count number of rows during execution of single DML statement we need to reset it in before statement trigger
create or replace trigger trg_test_table_counter_reset
before insert or update or delete on test_table
begin
pkg_test_table_counter.reset_counter;
end;
... and increment appropriate counter in trigger for each row:
create or replace trigger trg_test_table_counter_count
before insert or update or delete on test_table
for each row
begin
if inserting then
pkg_test_table_counter.count_insert;
end if;
if updating then
pkg_test_table_counter.count_update;
end if;
if deleting then
pkg_test_table_counter.count_delete;
end if;
end;
So, after executing MERGE statement without additional tricks inside DML query text it's always possible to get exact number of affected rows:
select
pkg_test_table_counter.get_insert_count insert_count,
(
pkg_test_table_counter.get_update_count
-
pkg_test_table_counter.get_delete_count
) update_count,
pkg_test_table_counter.get_delete_count delete_count
from dual
Note that delete operations also counted as updates for MERGE , but to keep package useful for another operations there are two separate counters.
SQLFiddle test

Resources