Oracle APEX Database Trigger - Problems with referencing database columns - sql

I have a colon delimited list of values being stored in a varchar2 column, ORDER_PARTS_LIST, of my Oracle database.
(I understand that storing data in a list like this might not be best practice but for now just ignore that fact.)
Here are the relevant table columns:
ORDER_TABLE(
ORDER_NUMBER number,
ORDER_PARTS_LIST varchar(4000))
PARTS_TABLE(
PART_NUMBER varchar(20),
ASSIGNED_ORDER_NUMBER number)
I have a conditional trigger:
create or replace trigger "ORDER_PARTS_T1"
BEFORE
insert or update or delete on "ORDER_TABLE"
for each row
begin
if :new.ORDER_PARTS_LIST LIKE '%'+PART_NUMBER+'%' then
update PARTS_TABLE set ASSIGNED_ORDER_NUMBER = :ORDER_NUMBER;
end if;
end;
When I run this trigger I get the following error:
PLS-00201: identifier 'PART_NUMBER' must be declared
What is supposed to happen is that the trigger checks which PART_NUMBERs, in PARTS_TABLE, are included in the ORDER_PARTS_LIST, in the ORDER_TABLE, and then inserts the ORDER_NUMBER, for the affected row in ORDER_TABLE, into the ASSIGNED_ORDER_NUMBER column, of PARTS_TABLE.
In the end, all the PARTS in an ORDER should be flagged with that ORDER's NUMBER.
Does that make ANY sense???
I am not certain exactly how to properly define the variables in this trigger so that it runs and honestly I have a few doubts as to whether or not the trigger would do what I think it should even if those worked. ANY suggestions or help in getting the trigger functioing like I have defined it should would be great. Thanks in advance.

You can do string matching to test each row:
create or replace trigger "ORDER_PARTS_T1"
BEFORE
insert or update on "ORDER_TABLE"
for each row
begin
update PARTS_TABLE p
set p.ASSIGNED_ORDER_NUMBER = :new.ORDER_NUMBER
where instr(':' || :new.ORDER_PARTS_LIST || ':'
,':' || p.PART_NUMBER || ':') > 0;
end;
So for example, if ORDER_PARTS_LIST is '123:456:789', the INSTR will find matches for the ids 123, 456 and 789, but not 124, 45 or 8, for example.
When parts are removed from an order you will need a different trigger to NULL the appropriate fields in PARTS_TABLE:
create or replace trigger "ORDER_PARTS_T1"
BEFORE
update on "ORDER_TABLE"
for each row
begin
update PARTS_TABLE p
set p.ASSIGNED_ORDER_NUMBER = NULL
where instr(':' || :new.ORDER_PARTS_LIST || ':'
,':' || p.PART_NUMBER || ':') = 0
and instr(':' || :old.ORDER_PARTS_LIST || ':'
,':' || p.PART_NUMBER || ':') > 0;
end;

You are creating a trigger on the ORDER_TABLE. Since the ORDER_TABLE does not contain a column named PART_NUMBER, Oracle is unable to find the identifier 'PART_NUMBER' as it belongs to the PARTS_TABLE.
You will need to write a separate query in your trigger to access the PART_NUMBER in PARTS_TABLE.

Not entirely sure how this all fits together (seems like this won't account for the same part on multiple orders), but it looks like what you're trying to do is something like this:
create or replace trigger "ORDER_PARTS_T1"
BEFORE
insert or update or delete on "ORDER_TABLE"
for each row
begin
update parts_table
set assigned_order_number = :new.ORDER_NUMBER
where part_number in (:new.order_parts_list);
end;

Related

Oracle trigger update values after insertion

I am working on creating a trigger, but still stuck and not sure what is wrong with the code. Any help is appreciated.
The trigger BI_FILM_DESP appends text to the description of every new film inserted into the db.
Output should be something like this [description].[rating] : Originally in <original_langauge_id>. Re-released in <language_id>.
If rating, language id, or original language is null, the film would use the original description.
CREATE OR REPLACE TRIGGER "BI_FILM_DESP"
AFTER INSERT ON "FILM"
FOR EACH ROW
DECLARE
DESCRIPTION VARCHAR2 (255);
BEGIN
INSERT INTO FILM
(TITLE, DESCRIPTION, LANGUAGE_ID, ORIGINAL_LANGUAGE_ID, RATING) VALUES (:new.TITLE, :new.DESCRIPTION, :new.LANGUAGE_ID, :new.ORIGINAL_LANGUAGE_ID, :new.RATING)
UPDATE FILM
SET DESCRIPTION = DESCRIPTION '. ' RATING ': Originally in ' LANGUAGE_ID '. RE-released in ' ORIGINAL_LANGUAGE_ID
WHERE RATING IS NOT NULL
OR LANGUAGE_ID IS NOT NULL
OR ORIGINAL_LANGUAGE_ID IS NOT NULL;
END;
/
That is not how a trigger works. DML statements on the table that the trigger is created on are not possible. Instead do something like this:
CREATE OR REPLACE TRIGGER "BI_FILM_DESP" BEFORE
INSERT ON "FILM"
FOR EACH ROW
DECLARE
l_description VARCHAR2(255);
BEGIN
IF ( :new.rating IS NOT NULL OR :new.language_id IS NOT NULL OR :new.original_language_id IS NOT NULL ) THEN
:new.description := :new.description
|| '. '
|| :new.rating
|| ': Originally in '
|| :new.language_id
|| '. RE-released in '
|| :new.original_language_id;
END IF;
END;
/
a BEFORE INSERT trigger is what you want, because you're modifying a column before it is inserted
You don't need a trigger for this functionality. Same functionality can be achieved using a virtual column.
contatenation in oracle is done using the || symbol.
makes sense to prefix your variables. A variable with the same name as a column of a table is asking for problems. I renamed description to l_description
You might want to read up on triggers. A great place to start is a blog like this one.

Trigger on Views in PL/SQL

I want to write a trigger in PL/SQL. My first aim is to compare two time_stamp data type (A and B). If one of these is bigger than another, for instance A>B, i will update columns on another table. Trigger that i try to write is like below.
CREATE OR REPLACE TRIGGER trigger_name
AFTER INSERT OR UPDATE OR DELETE ON views
FOR EACH ROW
DECLARE
A views.X%TYPE;
B views.Y%TYPE;
C views.Z%TYPE;
BEGIN
SELECT X, Y, Z INTO A, B, C FROM views;
IF A>B THEN
update another_table set D=' ' and E='UNRESOLVED' where column1=A;
ELSE
dbms_output.put_line('ABC: ' || A || ' < ' || 'CDE' || B);
END IF;
END;
If i execute this trigger, i'm getting error like below.
Error report:
ORA-25001: kan inte skapa den här triggertypen i den här typen av vy
25001. 00000 - "cannot create this trigger type on views"
*Cause: Only INSTEAD OF triggers can be created on a view.
*Action: Change the trigger type to INSTEAD OF.
Thanks in advance for your help.
You're nearly there. This is only a syntactic confusion. You cannot create a trigger that fires BEFORE or AFTER an insert or update or delete of a view, but you can create a trigger that fires INSTEAD OF an insert or update or delete:
TABLE BEFORE / AFTER insert or update or delete
VIEW INSTEAD OF insert or update or delete
And, as #Belayer writes, you don't (and shouldn't) use SELECT, use the automatically prepared record called :new for the new values during insert or update, or the record ':old' for the old values during update or delete.
Your trigger would look something like:
CREATE OR REPLACE TRIGGER views_tr
INSTEAD OF INSERT OR UPDATE OR DELETE ON views
FOR EACH ROW
BEGIN
IF :new.x > :new.y THEN
UPDATE another_table SET D=' ', ... WHERE column1 = :new.x;
ELSE
dbms_output.put_line('ABC: ' || :new.x || ' < ' || 'CDE' || :new.y);
END IF;
END views_tr;
/

Check if VARCHAR2 contains only alphabets using trigger

I need to write such a trigger that will check name of the person and will print out his/her id if those people have any digits in theirs names.
What I have by now:
set SERVEROUTPUT ON
create or replace trigger BeforeUpdate
Before insert on customer
for each row
declare
n varchar2(10);
counter number;
nextR number:=0;
begin
select count(id_customer) into counter from customer;
LOOP
nextR:= nextR +1;
select cname into n from customer where id_customer = nextR;
if n not like '%[0-9]%' then
DBMS_OUTPUT.PUT_LINE(nextR || ' has incorrect name');
end if;
exit when nextR = counter;
end loop;
end;
/
It compiles and when I am trying to fire this trigger it do nothing.
I will be grateful for any help.
There are a couple of problems in your code:
using dbms_output in a trigger doesn't really make sense; usually, the INSERT will be performed by client code that doesn't handle the console output.
The sensible thing is to raise an exception instead.
You don't need to perform a SELECT in your trigger code. In fact, doing so will usually either be superfluous or raise a mutating table error. Instead, use :new and :old to refer to the values of the row that was inserted
(minor) naming a before insert trigger BeforeUpdate is somewhat confusing
use a regular expression for testing this business rule (seriously; regexes rule for this kind of thing)
Altogether, here's the fixed version (untested, I don't have an Oracle instance available for testing right now):
create or replace trigger TR_BI_CUSTOMER
Before insert on customer
for each row
begin
if regexp_like(:new.name, '.*[0-9].*') then
raise_application_error(-20001, 'Incorrect name: ' || :new.name);
end if;
end;
Use regular expression to get your result.
In your case, if you get a digit in n, your if clause should be executed.
So,
if regexp_replace(n,'[^[:digit:]]') IS NOT NULL then
DBMS_OUTPUT.PUT_LINE(nextR || ' has incorrect name');
end if
It seems you are also attempting to use a regular expression for digit. However, what your code is searching is for a string that has [0-9] in it. Like Bat[0-9]Man, which is not your desired result.
In my code, whatever expression is not digit in the given name is being replaced. If the name does not contain any digits, the regular expression would return null. If there is any digit at any place,the expression would return those digits.
You could analyse the following query for better grasping of what is happening here:
select regexp_replace(cname,'[^[:digit:]]') OUTP, cname from customer;
EDIT :
This is not how you write a trigger !
The trigger will be fired each time an insert is going to take place. You don't need the counter. You need to use :NEW reference
set SERVEROUTPUT ON
create or replace trigger update or
Insert on customer
for each row
begin
if regexp_replace(:NEW.cname,'[^[:digit:]]') IS NOT NULL then
DBMS_OUTPUT.PUT_LINE(nextR || ' has incorrect name');
end if;
end;
/
This is a job for REGEXP_LIKE()! The regex of '\d' matches a number.
SQL> with tbl(id, name) as (
select 1, 'Batman' from dual union
select 2, 'Robin1' from dual union
select 3, 'Supe4rman' from dual union
select 4, '3Joker' from dual
)
select id, name bad_name
from tbl
where regexp_like(name, '\d');
ID BAD_NAME
---------- ---------
2 Robin1
3 Supe4rman
4 3Joker
SQL>
If your goal is to strip out the digits on the way in (but be careful, a company really could have a number in the name like Level3 Communications or 3Com, if it's a person its less likely but these days who knows!) This is untested:
CREATE OR REPLACE TRIGGER customer_bu
BEFORE INSERT OR UPDATE
ON customer
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
BEGIN
-- If the new name contains a digit, strip it.
if regexp_like(:new.name, '\d') then
:new.name := regexp_replace(:new.name, '\d', NULL);
end if;
END customer_bu;
/

SQL/Oracle 10g - Troubles with triggers/getting values from a table

I'm currently having an issue with a trigger I'm writing. I want to do a simple trigger in which after an update to table STATEMENT with the status field set to 'Sent', it would create a new row in the table NOTICE with fields such as id, date, user and the last field being a message which takes certain field values to create a "notice".
If it will help, my STATEMENT table contains the following fields:
id
List item
Title
Others not needed to know
So, with the last field of the NOTICE to be inserted, I want to create like a message, perhaps saying "The statement, (id) - (title), issued on (date) has been sent."
I currently have at the moment:
create trigger send_notice
after update on STATEMENT
for each row
when (new.status = 'Sent')
begin
insert into NOTICE values (notice_seq.nextval, SYSDATE, '10001', 'the notice
im having trouble constructing');
end send_notice;
I have tested this trigger in a database and everything seems to work fine. Another thing I was just wondering is if the formatting or if there is anything missing that might help with this trigger? And also, I would I go about creating that notice, which takes field values from STATEMENT?
Any help is appreciated
You can refer to new STATEMENT column values in the trigger using :new., and concatenate them into your text:
create trigger send_notice
after update on STATEMENT
for each row
when (new.status = 'Sent')
begin
insert into NOTICE values (notice_seq.nextval, SYSDATE, '10001',
'The statement, ' || :new.id || ' - ' || :new.title || ', issued on '
|| :new.issue_date || ' has been sent');
end send_notice;
Sometimes concatenating a lot of text and values can get confusing, and you may find it easier to use this "template" approach:
create trigger send_notice
after update on STATEMENT
for each row
when (new.status = 'Sent')
declare
l_text varchar2(500);
begin
l_text := 'The statement, #ID# - #TITLE#, issued on #DATE# has been sent';
l_text := replace (l_text, '#ID#', :new.id);
l_text := replace (l_text, '#TITLE#', :new.title);
l_text := replace (l_text, '#DATE#', :new.issue_date);
insert into NOTICE values (notice_seq.nextval, SYSDATE, '10001', l_text);
end send_notice;

inserting mutiple column values into single insert value?

So I have this University assignment in which I have to create a trigger called bill_overdue. When a row that has status = overdue is inserted into table invoice, a row is inserted into another table called message.
CREATE SEQUENCE AUTOINCREMENTMESSAGE
MINVALUE 100
START WITH 101
INCREMENT BY 1
CACHE 10
;
CREATE OR REPLACE TRIGGER BILL_OVERDUE
BEFORE INSERT ON INVOICE
FOR EACH ROW
WHEN (NEW.STATUS = 'Overdue')
BEGIN
INSERT INTO MESSAGE (MESSAGENO,MESSAGEDATE,ORIGIN,MESSAGE)
VALUES (AUTOINCREMENTMESSAGE.nextval,SYSDATE,USER,:NEW.DATEISSUED,:NEW.INVOICENO,:NEW.CAMPAIGNTITLE);
END;
/
Now as you can see I want to add :new.dateissued, :new.invoiceno and :new.campaigntitle into a single field(message).Now I know that what I have done is wrong but I've tried adding parentheses around it etc and nothing seems to do what I want. How do I get this to work? Is it possible to do what I want or have I got it completely wrong?
You could use concatenation
:new.dateissued || ', ' || :new.invoiceno || ', ' || :new.campaigntitle
For Oracle you can also concatenate using the CONCAT() function and you can try
CONCAT(:NEW.DATEISSUED,:NEW.INVOICENO,:NEW.CAMPAIGNTITLE)
You may have to cast/convert some of those values though.