I'm learning PL/SQL specifically triggers, and I want to learn how to operate on multiple tables using triggers but I'm having trouble understanding the process. Here's an example:
CREATE TABLE Sess
(code INTEGER NOT NULL,
dateStart DATE NOT NULL,
dateEnd DATE NOT NULL,
CONSTRAINT KeySess PRIMARY KEY(code)
)
CREATE TABLE Ins
(codeIns CHAR(12) NOT NULL,
code INTEGER NOT NULL,
dateIns DATE NOT NULL,
dateAb DATE,
note INTEGER,
CONSTRAINT KeyIns PRIMARY KEY (codeIns, code)
)
1/ I'm trying to build a trigger that will ensure this:
before Insert or Update Ins
dateAb is between dateStart and dateEnd
2/ I want also to ALTER TABLE and lock code, and codeIns in Ins so no one can update them but i don't know the command.
For the first trigger, I tried something like:
CREATE OR REPLACE TRIGGER test
BEFORE INSERT OR UPDATE OF dateAb ON Ins
FOR EACH ROW
DECLARE
start date;
BEGIN
SELECT DISTINCT s.dateStart INTO start
WHERE Ins.code = s.code
IF (:NEW.dateAb < start) THEN
raise_application_error(-20101,'dateAb incorrect');
END IF;
END;
Note: I didn't declare a variable end to check if it's between, my purpose here was to test the correct syntax
Note2: the table are empty.
I had Warning: Trigger created with compilation errors. and 3 errors :
PL/SQL: SQL Statement ignored
4/40 PL/SQL: ORA-00923: key-word FROM absent
9/5 PLS-00103: Encountered the symbol "IF" when expecting one of the
following:
;
And finally like I said I don't know the correct syntax to lock column on table to prevent updates or if it's possible.
I'm learning so if one of you can teach me the correct way to process with my two request, I would apreciate that.
Thank you
Try this:
create or replace trigger test
before insert or update of dateab on ins
for each row
declare
l_sess_start date;
begin
select s.datestart into l_sess_start
from sess s
where s.code = :new.code;
if :new.dateab < l_sess_start then
raise_application_error
( -20101
, 'Start date ' || to_char(:new.dateab,'DD-MON-YYYY') ||
' cannot be before ' || to_char(l_sess_start,'DD-MON-YYYY'));
end if;
end;
Test:
insert into sess values (1, date '2018-04-22', date '2018-04-30');
insert into ins values ('Z', 1, date '2018-04-01', date '2018-04-01', null);
ERROR at line 1:
ORA-20101: Start date 01-APR-2018 cannot be before 22-APR-2018
ORA-06512: at "WILLIAM.TEST", line 10
ORA-04088: error during execution of trigger 'WILLIAM.TEST'
PLS-00103: Encountered the symbol "IF" when expecting one of the
following:
;
This error is because you are missing ; after select statement
Related
Im trying to create a trigger on oracle sql that updates the 'RESTRICTION' column of 'LRESTRICTION' table to display 'YES' or 'NO' based on the value of 'LAMOUNT' column of 'LEMBER' table when 'LAMOUNT' = 5, I have tried a few other ways but I cant seem to understand how to actually set a trigger properly as there always seems to be errors. I am new to sql and having a hard time understanding triggers as the error messages are also confusing to me they dont seem to be as straight forward as in other languages. My question here is how can I create a trigger that does what Im trying to do or is it even possible?
My tables look like this:
CREATE TABLE LMEMBER (
FNAME VARCHAR2(10),
LNAME VARCHAR2(10),
MTYPE VARCHAR2(7),
IDNUM NUMBER(6) NOT NULL PRIMARY KEY,
LAMOUNT NUMBER(2),
LDURATION NUMBER(3)
);
CREATE TABLE LRESTRICTION(
ID_NUM NUMBER(6) REFERENCES LMEMBER(IDNUM),
RESTRICTION VARCHAR2(3)
);
-------##########------
And my trigger looks like this:
CREATE OR REPLACE TRIGGER SET_RES
AFTER INSERT OR UPDATE ON LMEMBER
FOR EACH ROW
BEGIN
IF: NEW.LAMOUNT:5
UPDATE LRESTRICTION
INSERT INTO LRESTRICTION(RESTRICTION)VALUES('YES')
END IF;
END;
Error is get from this:
Errors: TRIGGER SET_RES
Line/Col: 2/20 PLS-00103: Encountered the symbol "" when expecting one of the following:
. ( * # % & = - + < / > at in is mod remainder not rem then
<an exponent (**)> <> or != or ~= >= <= <> and or like like2
like4 likec between || indicator multiset member submultiset
Line/Col: 2/20 PLS-00049: bad bind variable '5'
also tried more triggers with SET RESTRICTION = 'YES' instead of INSERT INTO LRESTRICTION(RESTRICTION)VALUES('YES') and also tried 'WHEN' and 'WHERE' clauses instead of IF but I cant seem to make it work
You have typos in your IF clause. And the syntax for an UPDATE is UPDATE table SET column = <something> WHERE <some condition>.
CREATE OR REPLACE TRIGGER set_res
AFTER INSERT OR UPDATE ON lmember
FOR EACH ROW
BEGIN
IF :new.lamount = 5 THEN
UPDATE lrestriction
SET restriction = 'YES'
WHERE id_num = :new.idnum;
END IF;
END;
I have this simple stored procedure, where it would add a column to my Orders table
create or replace PROCEDURE ADD_ORDER
(
CUSTOMER_ID IN NUMBER
, NEW_ORDER_ID OUT NUMBER
) AS
DECLARE
NEW_ORDER_ID := MAX(ORDERS.ORDER_NO) + 1;
BEGIN
INSERT INTO ORDERS(ORDER_NO, REP_NO, CUST_NO, ORDER_DT, STATUS)
VALUES( NEW_ORDER_ID, 36, CUSTOMER_ID, CURDATE(), 'C')
END ADD_ORDER;
It is saying the the declare part is not at the correct place (I think), and also it should not end there. Here is what it is saying at the error screen:
Error(6,1): PLS-00103: Encountered the symbol "DECLARE" when expecting one of the following:
begin function pragma procedure subtype type current cursor delete exists prior external language The symbol "begin was inserted before "DECLARE" to continue.
Error(11,1): PLS-00103: Encountered the symbol "END" when expecting one of the following: , ; return returning
Can anyone tell me what is going wrong here ?
As has been mentioned, it is a bad idea to select the maximum order number and then use that to insert a row. If two processes do this at the same time, they try to insert rows with the same order number.
Better use Oracle's built-in features SEQUENCE or IDENTITY.
Here is how you could create the table:
CREATE TABLE orders
(
order_no NUMBER(8) GENERATED ALWAYS AS IDENTITY,
rep_no NUMBER(3) DEFAULT 36 NOT NULL,
cust_no NUMBER(8) NOT NULL,
order_dt DATE DEFAULT SYSDATE NOT NULL,
status VARCHAR2(1) DEFAULT 'C' NOT NULL
);
And this is what your procedure would look like then:
CREATE OR REPLACE PROCEDURE add_order
(
in_cust_no IN NUMBER,
out_order_no OUT NUMBER
) AS
BEGIN
INSERT INTO ORDERS(cust_no) VALUES (in_cust_no)
RETURNING order_no INTO out_order_no;
END add_order;
Demo: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=4b49723c15eb810c01077286e171bc95
There is a syntax error in your code.
NEW_ORDER_ID := MAX(ORDERS.ORDER_NO) + 1; --not be used liked it.
Use below code
create or replace PROCEDURE ADD_ORDER
(
CUSTOMER_ID IN NUMBER
, NEW_ORDER_ID OUT NUMBER
) AS
V_NEW_ORDER_ID NUMBER;
BEGIN
SELECT NVL(MAX(ORDER_NO),0)+1 INTO V_NEW_ORDER_ID FROM ORDERS;
INSERT INTO ORDERS(ORDER_NO, REP_NO, CUST_NO, ORDER_DT, STATUS)
VALUES( V_NEW_ORDER_ID, 36, CUSTOMER_ID, CURDATE(), 'C');
NEW_ORDER_ID:=V_NEW_ORDER_ID;
/*
* CURDATE() -> I am assuming it is user defined function. You can also use SYSDATE, CURRENT_DATE istead of CURDATE()
* OUT Parameter is a write-only parameter. You cannot read value from OUT Parameter
*/
END ADD_ORDER;
Few things need to be correct.
If you're expecting to write a PROCEDURE or a FUNCTION you don't have to use the DECLARE keyword. In writing a test script or something, you should use the DECLARE keyword to declare variables.
When writing a procedure,
All the parameters should be inside the brackets.
Variables should define between AS and BEGIN keywords and should give the datatype.
If you need to fetch the MAX number of ORDERS TAB you have to write a SQL query for that. Because the MAX function only can be used inside a SQL. Additionally, if you interest there is an in-built feature call SEQUENCE in ORACLE which can use for NEW_ORDER_ID. You can check with the link below.
adding a sequence for oracle plsql
I did some changes to your code. Hope it's working fine now. Please take a visit here.
CREATE or REPLACE PROCEDURE ADD_ORDER (
CUSTOMER_ID IN NUMBER
NEW_ORDER_ID OUT NUMBER
) AS
CURSOR get_max_order_no IS
SELECT MAX(order_no)
FROM ORDERS;
rec_ NUMBER := 0;
BEGIN
OPEN get_max_order_no;
FETCH get_max_order_no INTO rec_;
CLOSE get_max_order_no;
NEW_ORDER_ID := rec_ + 1;
INSERT INTO ORDERS
(ORDER_NO, REP_NO, CUST_NO, ORDER_DT, STATUS)
VALUES
(NEW_ORDER_ID, 36, CUSTOMER_ID, SYSDATE, 'C');
END ADD_ORDER;
I am new to oracle, comming from MySQL, and I am trying to get autoincrement to work in Oracle with a sequence and a trigger, so that it increments my field by one, each time I do an insert.
CREATE SEQUENCE proposals_seq MINVALUE 1
START WITH 1 INCREMENT BY 1 CACHE 10;
CREATE OR REPLACE TRIGGER proposals_before_insert
BEFORE INSERT
ON proposals
FOR EACH ROW
BEGIN
SELECT proposals_seq.nextval INTO :new.proposal_id FROM dual;
END;
But when I run the script, I get the error:
Error code 900, SQL state 42000: ORA-00900: invalid SQL statement
If I remove the ";" after the select statement, I get no error, until I try to insert data into the table, then I get this error:
INSERT INTO proposals (target_audience, line_of_business, activity_description, cost, comments, objectives_and_results)
VALUES ('test', 'test', 'test', 15, 'test', 'test');
Error code 4098, SQL state 42000: ORA-04098: trigger 'PROPOSALS_BEFORE_INSERT' is invalid and failed re-validation
I am using this version of Oracle:
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production
All the articles I have found about it, seems to be doing it like this and answers in here aswell: How to create id with AUTO_INCREMENT on Oracle?
Can it be because of my version of Oracle? Is there another way for me to autoincrement this? Or will I have to increment manually with the sequence in my sql?
My table looks like this:
CREATE TABLE proposals (
proposal_id INT NOT NULL,
target_audience VARCHAR2(50) NOT NULL,
line_of_business VARCHAR2(50),
activity_description VARCHAR2(250) NOT NULL,
cost DECIMAL(19, 4) NOT NULL,
comments VARCHAR2(250),
objectives_and_results VARCHAR2(250),
PRIMARY KEY (proposal_id)
);
I suspect the problem is that your client tool is reading every semicolon as the end of a command, causing PL/SQL code (which requires semicolons as statement terminators) to be incorrectly transmitted to the server.
When you remove the semicolon, the statement is correctly sent to the server, but you end up with an invalid object because the PL/SQL is incorrect.
I duplicated your problem on SQL Fiddle. Then I change the statement terminator to / instead of ; and changed the code to use a slash to execute each statement, and it worked without error:
CREATE TABLE proposals (
proposal_id INT NOT NULL,
target_audience VARCHAR2(50) NOT NULL,
line_of_business VARCHAR2(50),
activity_description VARCHAR2(250) NOT NULL,
cost NUMBER(19, 4),
comments VARCHAR2(250),
objectives_and_results VARCHAR2(250),
PRIMARY KEY (proposal_id)
)
/
CREATE SEQUENCE proposals_seq MINVALUE 1
START WITH 1 INCREMENT BY 1 CACHE 10
/
CREATE OR REPLACE TRIGGER proposals_before_insert
BEFORE INSERT ON proposals FOR EACH ROW
BEGIN
select proposals_seq.nextval into :new.proposal_id from dual;
END;
/
This is the code I use adapted to your table. It will work on any Oracle version but does not take advantage of the new functionality in 12 to set a sequence as an auto increment id
CREATE OR REPLACE TRIGGER your_schema.proposal_Id_TRG BEFORE INSERT ON your_schema.proposal
FOR EACH ROW
BEGIN
if inserting and :new.Proposal_Id is NULL then
SELECT your_schema.proposal_Id_SEQ.nextval into :new.Proposal_Id FROM DUAL;
end if;
END;
/
and usage is
INSERT INTO proposals (proposal_id,target_audience, line_of_business, activity_description, cost, comments, objectives_and_results)
VALUES (null,'test', 'test', 'test', 15, 'test', 'test');
Note that we are deliberately inserting null into the primary key to initiate the trigger.
I created SEQUENCE and Trigger in Oracle for my table.
my entity:
#Entity
#Table(name = "TBL_AUTHENTICATE")
public class UserSession implements Serializable {
#Id
#Column(name = "SESSIONID", nullable = false, length = 12)
private Long sessionId;
#Column(name = "USER_ID", nullable = false, length = 10)
private int user_id;
#Column(name = "TIME_TO_LIVE", nullable = false)
private Date time_to_live;
:-D
I try insert data within this table and set sessionId with my generated value and finally I understand the problem. That's my fault.
then I drom Sequence and Trigger from table.
My problem solved.
This is my sql code:
CREATE OR REPLACE TRIGGER PLACE_NO_TRIGGER
BEFORE INSERT or UPDATE ON UHA_LEASE
FOR EACH ROW BEGIN
INSERT INTO UHA_TEMPVAL SELECT PLACE_NO FROM UHA_RESHALL;
INSERT INTO UHA_TEMPVAL SELECT PLACE_NO FROM UHA_GENERAL;
IF(:NEW.PLACE_NO NOT IN UHA_TEMPVAL.TEMP_VALUE AND :NEW.PLACE_NO IS NOT NULL) THEN
RAISE_APPLICATION_ERROR(-10001, 'PLACE_NO MUST BE IN UHA_RESHALL OR IN UHA_GENERAL OR NULL');
END IF;
DELETE FROM UHA_TEMPVAL;
END;
This is giving these errors:
Error(4,27): PLS-00103: Encountered the symbol "UHA_TEMPVAL" when expecting one of the following: ( The symbol "(" was substituted for "UHA_TEMPVAL" to continue.
Error(4,69): PLS-00103: Encountered the symbol "THEN" when expecting one of the following: ) , and or as The symbol ")" was substituted for "THEN" to continue.
Can someone help me understand why these errors are occurring? It's especially odd because row 4 ends at column 60.
Thanks!
It looks like you're trying to enforce referential integrity from a child table to either of two parent tables. You don't really need to insert and delete from a temporary table; you can count how many rows match in either table:
CREATE OR REPLACE TRIGGER PLACE_NO_TRIGGER
BEFORE INSERT or UPDATE ON UHA_LEASE
FOR EACH ROW
DECLARE
CNT NUMBER;
BEGIN
SELECT COUNT(*)
INTO CNT
FROM (
SELECT PLACE_NO FROM UHA_RESHALL
WHERE PLACE_NO = :NEW.PLACE_NO
UNION ALL
SELECT PLACE_NO FROM UHA_GENERAL
WHERE PLACE_NO = :NEW.PLACE_NO
);
IF :NEW.PLACE_NO IS NOT NULL AND CNT = 0 THEN
RAISE_APPLICATION_ERROR(-10001,
'PLACE_NO MUST BE IN UHA_RESHALL OR IN UHA_GENERAL OR NULL');
END IF;
END;
/
You could choose to only run the query if the :NEW.PLACE_NO is not null but it probably won't make much difference if the columns are indexed.
Hi there I already seen other posts with the same error code, but I can't figure it out.
I have this table 'NOLEGGIO' created in this way:
CREATE TABLE NOLEGGIO(
idNoleggio INT PRIMARY KEY,
dataNoleggio DATE,
dataRestituzione DATE,
dataRestituito DATE,
CF CHAR(16) NOT NULL,
prezzo NUMBER(4),
--SEVERAL CONSTRAINTS...
All I want to do now is a trigger that sets a 'dataRestituzione' := :NEW.dataNoleggio + INTERVAL '3' DAY; (that means returnDate := :NEW.rentalDATE ) IF the date of membership is < than a specific date.
I show you my 'TESSERATO' table (tesserato stands for membership)
CREATE TABLE TESSERATO(
numTessera INT NOT NULL UNIQUE,
dataTesseramento DATE,
dataScadenza DATE,
CF CHAR(16) PRIMARY KEY,
-- CONSTRAINT...
If I execute the query outside my trigger (coming next) it works (because I have datas in the fields i'm looking at) but if I insert this query in the trigger, it doesn't work!
This is the trigger:
CREATE OR REPLACE TRIGGER TR_NOLEGGIO
BEFORE INSERT ON NOLEGGIO
FOR EACH ROW
DECLARE
DATAT DATE;
BEGIN
:NEW.idNoleggio := id_noleggio.NEXTVAL;
SELECT T.dataTesseramento INTO DATAT
FROM NOLEGGIO N JOIN TESSERATO T ON N.CF=T.CF
WHERE DATAT < TO_DATE('27/02/2014','DD/MM/YYYY');
/* Here I've even tried to do something like:
IF DATAT < TO_DATE.... THEN . But it doesn't work either.
However the query that actually works if I execute outside the trigger is the SELECT above.
*/
:NEW.dataRestituzione := :NEW.dataNoleggio + INTERVAL '3' DAY;
END;
/
It says No data Found error, while there are datas in the rows instead!! (In fact doing the select outside the trigger matches several rows).
It's definitely driving me crazy ! Cannot understand what I do wrong.
Thank you in advance for anyone that get involved into this.
Insert staments for the two tables
-- NOLEGGIO
INSERT INTO NOLEGGIO VALUES(001,'18-OTT-2013','20-OTT-2013',NULL,'P3SDTI85A15H501H',10);
INSERT INTO NOLEGGIO VALUES(002,'15-NOV-2013','19-NOV-2013',NULL,'CNTNDR89T42F839M',700);
--idRental,dateRental,dateReturn,dateReturned,SSN,price)
-- TESSERATO
INSERT INTO TESSERATO(dataTesseramento,dataScadenza,CF) VALUES('07-set-2013','07-set-2014','RDLVRT70M08F205K');
-- SEVERAL INSERTS MORE
-- N.B. the numTessera is made with a sequence in another trigger
New Answer Following Comments
I have put together a test script for this. The new code used for the trigger seems to work correctly updating the return date if a valid membership exists within the date requirements set. Feel free to just take the trigger code and discard the rest, I have just included this as it is what I have used to verify that the trigger performs an update when it should:
CAUTION: I am dropping tables in this test to make it rerunable, so i would only recommend using the full script in a test environment
/**************** R U N O N C E ********************/
--CREATE OR REPLACE SEQUENCE id_noleggio
-- MINVALUE 0
-- MAXVALUE 1000000000
-- START WITH 1
-- INCREMENT BY 1
-- CACHE 20;
/********************************************************/
/****************** R E R U N A B L E ****************/
drop table NOLEGGIO;
drop table TESSERATO;
CREATE TABLE NOLEGGIO(
idNoleggio INT PRIMARY KEY,
dataNoleggio DATE,
dataRestituzione DATE,
dataRestituito DATE,
CF CHAR(16) NOT NULL,
prezzo NUMBER(4));
CREATE TABLE TESSERATO(
numTessera INT NOT NULL UNIQUE,
dataTesseramento DATE,
dataScadenza DATE,
CF CHAR(16) PRIMARY KEY);
-- TESSERATO
INSERT INTO TESSERATO(numTessera, dataTesseramento, dataScadenza, CF) VALUES(1, '15-NOV-2013','15-NOV-2014','ABCDEFGHI0000001');
INSERT INTO TESSERATO(numTessera, dataTesseramento, dataScadenza, CF) VALUES(2, '01-MAR-2014','01-MAR-2015','ABCDEFGHI0000002');
-- SEVERAL INSERTS MORE
-- N.B. the numTessera is made with a sequence in another trigger
CREATE OR REPLACE TRIGGER TR_NOLEGGIO
BEFORE INSERT ON NOLEGGIO
FOR EACH ROW
DECLARE
CUT_OFF_DATE DATE := TO_DATE('27/02/2014','DD/MM/YYYY');
MEMBER_EXISTS VARCHAR2(1) := 'N';
DATAT DATE;
BEGIN
:NEW.idNoleggio := id_noleggio.NEXTVAL;
-- membership exists
SELECT 'Y', T.dataTesseramento
INTO MEMBER_EXISTS, DATAT
FROM TESSERATO T
WHERE T.CF = :NEW.CF
AND T.dataTesseramento < CUT_OFF_DATE;
-- if value returned from query above is not null...
if MEMBER_EXISTS = 'Y' then
:NEW.dataRestituzione := :NEW.dataNoleggio + INTERVAL '3' DAY;
end if;
exception
when no_data_found then
-- e.g. if there are no records in the TESSERATO table with the same CF value
null; -- no action required, this will just stop an error being flagged
END;
/
-- test trigger
-- should set dataRestituzione (a valid membership exists within date requirements)
INSERT INTO NOLEGGIO VALUES(004, '01-Mar-2014', NULL, NULL, 'ABCDEFGHI0000001', 20); -- should set dataRestituzione
-- should not set dataRestituzione (membership too recent)
INSERT INTO NOLEGGIO VALUES(004, '01-Mar-2014', NULL, NULL, 'ABCDEFGHI0000002', 30);
-- should not set dataRestituzione (no record of membership in TESSERATO table)
INSERT INTO NOLEGGIO VALUES(1, '18-OCT-2013', NULL, NULL, 'P3SDTI85A15H501H', 10);
INSERT INTO NOLEGGIO VALUES(2, '15-NOV-2013', NULL, NULL, 'CNTNDR89T42F839M', 700);
--idRental,dateRental,dateReturn,dateReturned,SSN,price)
-- look at results
select * from TESSERATO;
select * from NOLEGGIO;
I think that the key problem with the way that you were trying to do this before is that you were joining to the NOLEGGIO table to retrieve data that had not yet been inserted.
Previous Answer
Try chaining the line:
WHERE DATAT < TO_DATE('27/02/2014','DD/MM/YYYY');
to:
WHERE T.dataTesseramento < TO_DATE('27/02/2014','DD/MM/YYYY');
It looks like you are using this variable for a where condition before you have assigned a value to it i.e. it doesn't know the value if DATAT until the query has completed, but you are trying to use this value within the query.