I want to create a Trigger on the Oracle SQLDeveloper.
The trigger should only allow the 'grade' column within the 'exams' table to get altered afterwards, IF the data in the grade column is below 4.0 aka 5.0/6.0 (German Grading System).
IF the data in the grade column is at 1.0/ 2.0/ 3.0/4.0, the Trigger should deny an alteration.
So the Trigger should check if the grade is below or above 4.0 and based on that, either allow or deny an alteration to the existent table data.
My main problem is that I don't quite understand the Syntax of a Trigger Creation for Oracle SQL Developer. Creating Constraints seems easy but Triggers have me confused.
Solutions I am trying to get to work :
CREATE OR REPLACE TRIGGER KlausurWiederholung
BEFORE UPDATE OF Note ON Prüfen
FOR EACH ROW
BEGIN
IF NEW.Note > OLD.Note THEN UPDATE Prüfen
END IF;
END
So if anyone could explain or link an useful example of how to correctly structure my Trigger, it would be much appreciated.
You want to use :OLD and :NEW to refer to the row's records before and after the update and if the update is invalid then you want to raise an exception (and you need ; as a statement terminator for the final END and to terminate the PL/SQL block with / on a new line):
CREATE OR REPLACE TRIGGER KlausurWiederholung
BEFORE UPDATE OF Note ON Prüfen
FOR EACH ROW
BEGIN
IF :OLD.Note <= 4.0 THEN
RAISE_APPLICATION_ERROR(-20000, 'Grade too high to update.');
END IF;
END;
/
Then, for the sample data:
CREATE TABLE Prüfen (id, note) AS
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 2, 1.2 FROM DUAL UNION ALL
SELECT 3, 2 FROM DUAL UNION ALL
SELECT 4, 2.5 FROM DUAL UNION ALL
SELECT 5, 3 FROM DUAL UNION ALL
SELECT 6, 4 FROM DUAL UNION ALL
SELECT 7, 4.4 FROM DUAL UNION ALL
SELECT 8, 5 FROM DUAL UNION ALL
SELECT 9, 5.7 FROM DUAL UNION ALL
SELECT 10, 6 FROM DUAL UNION ALL
SELECT 11, 6.3 FROM DUAL;
Then:
UPDATE Prüfen
SET note = 1
WHERE id = 2;
Gives the exception:
ORA-20000: Grade too high to update.
ORA-06512: at "FIDDLE_DPBZKTZLAVYIZCPLKBSE.KLAUSURWIEDERHOLUNG", line 3
ORA-04088: error during execution of trigger 'FIDDLE_DPBZKTZLAVYIZCPLKBSE.KLAUSURWIEDERHOLUNG'
But:
UPDATE Prüfen
SET note = note + 4
WHERE note > 4;
Works.
fiddle
Related
Good Day Buddies!
So, here is my Question, it says -
Write a update, delete trigger on clientmstr table. The System
should keep track of the records that ARE BEING updated or
deleted. The old value of updated or deleted records should be
added in audit_trade table. (Separate implementation using both row
and statement triggers)
And my solution looks like this -
-- For row trigger
create or replace trigger row_trigger
before delete or update on client_master
referencing old as old new as new
for each row
begin
insert into audit_table values(
:old.client_id, :old.client_name, :old.client_budget
);
end;
/
And as per the question I have to implement the same using statement trigger but I couldn't think of a way it can be done. I studied about statement triggers and I learned that we can't use :old and :new here. Is there any way we can implement the same row trigger method of adding in audit table using statement trigger? I am just starting out and it's just been two days I started learning PL/SQL. I spend whole day searching everywhere on the internet - tried looking for an example but I am not getting it. Can anyone help?
Edit
(1) I am using Oracle SQL Developer
(2) As someone suggested in comments - it isn't possible to do this in statement trigger, I think the same. I have to submit my assignment this Saturday. I had a conversation with my teacher - she said it's possible to implement it using statement trigger. I asked her how - but she didn't responded. Then I asked her for a hint and she said this (I'm copy pasting her text)-
Create a separate table with col as operations and timestamp. Write statement level trigger on insert update and delete operations. The trigger will capture the operation fired and timestamp by inserting values in table.
I am not getting what does that mean or how to do it! Can anyone help me solve this?
You could use a compound trigger.
Create the types:
CREATE TYPE client_master_obj IS OBJECT(
id NUMBER,
name VARCHAR2(20),
budget NUMBER(10,2)
);
CREATE TYPE client_master_table IS TABLE OF client_master_obj;
Then the trigger:
CREATE TRIGGER client_master_cmp_trigger
FOR DELETE OR UPDATE ON client_master
COMPOUND TRIGGER
data client_master_table := client_master_table();
AFTER EACH ROW
IS
BEGIN
data.EXTEND(1);
data(data.COUNT) := client_master_obj(
:OLD.client_id,
:OLD.client_name,
:OLD.client_budget
);
END AFTER EACH ROW;
AFTER STATEMENT
IS
BEGIN
INSERT INTO audit_table (client_id, client_name, client_budget, trg_type)
SELECT id,
name,
budget,
'C'
FROM TABLE(data);
END AFTER STATEMENT;
END;
/
Which, for the sample data:
CREATE TABLE client_master (client_id, client_name, client_budget) AS
SELECT 1, 'Alice', 100 FROM DUAL UNION ALL
SELECT 2, 'Beryl', 200 FROM DUAL UNION ALL
SELECT 3, 'Carol', 300 FROM DUAL UNION ALL
SELECT 4, 'Debra', 400 FROM DUAL UNION ALL
SELECT 5, 'Emily', 500 FROM DUAL;
CREATE TABLE audit_table (client_id, client_name, client_budget, trg_type) AS
SELECT cm.*, 'X' FROM client_master cm WHERE 1 = 0;
Then after:
UPDATE client_master
SET client_budget = client_budget + 600
WHERE client_id IN (1, 2);
DELETE FROM client_master WHERE client_id IN (1, 3);
Then the audit table contains (with the row trigger also firing for the same changes):
SELECT * FROM audit_table;
CLIENT_ID
CLIENT_NAME
CLIENT_BUDGET
TRG_TYPE
1
Alice
100
R
2
Beryl
200
R
1
Alice
100
C
2
Beryl
200
C
1
Alice
700
R
3
Carol
300
R
1
Alice
700
C
3
Carol
300
C
db<>fiddle here
Same approach using compound trigger, but although is not literally a level statement trigger, because normally they refer to table level triggers.
create or replace trigger row_compound_trigger
for delete or update on client_master
compound trigger
--
-- an array structure to buffer all the row changes
--
type t_row_list is
table of client_master%rowtype index by pls_integer;
l_audit_rows t_row_list;
l_operation varchar2(1) :=
case when updating then 'U'
when deleting then 'D'
end;
before statement is
begin
--
-- initialize the array
--
l_audit_rows.delete;
end before statement;
after each row is
begin
--
-- at row level, capture all the changes into the array
-- this variables use sys_context in case you want to use it ( not needed )
--
l_audit_rows(l_audit_rows.count+1).aud_who := sys_context('USERENV','SESSION_USER');
l_audit_rows(l_audit_rows.count).aud_when := sysdate;
l_audit_rows(l_audit_rows.count).aud_operation := l_operation;
l_audit_rows(l_audit_rows.count).aud_module := sys_context('USERENV','MODULE');
if updating then
l_audit_rows(l_audit_rows.count).client_id := :new.client_id
l_audit_rows(l_audit_rows.count).client_name := :new.client_name
... all the fields
else
l_audit_rows(l_audit_rows.count).client_id := :old.client_id
l_audit_rows(l_audit_rows.count).client_name := :old.client_name
... all the fields
end if;
end after each row;
after statement is
begin
--
-- then at completion, do a single insert of all the rows into our audit table
--
forall i in 1 .. l_audit_rows.count
insert into audit_table values l_audit_rows(i);
l_audit_rows.delete;
end after statement;
end;
/
Hi guys I have two triggers I am meant to be be creating but I am getting compilation errors on both
this first is supposed to record evaluations of 0 to an audit table and the second is supposed to prevent the deletion of entries in which the date is less than todays date.
SQL> CREATE TABLE EVALUATION_AUDIT
2 (C_NAME VARCHAR (15), CO_ID NUMBER(7), E_DATE DATE,
3 V_ID NUMBER (7), C_EVALUATION NUMBER(1));
Table created.
SQL> CREATE OR REPLACE TRIGGER ZERO_EVAL
2 BEFORE INSERT OR UPDATE OF C_EVALUATION ON CUSTOMER_EVENT
3 FOR EACH ROW
4 WHEN (NEW.C_EVALUATION = 0)
5 BEGIN
6 SELECT C_NAME, CO_ID, E_DATE, V_ID, C_EVALUATION
7 FROM CUSTOMER_EVENT CE, CUSTOMER C, EVENT E
8 WHERE CE.C_ID = C.C_ID
9 AND CE.EVENT_ID = E.EVENT_ID
10 AND C_EVALUATION = NEW.C_EVALUATION;
11 INSERT INTO EVALUATION AUDIT
12 VALUES (:NEW.C_NAME, :NEW.CO_ID, :NEW.E_DATE, :NEW.V_ID, :NEW.C_EVALUATION);
13 END;
14 /
Warning: Trigger created with compilation errors.
SQL> CREATE OR REPLACE TRIGGER PASTEVENTS
2 BEFORE DELETE
3 ON EVENT
4 FOR EACH ROW
5 BEGIN
6 IF :OLD.E_DATE =< SYSDATE
7 THEN RAISE_APPLICATION_ERROR (-20002, 'CAN NOT DELETE PAST EVENT RECORDS');
8
9 END IF;
10 END;
11 /
Warning: Trigger created with compilation errors.
As Justin said, when you get created with compilation errors for any stored PL/SQL, type show errors, or you can query the user_errors table to see all outstanding errors on your objects.
From a quick scan, the first trigger is missing a colon when you reference NEW.C_EVALUATION in the select:
AND C_EVALUATION = :NEW.C_EVALUATION;
You need to select into something, though I'm not sure if it's necessary here as you have the values from the :NEW psuedorecord; not sure why you're selecting at all?
And the second has an incorrect operator, =< instead of <=:
IF :OLD.E_DATE <= SYSDATE
It's generally a good idea to prefix column names with the table alias to avoid ambiguity, e.g. SELECT C.C_NAME, ... if that column comes from the CUSTOMER table, etc. You could have another error in there is you have the same column on multiple tables. And it's good practise to list the column names in your INSERT too, i.e. INSERT INTO EVALUATION_AUDIT (C_NAME, ...) VALUES (...). With the missing underscore that #Dba spotted!
In your first trigger code you don't need to SELECT from the tables as you are just inserting the values from the table CUSTOMET_EVENT to EVALUATION_AUDIT. Also you have missed and underscore _ in table_name in EVALUATION_AUDIT in line 11.
CREATE OR REPLACE TRIGGER zero_eval
BEFORE INSERT OR UPDATE OF c_evaluation ON customer_event
FOR EACH ROW
WHEN (NEW.c_evaluation = 0)
BEGIN
INSERT INTO evaluation_audit(c_name, co_id, e_date, v_id, c_evaluation)
VALUES (:NEW.c_name, :NEW.co_id, :NEW.e_date, :NEW.v_id, :NEW.c_evaluation);
END;
/
In Your second code, it should be <= instead of =<
CREATE OR REPLACE TRIGGER pastevents
BEFORE DELETE
ON event
FOR EACH ROW
BEGIN
IF :OLD.e_date <= SYSDATE THEN
raise_application_error (-20002, 'CAN NOT DELETE PAST EVENT RECORDS');
END IF;
END;
/
Essentially, my problem is that I need to run a query in Oracle that unions a static list of values ('Static' meaning it's obtained from somewhere else that I cannot get from the database, but is actually an arbitrary list of values I plug into the query) with a dynamic list of values returned from a query.
So, my initial query looks like:
select * from (select ('18776') as instanceid from dual) union (<more complex query>)
I think, hooray! And then try to do it with a longer list of static values. Turns out, I get 'Missing Right Parenthesis' if I try to run:
select ('18776','18775') as instanceid from dual
So, my basic issue is how can I integrate a list of static values into this union?
NOTE: This is a simplified example of the problem. The actual list is generated from an API before I generate a query, and so this list of "static" values is unpredictably and arbitrarily large. I'm not dealing with just 2 static values, it is an arbitrary list.
select '18776' as instanceid from dual union all
select '18775' as instanceid from dual
or
select column_value from table(sys.odcivarchar2list('18776', '18775'))
or some sort of hierarchical query that could take your comma separated-string and split it into a set of varchars.
Union these to your initial query.
update: "I'm not dealing with just 2 static values, it is an arbitrary list."
Still can pass to a query as a collection (below is just one of many possible approaches)
23:15:36 LKU#sandbox> ed
Wrote file S:\spool\sandbox\BUFFER_LKU_39.sql
1 declare
2 cnt int := 10;
3 coll sys.odcivarchar2list := sys.odcivarchar2list();
4 begin
5 coll.extend(cnt);
6 for i in 1 .. cnt loop
7 coll(i) := dbms_random.string('l', i);
8 end loop;
9 open :result for 'select * from table(:c)' using coll;
10* end;
23:37:03 11 /
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.50
23:37:04 LKU#sandbox> print result
COLUMN_VALUE
-------------------------------------------------------------
g
kd
qdv
soth
rvwnq
uyfhbq
xxvxvtw
eprralmd
edbcajvfq
ewveyljsjn
10 rows selected.
Elapsed: 00:00:00.01
I think you want to break it into two subqueries:
select *
from ((select '18776' as instanceid from dual)
union
(select '18775' as instanceid from dual)
union
(<more complex query>)
) t;
Note that union all performs better than union. If you know there are no duplicates (or duplicates don't matter) then use union all instead.
If you have the ability/permission to create a table type, you can do this:
CREATE OR REPLACE
TYPE TYP_NUMBER_TABLE AS TABLE OF NUMBER(11);
And then you can use the TABLE function to select from a instance of that type that you initialize on the fly in your SQL:
SELECT COLUMN_VALUE FROM TABLE(TYP_NUMBER_TABLE(1, 2, 3));
Result:
COLUMN_VALUE
------------
1
2
3
I want to insert a row 100 times in a table based on count. For example, I have a table with table_id, table_name and want the rows
1,asd
2,asd
3,asd
4,asd
'
'
'
100,asd
I am looking for a solution using SQL Developer.
You can use the connect by level syntax to easily produce your result.
select level, 'asd'
from dual
connect by level <= 100
Or, as FSP notes an anonymous PL/SQL block with a loop, which isn't as good a solution as you should always use SQL over PL/SQL if possible...
begin
for i in 1 .. 100 loop
insert into my_table(table_id, table_name)
values(i, 'asd');
end loop;
end;
/
If you are using oracle it can be done with a single stamtement:
insert into your_table (table_id, table_name) select level, 'asd' from dual connect by level <= 100;
In SQL Server, you can do things like this:
INSERT INTO some_table (...) OUTPUT INSERTED.*
VALUES (...)
So that you can insert arbitrary sets of columns/values and get those results back. Is there any way to do this in Oracle?
The best I can come up with is this:
INSERT INTO some_table (...)
VALUES (...)
RETURNING ROWID INTO :out_rowid
...using :out_rowid as a bind variable. And then using a second query like this:
SELECT *
FROM some_table
WHERE ROWID = :rowid
...but this isn't quite the same as it returns everything within the column, not just the columns I inserted.
Is there any better way to do this without using a lot of PL/SQL and preferably with only one query?
Maybe I don't understand the question, but wouldn't this do it? (you must know what you want back)
INSERT INTO some_table (...)
VALUES (...)
RETURNING some_column_a, some_column_b, some_column_c, ... INTO :out_a, :out_b, :out_c, ...
#Vincent returning bulk collect into for multi-row insert works only in conjunction with forall (in another words if you insert from collection you can retrieve "results" into another)
The RETURNING clause supports the BULK COLLECT INTO synthax. Consider (10g):
SQL> CREATE TABLE t (ID NUMBER);
Table created
SQL> INSERT INTO t (SELECT ROWNUM FROM dual CONNECT BY LEVEL <= 5);
5 rows inserted
SQL> DECLARE
2 TYPE tab_rowid IS TABLE OF ROWID;
3 l_r tab_rowid;
4 BEGIN
5 UPDATE t SET ID = ID * 2
6 RETURNING ROWID BULK COLLECT INTO l_r;
7 FOR i IN 1 .. l_r.count LOOP
8 dbms_output.put_line(l_r(i));
9 END LOOP;
10 END;
11 /
AADcriAALAAAAdgAAA
AADcriAALAAAAdgAAB
AADcriAALAAAAdgAAC
AADcriAALAAAAdgAAD
AADcriAALAAAAdgAAE
It works with multi-row UPDATE and DELETE with my version (10.2.0.3.0) but NOT with INSERT:
SQL> DECLARE
2 TYPE tab_rowid IS TABLE OF ROWID;
3 l_r tab_rowid;
4 BEGIN
5 INSERT INTO t (SELECT ROWNUM FROM dual CONNECT BY LEVEL <= 5)
6 RETURNING ROWID BULK COLLECT INTO l_r;
7 FOR i IN 1 .. l_r.count LOOP
8 dbms_output.put_line(l_r(i));
9 END LOOP;
10 END;
11 /
ORA-06550: line 7, column 5:
PL/SQL: ORA-00933: SQL command not properly ended
Maybe you have a more recent version (11g?) and the BULK COLLECT INTO is supported for multi-row INSERTs ?