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.
Related
I have user table and profile table. When a record is created in user table, it automatically create a record in profile table with the returned id.
The profile table is like:
create table app_public.profiles (
user_id int not null primary key references users,
first_name text check (char_length(first_name) < 50),
last_name text check (char_length(last_name) < 50),
display_name text
);
When a user update the profile table and enter values for first_name and last_name, I want a trigger function to concat the two columns and use the result to update display_name.
Currently I define the trigger function as
create function app_public.tg_user_display_name() returns trigger as $$
begin
NEW.display_name = CONCAT(profiles.first_name || ' ' || NEW.profiles.last_name);
return NEW;
end;
$$ language plpgsql volatile set search_path from current;
Also I have the trigger as
create trigger _500_update_display_name
before update on app_public.profiles
for each row
execute procedure app_public.tg_user_display_name();
There is a simpler way. Rather that creating a check constraint to enforce a size check just put that limit on the definition. Further the best way to handle NULL for either name is to just not allow it. so define Profiles as:
create table profiles (
user_id integer not null primary key
, first_name varchar(49) not null
, last_name varchar(49) not null
, display_name text
);
If you have a newer Postgres version (v12 or greater) you can eliminate the the trigger function and the trigger altogether. Define display_name as a generated column. Then just forget about it, except Select there nothing you do with it anyway.
create table profiles (
user_id integer not null primary key
, first_name varchar(49) not null
, last_name varchar(49) not null
, display_name text generated always as (first_name || ' ' || last_name) stored
);
See the difference in the following examples:
Profile with Trigger. Note: This will require the trigger to fire on both Insert and Update. Otherwise an Update will do what you tell it to.
Profile with Generated Column. Note: Insert and Update will do what you wait it do.
it is not clear what is wrong. Your tigger function looks like good. A small problem is using CONCAT() function together string concatenation operator.
SELECT 'Jon' || ' ' || 'Dir', concat('Jon' , ' ' , 'Dir')
Use RAISE NOTICE or RAISE EXCEPTION for debugging.
I have a varchar2 datatype column in a table. User only can insert number to that column and I don't want to change as number. Sometimes while inserting data this column as manual, user can space in column. I create a trigger to avoid it. Trigger i wrote is as below.
CREATE OR REPLACE trigger_name
BEFORE INSERT ON table
FOR EACH ROW
BEGIN
IF :new.column != TRIM(:new.column) THEN
:new.column := TRIM(:new.column);
ELSE
dbms_output.put_line(:new.column || ' is suitable for jobid');
END IF;
END;
I got error like below while compiling above code.
"ORA-00922: missing or invalid option"
Thanks in advance.
The error ORA-00922 is caused by the missing keyword TRIGGER, as pointed out by Barbaros Özhan. Just add it, assuming you got the table name and column name correct, then it would do fine.
But going further your inquiry, it seems you wanted to replace all the spaces entered by the user for that column. If so, this trigger could do it:
CREATE OR REPLACE TRIGGER trigger_name
BEFORE INSERT ON table_name
FOR EACH ROW
BEGIN
IF :new.column_name != REPLACE(:new.column_name,' ','') THEN
:new.column_name := REPLACE(:new.column_name,' ','');
ELSE
dbms_output.put_line(:new.column_name|| ' is suitable for jobid');
END IF;
END;
I added the missing keyword trigger.
Just replace the table_name and column_name to their correct names and it should work.
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;
/
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;
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;