SQL Trigger creating trouble - sql

I have the movie executive table movieexec with the columns
(name varchar2(20), address varchar2(20), cert# number(10), networth float)
I want to create a trigger which calculates the average networth, and if it exceeds some amount say 400000 then further insertion should not be possible and a error message should be displayed.
I implemented the following code:
CREATE OR REPLACE TRIGGER pronet
AFTER INSERT
ON movieexec
FOR EACH ROW
DECLARE netavg float;
BEGIN
SELECT AVG(networth) INTO netavg FROM movieexec;
IF(netavg>400000) THEN
RAISE_APPLICATION_ERROR(-20000,'average limit reached, cannot insert');
ENDIF;
END
But the below error occurs
ERROR at line 7: PLS-00103: Encountered the symbol ";" when expecting one of the following:
if
5. DECLARE netavg float;
6. BEGIN
7. **SELECT AVG(networth) INTO netavg FROM movieexec;**
8. IF(netavg>400000) THEN
9. RAISE_APPLICATION_ERROR(-20000,'average limit reached, cannot insert');
kindly assist.

Create table as
CREATE TABLE movieexec
(
name VARCHAR2 (20),
address VARCHAR2 (20),
cert_no NUMBER (10),
networth FLOAT
);
and create trigger as
CREATE OR REPLACE TRIGGER pronet
AFTER INSERT
ON movieexec
FOR EACH ROW
DECLARE
netavg FLOAT;
BEGIN
SELECT AVG (networth)
INTO netavg
FROM movieexec;
IF (netavg > 400000)
THEN
raise_application_error (-20000,
'average limit reached, cannot insert'
);
END IF;
END;
/

There is no safe way to do this with a trigger, as there will always be a way to subvert it.
In Oracle EE you might create a fast refresh on commit materialised view to store the aggregation, and place a check constraint on it.
Also, never use the SYS or SYSTEM accounts for creating your own objects.

Related

Insert into a table using a stored procedure

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;

SQL - Trigger is invalid and failed re-validation

When a user creates an application for the table 'application' I want a copy placed into the table 'application_history'. I've tried creating a trigger and get the error that is in the title.
create or replace TRIGGER create_history AFTER INSERT ON application
FOR EACH ROW
DECLARE
BEGIN
insert into application_history values (:new.arn, :new.srn, :new.job_id, :new.application_status);
END;
Any help will be much appreciated.
As Justin suspected, you seem to be having more than 4 columns in application_history. Either provide values for all columns or specify columns lie 'insert into table1 (col1, col2) values (1,2)'
It worked for me after adding extra column 'hist_id'(just to give you an idea) ...
create table application (arn number, srn number, job_id number, application_status char(1) );
create table application_history (hist_id number, arn number, srn number, job_id number, application_status char(1) );
create or replace TRIGGER create_application_history AFTER INSERT ON application
FOR EACH ROW
DECLARE
BEGIN
insert into application_history values (1, :new.arn, :new.srn, :new.job_id, :new.application_status);
END;
/
I think you need a / at the end. So:
create or replace TRIGGER create_history AFTER INSERT ON application
FOR EACH ROW
DECLARE
BEGIN
insert into application_history values (:new.arn, :new.srn, :new.job_id, :new.application_status);
END;
/

"Table is mutating" error occurs when inserting into a table that has an after insert trigger

I'm trying to create an after insert trigger that inserts the details of a purchase made into a purchases log table and, as well, update the total cost column in the purchases table to add GCT of 16.5% to the total. The two tables have these columns:
CREATE TABLE purchases
(
p_Id INTEGER PRIMARY KEY,
product VARCHAR(25) ,
quantity INTEGER ,
unit_Cost FLOAT ,
total_Cost FLOAT
);
CREATE TABLE purchases_log
(
event_Date DATE ,
p_Id INTEGER PRIMARY KEY,
description varchar(75)
);
The trigger I'm trying to use is:
CREATE OR REPLACE TRIGGER PURCHASES_AUDIT
AFTER INSERT ON purchases
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
DECLARE TAX FLOAT;
BEGIN
INSERT INTO purchases_log
VALUES(SYSDATE,:NEW.p_Id,'INSERT INTO PURCHASES TABLE');
tax := 1.165;
UPDATE purchases SET total_Cost = quantity * unit_Cost;
UPDATE purchases SET total_Cost = total_Cost*tax;
END PURCHASES_AUDIT;
/
however when trying to run an insert on the purchase table oracle gives me this error
ERROR at line 1:
ORA-04091: table PURCHASES is mutating, trigger/function may
not see it
ORA-06512: at "PURCHASES_AUDIT", line 9
ORA-04088: error during execution of trigger 'PURCHASES_AUDIT'
Please Help
Don't update the table on which the trigger is defined.
It sounds like you want a before insert trigger, not an after insert trigger, that modifies the :new pseudo-record. If I understand your intention correctly
CREATE OR REPLACE TRIGGER PURCHASES_AUDIT
BEFORE INSERT ON purchases
FOR EACH ROW
DECLARE
TAX FLOAT;
BEGIN
INSERT INTO purchases_log
VALUES(SYSDATE,:NEW.p_Id,'INSERT INTO PURCHASES TABLE');
tax := 1.165;
:new.total_Cost = :new.quantity * :new.unit_Cost * tax;
END PURCHASES_AUDIT;
As an aside, do you really, really want to use a float rather than a number? Do you fully understand the approximate nature of floating point numbers? I've never come across anyone working with money that wanted to use a float.
As a point of clarification: before triggers allow you to update the :new values, after triggers do not. The :new values if after triggers will always contain the final value.

Time constraint on a sql trigger

I have the following table called employees
create table employees
(
eno number(4) not null primary key,
ename varchar2(30),
zip number(5) references zipcodes,
hdate date
);
And I'm trying to set a trigger on the table that will fire before an update or delete that will check the system time first to see if the time is between 12:00-13:00, if it is it will allow the insertion, otherwise prevent it.
My guess is so far:
CREATE or REPLACE TRIGGER twelve_one
BEFORE INSERT or Update ON employees
BEGIN
IF
....
ELSE
....
END;
But that's how far I've gotten into unfortunately. Can someone please help me to retrieve the system time first? Then how can I set up that IF ELSE block? And finally How to abort the transaction/insertion/update?
I'm using Oracle SQL Developer 4.02.15
Many Thanks
I assume you must declare temporary variable to set time now, and then compare the temporary variable.
CREATE OR REPLACE TRIGGER TWELVE_ONE
BEFORE INSERT OR UPDATE
ON EMPLOYEES
FOR EACH ROW
DECLARE
V_DATE VARCHAR2 (10);
BEGIN
SELECT TO_CHAR (SYSDATE, 'hh24:mi:ss') INTO V_DATE FROM DUAL;
IF (V_DATE >= '12:00:01' AND V_DATE < '13:00:00')
THEN
INSERT INTO TABLE ..
ELSE
UPDATE TABLE...
END IF;
END;

Select Command in a procedure in SQL (ORACLE)

I have this piece of code:
CREATE OR REPLACE PROCEDURE MOSTRA_LOG (P_CODIGO_CLIENTE CLIENTE.CODIGO_CLIENTE%TYPE,N_LOG OUT, CODIGO_CLIENTE OUT, EMAIL OUT,TELEFONE OUT, NOME OUT, TIPO OUT) IS
BEGIN
SELECT (N_LOG, CODIGO_CLIENTE, EMAIL ,TELEFONE, NOME, TIPO) FROM LOG WHERE
(CODIGO_CLIENTE=P_CODIGO_CLIENTE);
END;
I know a procedure isn't the optimal thing to do but it is required in my case. It complains about an into clause that needs to be added, however I have nothing to "select into".
All I want basically is to show on the screen everything from the table LOG with the CODIGO_CLIENTE sent as a parameter.
EDIT:
After a suggestion I changed it to this:
CREATE OR REPLACE PROCEDURE MOSTRA_LOG (P_CODIGO_CLIENTE CLIENTE.CODIGO_CLIENTE%TYPE, N_LOGO OUT INTEGER, CODIGO_CLIENTEO OUT INTEGER, EMAILO OUT VARCHAR(30), TELEFONEO OUT NUMERIC(9,0), NOMEO OUT VARCHAR(50), TIPOO OUT VARCHAR(20)) IS
BEGIN
SELECT (N_LOG into N_LOGO, CODIGO_CLIENTE into CODIGO_CLIENTEO, EMAIL into EMAILO, TELEFONE into TELEFONEO, NOME into NOMEO, TIPO into TIPOO) FROM LOG WHERE (CODIGO_CLIENTE=P_CODIGO_CLIENTE);
END;
It's still going wrong saying it expects a symbol when it encountered "(".
Try this way using collections:
First you must create the collection where you will store your LOG query results:
CREATE TYPE LOG_OBJECT AS OBJECT
(CODIGO_CLIENTEO INTEGER,
EMAILO VARCHAR (30),
TELEFONEO NUMERIC (9, 0),
NOMEO VARCHAR (50),
TIPOO VARCHAR (20));
CREATE TYPE LOG_TABLE AS TABLE OF LOG_OBJECT;
Later you create your procedure like this:
CREATE OR REPLACE PROCEDURE MOSTRA_LOG (P_CODIGO_CLIENTE CLIENTE.CODIGO_CLIENTE%TYPE, LOG_T OUT LOG_TABLE) IS
BEGIN
SELECT LOG_OBJECT(N_LOG, CODIGO_CLIENTE, EMAIL ,TELEFONE, NOME, TIPO) BULK COLLECT INTO LOG_T FROM LOG WHERE
(CODIGO_CLIENTE=P_CODIGO_CLIENTE);
END;
When procedure is created you can use the collection like it was a table. For example below anonymus block prints total of rows of your query on LOG table:
DECLARE
LOG_T LOG_TABLE;
total NUMBER;
BEGIN
mostra_log (LOG_T); --invoke the procedure and fill the collection
SELECT COUNT (*) INTO total FROM TABLE (LOG_T); -- get count of collection returned
DBMS_OUTPUT.put_line (total);
END;