SQL - Trigger to check insert of date on each student - sql

I'm trying to create a trigger that check the date that I'm inserting is greater than the one present in table for each student. The control need to be only on ID and DATE.
STUDENT_EXAMS
id_student subject mark date_exam
1 Chemistry 6 'May-05-2020'
2 Maths 7 'May-01-2020'
LEGITIMATE INSERT
insert into STUDENT_EXAMS (id_student, subject, mark, date_exam)
values (1, 'History', 8, 'May-06-2020');
insert into STUDENT_EXAMS (id_student, subject, mark, date_exam)
values (2, 'Biology', 8, 'May-05-2020');
ILLEGITIMATE INSERT
insert into STUDENT_EXAMS (id_student, subject, mark, date_exam)
values (1, 'History', 8, 'May-04-2020');
insert into STUDENT_EXAMS (id_student, subject, mark, date_exam)
values (2, 'Biology', 10, 'Apr-30-2020');
This is the trigger that I tried to create, but it's not working and I don't know how to insert also a control on each ID_STUDENT.
create or replace trigger check_date
before insert on STUDENT_EXAM
for each row
begin
if (:new.date_exam > :old.date_exam) then
insert into STUDENT_EXAM (id_student, subject, mark, date_exam)
values (:new.id_student, :new.subject, :new.mark, :new.date_exam);
end if;
end;

Perhaps this is the one that you are interested in.It prevents insertion to STUDENT_EXAM table when date_Exam for row being inserted is less than max value of date_Exam for the id.
create or replace trigger check_date
before insert on STUDENT_EXAM
for each row
DECLARE
lv_date_exam DATE;
begin
select max(date_exam) into lv_date_exam
from student_exam where id = :new.id;
if (:new.date_exam < lv_date_exam) then
raise_application_error(-20000
, 'Cannot insert record as date_exam '||:new.date_exam||' is less than max date_exam '||lv_date_exam);
end if;
end;
[EDITED by Littlefoot, to show why it will fail with the mutating table error]
Table & trigger you suggested:
SQL> create table student_exam (id number, date_exam date);
Table created.
SQL> create or replace trigger check_date
2 before insert on STUDENT_EXAM
3 for each row
4 DECLARE
5
6 lv_date_exam DATE;
7 begin
8
9 select max(date_exam) into lv_date_exam
10 from student_exam where id = :new.id;
11
12 if (:new.date_exam < lv_date_exam) then
13 raise_application_error(-20000
14 , 'Cannot insert record as date_exam '||:new.date_exam||' is less than max date_exam '||lv_date_exam);
15 end if;
16 end;
17 /
Trigger created.
Testing:
This works:
SQL> insert into student_exam (id, date_exam) values (1, sysdate);
1 row created.
But this does not:
SQL> insert into student_exam (id, date_exam)
2 select 1, sysdate - 10 from dual union all
3 select 1, sysdate + 20 from dual;
insert into student_exam (id, date_exam)
*
ERROR at line 1:
ORA-04091: table SCOTT.STUDENT_EXAM is mutating, trigger/function may not see
it
ORA-06512: at "SCOTT.CHECK_DATE", line 6
ORA-04088: error during execution of trigger 'SCOTT.CHECK_DATE'
SQL>

Related

TRIGGER for validating data

I have 2 tables:
Chestionar
{
id_c pk
punctaj_max
}
Test{
id_t pk
punctaj
id_c fk
}
I want to define a trigger to validate that, before an update, the modified punctaj is between 0 and the punctaj_max from the chestionar table with that id_c.
I tried this but it doesn't work
CREATE OR REPLACE TRIGGER check_val_salary
BEFORE UPDATE of punctaj ON test
FOR EACH ROW
BEGIN
IF :new.punctaj<0 OR :new.punctaj > (Select punctaj_max from chestionar c where c.id_c=:old.id_c)
THEN
RAISE_APPLICATION_ERROR (-20508, 'Punctaj out of bounds');
END;
Any tips please?
Tables and some sample data for chestionar:
SQL> create table chestionar
2 (id_c number primary key,
3 punctaj_max number);
Table created.
SQL> insert into chestionar
2 select 1 id_c, 100 punctaj_max from dual union all
3 select 2 , 200 from dual;
2 rows created.
SQL> create table test
2 (id_t number primary key,
3 punctaj number,
4 id_c number references chestionar(id_c));
Table created.
Trigger:
SQL> create or replace trigger trg_biu_test
2 before insert or update on test
3 for each row
4 declare
5 l_punctaj_max chestionar.punctaj_max%type;
6 begin
7 select c.punctaj_max
8 into l_punctaj_max
9 from chestionar c
10 where c.id_c = :new.id_c;
11
12 if :new.punctaj not between 0 and l_punctaj_max then
13 raise_application_error(-20000, 'Punctaj should be between 0 and ' || l_punctaj_max);
14 end if;
15 exception
16 when no_data_found then
17 raise_application_error(-20001, 'Can not find boundary for that ID');
18 end;
19 /
Trigger created.
Testing:
SQL> insert into test (id_t, punctaj, id_c) values (1001, 555, 3);
insert into test (id_t, punctaj, id_c) values (1001, 555, 3)
*
ERROR at line 1:
ORA-20001: Can not find boundary for that ID
ORA-06512: at "SCOTT.TRG_BIU_TEST", line 14
ORA-04088: error during execution of trigger 'SCOTT.TRG_BIU_TEST'
SQL> insert into test (id_t, punctaj, id_c) values (1001, 555, 1);
insert into test (id_t, punctaj, id_c) values (1001, 555, 1)
*
ERROR at line 1:
ORA-20000: Punctaj should be between 0 and 100
ORA-06512: at "SCOTT.TRG_BIU_TEST", line 10
ORA-04088: error during execution of trigger 'SCOTT.TRG_BIU_TEST'
SQL> insert into test (id_t, punctaj, id_c) values (1001, 55, 1);
1 row created.
SQL>

How to enforce values in one column of the table are not part of another column of the same table

We have a requirement to link parent and child data. We have to enforce that child id cannot be parent and similarily parent id cannot be in child column data.
Example Table
PARENT_ID
CHILD_ID
Expectation
A
B
Allowed
A
C
Allowed
D
E
Allowed
C
F
Should not be allowed since C is already in child
G
D
Should not be allowed since D is already in parent
How can we put a constraint on column for its allowed values based on the values available in another column of the same table. The "Not Correct" ones should not be allowed for insertion in the table.
A trigger, perhaps? I'm not sure constraint can do it itself.
SQL> create table test (parent_id varchar2(10), child_id varchar2(10));
Table created.
SQL> create or replace trigger trg_bi_test
2 before insert on test
3 for each row
4 declare
5 l_cnt number;
6 begin
7 select max(1)
8 into l_cnt
9 from test a
10 where a.child_id = :new.parent_id
11 or a.parent_id = :new.child_id;
12
13 if l_cnt = 1 then
14 raise_application_error(-20000, 'Invalid value');
15 end if;
16 end;
17 /
Trigger created.
Testing:
SQL> insert into test (parent_id, child_id) values ('A', 'B');
1 row created.
SQL> insert into test (parent_id, child_id) values ('A', 'C');
1 row created.
SQL> insert into test (parent_id, child_id) values ('D', 'E');
1 row created.
SQL> insert into test (parent_id, child_id) values ('C', 'F');
insert into test (parent_id, child_id) values ('C', 'F')
*
ERROR at line 1:
ORA-20000: Invalid value
ORA-06512: at "SCOTT.TRG_BI_TEST", line 11
ORA-04088: error during execution of trigger 'SCOTT.TRG_BI_TEST'
SQL> insert into test (parent_id, child_id) values ('G', 'D');
insert into test (parent_id, child_id) values ('G', 'D')
*
ERROR at line 1:
ORA-20000: Invalid value
ORA-06512: at "SCOTT.TRG_BI_TEST", line 11
ORA-04088: error during execution of trigger 'SCOTT.TRG_BI_TEST'
Behaves as expected.
Though, if you'll insert more than a single row at the same time, table will be mutating and you'll get an error. Can it be fixed? Yes, using a compound trigger but let's hope that you'll insert row-by-row.
SQL> insert into test (parent_id, child_Id)
2 select 'E', 'F' from dual union all
3 select 'I', 'J' from dual;
insert into test (parent_id, child_Id)
*
ERROR at line 1:
ORA-04091: table SCOTT.TEST is mutating, trigger/function may not see it
ORA-06512: at "SCOTT.TRG_BI_TEST", line 4
ORA-04088: error during execution of trigger 'SCOTT.TRG_BI_TEST'
SQL>

Insert into table requires specific condition from another table

I have two SQL tables with the following schema:
road_test (test_ID, examiner_ID, student_ID, vin, test_date)
lessons_count (student_ID, lessons_taken)
I am looking for some way to require a student to have at least 5 lessons_taken before they can insert into the road_test table.
Is there some sort of trigger or constraint that allows for this?
Don't store count of lessons; calculate it whenever needed.
Here's what I'd suggest:
SQL> -- the final table
SQL> create table road_test
2 (test_id number, student_id number, vin number);
Table created.
SQL> -- table that shows which lessons were taken by which student
SQL> create table lesson
2 (student_id number, lesson_id number);
Table created.
SQL>
Trigger which is supposed to control whether you're allowed to insert student's record into the road_test table: count number of lessons taken and raise an error if it is too low (I set it to 3 for simplicity):
SQL> create or replace trigger trg_bi_road
2 before insert on road_test
3 for each row
4 declare
5 l_cnt number;
6 begin
7 select count(*)
8 into l_cnt
9 from lesson
10 where student_id = :new.student_id;
11 if l_cnt < 3 then
12 raise_application_error(-20001,
13 'You have to take at least 3 lessons');
14 end if;
15 end;
16 /
Trigger created.
SQL>
Testing (as I said: restricted to 3 lessons, for simplicity):
SQL> -- initial record
SQL> insert into lesson(student_id, lesson_id) values (1, 100);
1 row created.
SQL> -- can I enter that student into the ROAD_TEST table? Nope
SQL> insert into road_test (test_id, student_id, vin) values (555, 1, 123456);
insert into road_test (test_id, student_id, vin) values (555, 1, 123456)
*
ERROR at line 1:
ORA-20001: You have to take at least 3 lessons
ORA-06512: at "SCOTT.TRG_BI_ROAD", line 9
ORA-04088: error during execution of trigger 'SCOTT.TRG_BI_ROAD'
SQL> -- Let's insert 2 more lessons for the same student
SQL> insert into lesson(student_id, lesson_id) values (1, 200);
1 row created.
SQL> insert into lesson(student_id, lesson_id) values (1, 300);
1 row created.
SQL> -- New attempt for the ROAD_TEST table:
SQL> insert into road_test (test_id, student_id, vin) values (555, 1, 123456);
1 row created.
SQL> select * From lesson;
STUDENT_ID LESSON_ID
---------- ----------
1 100
1 200
1 300
SQL> select * from road_test;
TEST_ID STUDENT_ID VIN
---------- ---------- ----------
555 1 123456
SQL>

Multiple insert SQL oracle

How do you do multiple insert with SQL in Oracle 12c when you have an identity column?
INSERT ALL
INTO Table1 (Column2) Values (1)
INTO Table1 (Column2) Values (2)
SELECT * FROM dual;
where Table1 has column1 as an identity, will set the identity column to have the same value which violates the primary key constraint.
CREATE TABLE Table1 (
Table1Id NUMBER GENERATED ALWAYS AS IDENTITY,
column2 VARCHAR2(255),
column3 NUMBER,
PRIMARY KEY (Table1Id)
);
INSERT ALL
INTO Table1 (column2, column3) VALUES ('a', '1')
INTO Table1 (column2, column3) VALUES ('b', '2')
SELECT * FROM dual;
--SQL Error: ORA-00001: unique constraint violated
What am I doing wrong with this?
EDIT Added two test cases, and a possible workaround.
Though Insert statement and insert all statement are practically the same conventional insert statement. But when it comes to sequences, they work differently.
Test case 1 : Identity columns
SQL> DROP TABLE table1 PURGE;
Table dropped.
SQL>
SQL> CREATE TABLE Table1 (
2 Table1Id NUMBER GENERATED ALWAYS AS IDENTITY,
3 column3 NUMBER,
4 PRIMARY KEY (Table1Id)
5 );
Table created.
SQL>
SQL> INSERT ALL
2 INTO Table1 (column3) VALUES ('1')
3 INTO Table1 (column3) VALUES ('2')
4 SELECT * FROM dual;
INSERT ALL
*
ERROR at line 1:
ORA-00001: unique constraint (LALIT.SYS_C0010439) violated
SQL>
Let's see what's actually happening under the hood -
SQL> CREATE TABLE Table1 (
2 Table1Id NUMBER GENERATED ALWAYS AS IDENTITY,
3 column3 NUMBER,
4 CONSTRAINT A UNIQUE (Table1Id)
5 );
Table created.
SQL> INSERT ALL
2 INTO Table1 (column3) VALUES (1)
3 INTO Table1 (column3) VALUES (2)
4 SELECT * FROM dual;
INSERT ALL
*
ERROR at line 1:
ORA-00001: unique constraint (LALIT.A) violated
SQL> SELECT * FROM table1;
no rows selected
SQL> ALTER TABLE table1
2 DISABLE CONSTRAINT a;
Table altered.
SQL> INSERT ALL
2 INTO Table1 (column3) VALUES (1)
3 INTO Table1 (column3) VALUES (2)
4 SELECT * FROM dual;
2 rows created.
SQL> SELECT * FROM table1;
TABLE1ID COLUMN3
---------- ----------
2 1
2 2
SQL>
So, the sequence progressed to nextval however there was an unique constraint violation the first time we did an Insert All. Next, we disabled the unique constraint, and the subsequent Insert All reveals that the sequence did not progress to nextval, rather it attempted to insert duplicate keys.
Though the issue doesn't occur with a INSERT-INTO-SELECT statement.
SQL> INSERT INTO table1(column3) SELECT LEVEL FROM dual CONNECT BY LEVEL <=5;
5 rows created.
SQL>
SQL> SELECT * FROM table1;
TABLE1ID COLUMN3
---------- ----------
2 1
3 2
4 3
5 4
6 5
SQL>
Surprisingly, as per the metadata, the sequence is supposed to proceed to nextval automatically, however it doesn't happen with an Insert All statement.
SQL> SELECT COLUMN_NAME,
2 IDENTITY_COLUMN,
3 DATA_DEFAULT
4 FROM user_tab_cols
5 WHERE table_name ='TABLE1'
6 AND IDENTITY_COLUMN='YES';
COLUMN_NAME IDENTITY_COLUMN DATA_DEFAULT
--------------- --------------- ------------------------------
TABLE1ID YES "LALIT"."ISEQ$$_94458".nextval
SQL>
Test Case 2 : Using a sequence explicitly
The INSERT ALL would work the same way whether an identity column is used or an explicit sequence is used.
SQL> DROP SEQUENCE s;
Sequence dropped.
SQL>
SQL> CREATE SEQUENCE s;
Sequence created.
SQL>
SQL> DROP TABLE t PURGE;
Table dropped.
SQL>
SQL> CREATE TABLE t (
2 ID NUMBER,
3 text VARCHAR2(50),
4 CONSTRAINT id_pk PRIMARY KEY (ID)
5 );
Table created.
SQL>
SQL> INSERT ALL
2 INTO t VALUES (s.nextval, 'a')
3 INTO t VALUES (s.nextval, 'b')
4 INTO t VALUES (s.nextval, 'c')
5 INTO t VALUES (s.nextval, 'd')
6 SELECT * FROM dual;
INSERT ALL
*
ERROR at line 1:
ORA-00001: unique constraint (LALIT.ID_PK) violated
SQL>
SQL> SELECT * FROM T;
no rows selected
SQL>
SQL> ALTER TABLE t
2 DISABLE CONSTRAINT id_pk;
Table altered.
SQL> INSERT ALL
2 INTO t VALUES (s.nextval, 'a')
3 INTO t VALUES (s.nextval, 'b')
4 INTO t VALUES (s.nextval, 'c')
5 INTO t VALUES (s.nextval, 'd')
6 SELECT * FROM dual;
4 rows created.
SQL> SELECT * FROM T;
ID TEXT
---------- ----------------------------------------
2 a
2 b
2 c
2 d
SQL>
Possible workaround - Using a ROW LEVEL trigger
SQL> CREATE OR REPLACE TRIGGER t_trg
2 BEFORE INSERT ON t
3 FOR EACH ROW
4 WHEN (new.id IS NULL)
5 BEGIN
6 SELECT s.NEXTVAL
7 INTO :new.id
8 FROM dual;
9 END;
10 /
Trigger created.
SQL> truncate table t;
Table truncated.
SQL> INSERT ALL
2 INTO t (text) VALUES ('a')
3 INTO t (text) VALUES ('b')
4 INTO t (text) VALUES ('c')
5 INTO t (text) VALUES ('d')
6 SELECT * FROM dual;
4 rows created.
SQL> SELECT * FROM t;
ID TEXT
---------- -------------------------
3 a
4 b
5 c
6 d
SQL>
Here's a workaround using the UNION ALL method instead of the INSERT ALL method. For some reason the data must be wrapped in a select * from (...) or it will generate the error ORA-01400: cannot insert NULL into ("JHELLER"."TABLE1"."TABLE1ID").
insert into table1(column2, column3)
select *
from
(
select 'a', '1' from dual union all
select 'b', '2' from dual
);

Oracle) Insert multiple rows with one fixed value

I'd like to insert these values in the following fashion:
insert into table (name, action-id) values ('user', select action from actions where name='user2');
The result being:
Inserts along the line of, ('user', 1) ('user', 2) ('user', 3)
I'm noticing this isn't correct sql.
How would I go about accomplishing this?
note)
select action from actions where name='user2'
would return: (1, 2, 3)
You can do it with a loop:
BEGIN
FOR x IN (select action from actions where name='user2') LOOP
insert into table (name, action-id) values ('user', x.action)
END LOOP;
END;
or you could use the INSERT/SELECT syntax:
INSERT INTO table (name, action-id)
SELECT 'user', action
FROM actions WHERE name='user2';
Add the fixed value as a column in your query, and use insert-select instead of insert-values:
insert into table (name, action-id)
select 'user', action from actions where name='user2';
Or can be done by procedure
Create that procedure and run it
create or replace procedure set_action
as
cursor c1 is
select * from user;
person c1%rowtype;
username varchar(8);
begin
username:='user';
for person in c1 loop
insert into table(name,action-id)
values (username,person.action);
end loop;
end;
It can be run by execute set_action;
Example:
create table testing(col1 varchar2(10), col2 varchar2(10));
create table testing2(col1 varchar2(10), col2 varchar2(10));
create table testing3(col1 varchar2(10), col2 int);
insert into testing2 (col1, col2) values ('test2_col1', 'test2_col2');
insert into testing3(col1, col2) values ('steve', 1);
insert into testing3(col1, col2) values ('brad', 2);
insert into testing3(col1, col2) values ('chad', 3);
insert into testing3(col1, col2) values ('nick', 1);
insert into testing(col1, col2)
(select col1 ,(select col2 from testing2) from testing3); -- inserts 4 rows
And finally:
select * from testing;
steve test2_col2
brad test2_col2
chad test2_col2
nick test2_col2