I have a trigger that verifies if a field is null:
create or replace trigger trig1
after insert on table_1
for each row
begin
if ((select table2.column2 from table2 where table2.id= :new.id) isnull) then
update table2 set table2.column2 = :new.column1 where table2.id = :new.id;
end if;
end trig1;
.
run;
I get an error that the trigger is created with compilation errors. I don't know what the problem is. I use Oracle SQL*Plus 10.2.0
The PL/SQL syntax doesn't allow for including SQL statements in the IF clause.
The correct approach is to separate out the SELECT statement and then test for its result. So that would be:
create or replace trigger trig1
after insert on table_1
for each row
declare
v table2.column2%type;
begin
select table2.column2
into v
from table2
where table2.id= :new.id;
if v is null
then
update table2
set table2.column2 = :new.column1
where table2.id = :new.id;
end if;
end trig1;
Note that this does not handle the existence of multiple rows in table2 matching the criteria, or indeed there being no matching rows. It also doesn't handle locking.
Also, bear in mind that code like this doesn't function well in multi-user environments. That's why I mentioned locking. You ought really to use procedural logic to handle these sorts of requirements. Although as is often the case with ill-conceived triggers the real culprit is a poor data model. table2.column2 should have been normalised out of existence.
Related
Say I have a PLPGSQL function
CREATE OR REPLACE FUNCTION function_name
RETURNS TRIGGER AS ...
BEGIN
PERFORM
1
FROM
table1 t1
JOIN
table2 t2 USING( column_name )
WHERE
t1.column_name = NEW.column_name;
RETURN NEW;
END;
DROP TRIGGER IF EXISTS trigger_name
ON table1;
CREATE TRIGGER trigger_name
BEFORE INSERT ON table1
FOR EACH ROW EXECUTE PROCEDURE function_name;
I noticed that only some columns in table1 and table2 are accessible with NEW.column_name. How can I see the full list of columns I can access with NEW?
Additionally, if there is a column in table1 or table2 that I cannot access with NEW, how can I make it accessible to NEW?
Look at this line:
BEFORE INSERT ON table1
This tells you that the trigger is executed before an INSERT ON table1. This means that you will have NEW.column_name for any column of table1, while table2 is unchanged by the trigger and makes no sense to use OLD or NEW on it, hence it illegal. So, to be precise: NEW works for table1 columns and does not work on table2 columns.
I'm trying to build a trigger that checks if the row that is gonna be inserted, exists in another table.
Basically my 2 tables share one column, ID.
I want to prevent the insertion when the new row doesnt exist at least once in the other table.
I have this:
create or replace trigger BIM
before insert on TABLE1
for each row
begin
if not exists (select 1 from TABLE2 where TABLE2.ID = :new.TABLE1.ID)
then
raise_application_error(-20634, 'Error');
end if;
end;
But i'm getting this:
PLS-00049: bad bind variable 'NEW.TABLE1'
Gordon is right, It is preferable to use Foreign Key constraint for this scenario.
The problem with your code ( apart from the error which Gordon pointed out )is that unlike few other DBMS like Postgres, In Oracle you cannot use EXISTS in a PL/SQL expression/statements like IF. It should be a purely SQL statement.
create or replace trigger BIM
before insert on TABLE1
for each row
declare
l_id_exists INT;
begin
select CASE WHEN
exists (select 1 from TABLE2 where TABLE2.ID = :new.ID)
THEN 1
ELSE 0 END INTO l_id_exists from dual;
if l_id_exists = 0
then
raise_application_error(-20634, 'Error');
end if;
end;
/
DEMO
You don't need to repeat the table name:
create or replace trigger BIM
before insert on TABLE1
for each row
begin
if (select 1 from TABLE2 where TABLE2.ID = :new.ID and rownum = 0) is not null
then
raise_application_error(-20634, 'Error');
end if;
end;
That said, this is an odd requirement. I would recommend that you use a foreign key constraint, but you explicitly say "at least once". That leads me to suspect that you have a bad data model -- you are missing some sort of entity where the id would be the primary key of that table.
I want to check if the id I want to insert into tableA exists in tableB into an if statement
Can I do something like this
if new.id exists (select id from tableB where stat = '0' ) then
some code here
end if;
When I try this I get an error message, any thoughts?
Why not do it like this? I'm not very knowledgeable about PostgreSQL but this would work in T-SQL.
INSERT INTO TargetTable(ID)
SELECT ID
FROM TableB
WHERE ID NOT IN (SELECT DISTINCT ID FROM TargetTable)
This is usually done with a trigger. A trigger function does the trick:
CREATE FUNCTION "trf_insert_tableA"() RETURNS trigger AS $$
BEGIN
PERFORM * FROM "tableB" WHERE id = NEW.id AND stat = '0';
IF FOUND THEN
-- Any additional code to go here, optional
RETURN NEW;
ELSE
RETURN NULL;
END IF;
END; $$ LANGUAGE plpgsql;
CREATE TRIGGER "tr_insert_tableA"
BEFORE INSERT ON "tableA"
FOR EACH ROW EXECUTE PROCEDURE "trf_insert_tableA"();
A few notes:
Identifiers in PostgreSQL are case-insensitive. PostgreSQL by default makes them lower-case. To maintain the case, use double-quotes. To make your life easy, use lower-case only.
A trigger needs a trigger function, this is always a two-step affair.
In an INSERT trigger, you can use the NEW implicit parameter to access the column values that are attempted to be inserted. In the trigger function you can modify these values and those values are then inserted. This only works in a BEFORE INSERT trigger, obviously; AFTER INSERT triggers are used for side effects such as logging, auditing or cascading inserts to other tables.
The PERFORM statement is a special form of a SELECT statement to test for the presence of data; it does not return any data, but it does set the FOUND implicit parameter that you can use in a conditional statement.
Depending on your logic, you may want the insert to succeed or to fail. RETURN NEW to make the insert succeed, RETURN NULL to make it fail.
After you defined the trigger, you can simply issue an INSERT statement: the trigger function is invoked automatically.
Presumably, you want something like this:
if exists (select 1 from tableB b where stat = '0' and b.id = new.id) then
some code here
end if;
I wrote the following code:
CREATE OR REPLACE TRIGGER CHECK_tuple
BEFORE INSERT ON tableB
FOR EACH ROW
DECLARE IS_JOIN BOOLEAN:=FALSE
BEGIN
SELECT tableB.column1, tableB.column2,
CASE
WHEN IS_JOIN:= FALSE THEN raise_application_error(-20101, 'ERROR.');
ELSE IS_JOIN:= TRUE
END AS CHCK_JOIN
FROM tableB
JOIN tableA
ON tableB.column1=tableA.column1 AND tableB.column2=tableA.column2;
END;
I have to check if a tuple (t1) exits in table A (with "tuple", i mean the entire row of the table with multiple columns). If exists, it has to match with t2 in table B. Before one inserts tuple t2 in table B, the trigger must activate. If t1 doesn't match with t2, the flag IS_JOIN will remain FALSE and Oracle SQL will give an error. Else, if t1 is equal to t2, IS_JOIN will be TRUE and no action will be take. I want this "check" to take place for each row that one will insert in table B. Is this the proper way to do it? If the task isn't clear, please ask for further info.
The proper way to do something like this:
CREATE OR REPLACE TRIGGER test_test_CHECK_tuple
BEFORE INSERT ON tableB
FOR EACH ROW
declare
v_cnt number(10);
BEGIN
SELECT count(*)
into v_cnt
FROM tableA
where column1=:new.column1
and column2=:new.column2;
if v_cnt= 0 then
raise_application_error(-20101, 'ERROR.');
end if;
END;
:new means, that are the values, you want to insert. There is no other way to use that values.
btw. that is not really how a foreign key works, since a foreign key is assigned to a primary key or unique key
I'm pretty new to PL/SQL and I have to work with it a lil bit. I had to make some functions which are pretty similar. I simplified it for this question.
I got 2 tables (called them TABLE1, TABLE2 in this example) which have some data. I have to trim, validate the data and insert it into other tables.
TABLE1 -> TABLE3
TABLE2 -> TABLE4
TABLE1 has some orders, while TABLE2 has several positions for each order. As I said its simplfied so I haven't posted things like the exception or the open/close cursors etc. Atm it works like this but I don't think this structure is somewhere near "best-practise" but I didn't found any PL/SQL code on the web which covered this problem, though it must be something pretty common.
COMMIT could be at the end of the outer loop I guess, maybe its even after the whole function.
So could you tell me if its okay like this or completely stupid and what I should/could change and why. I don't wanna get used to a 'bad' codingstyle so I wanna learn it the right way while I'm a beginner at it.
Heres the simplified code:
BEGIN
SAVEPOINT SAVE_Stufe_5;
LOOP
SAVEPOINT SAVE_LOOP;
FETCH CURSOR1 INTO RECORD1;
EXIT WHEN CURSOR1%NOTFOUND OR CURSOR1%NOTFOUND IS NULL;
vError := 0;
RECORD1 := CURSOR1;
-- DATAVALIDATION (vError will be the Errorcode)
IF (vError = 0) THEN
retcode := InsertTABLE3(RECORD1);
IF (retcode != DATABASE_OK) THEN
ROLLBACK TO SAVE_LOOP;
END IF;
END IF;
LOOP
FETCH CURSOR2 INTO CURSOR2;
EXIT WHEN CURSOR2%NOTFOUND OR CURSOR2%NOTFOUND IS NULL OR vError != 0 OR retcode != DATABASE_OK;
RECORD2 := CURSOR2;
-- DATAVALIDATION (vError will be the Errorcode)
IF (vError = 0) THEN
retcode := InsertTABLE4(RECORD2);
IF (retcode = DATABASE_OK) THEN
UPDATE TABLE2
SET TABLE2.Status = 20
WHERE TABLE2.ID = CURSOR2.ID;
ELSE
ROLLBACK TO SAVE_LOOP;
END IF;
END IF;
END LOOP;
IF (vError = 0) THEN
UPDATE TABLE1
SET TABLE1.Status = 20
WHERE TABLE1.ID = CURSOR1.ID
ELSE
ROLLBACK TO SAVE_LOOP;
UPDATE TABLE1
SET TABLE1.Status = vError
WHERE TABLE1.ID = CURSOR1.ID
UPDATE TABLE2
SET TABLE2.Status = vError
WHERE TABLE2.ID = CURSOR2.ID
END IF;
END LOOP;
END;
Small update:
I managed to do the validation set-based, though I don't really know how to get my data into the other table. I tried a insert select with the trim in it but that only inserts one row. If I would use a implicit cursor as suggested I still had to loop, I wouldn't loop the cursor but the SELECT INTO as far as the implicit cursor only has one row.
I guess I could really need a snippet or some link to help me out. Here's a simplified version of my try:
INSERT INTO TABLE3
(
val1,
val2,
val3
)
SELECT TRIM(val1),
TRIM(val2),
TRIM(val3),
FROM TABLE1
WHERE STATUS = 10
AND (TRIM(PK1) || TRIM(PK2)) NOT IN (SELECT (TABLE3.PK1 || TABLE3.PK2) FROM TABLE3);
Generally speaking, it is bad style to do things by looping in SQL. A loop like this will be extremely slow compared to a set-based solution. Instead of all these loops, it would be preferable to use a single insert or merge statement to copy all of table 1 into table 3--or perhaps a few statements if your validation is complex and you need some intermediate steps.
Most types of trimming and data validation you would want to do can be handled like this. Almost never do you need nested loops. There are exceptions, but they are rare. Those who are new to SQL tend to use loops because that is what we know from other languages. I was in that category not so long ago. But to really use the power of the language, you have to get beyond that.
Beyond this general point, not much specific help can be given if we don't know anything about the tables or what kind of validation you are doing.
When you should commit is also dependent on your specific design and what you are trying to accomplish.