I am getting an SQL error that makes no sense to me. I created a table called SUBORDER using this command:
CREATE TABLE SUBORDER (
OrderNo int,
SuborderNo int,
ReqShipDate date,
ActualShipDate date,
BranchName varchar(50),
primary key (OrderNo, SuborderNo),
foreign key (OrderNo) references ORDERS(OrderNo) on delete set null,
foreign key (BranchName) references BRANCH(BranchName) on delete set null
);
As you can see, ReqShipDate and ActualShipDate were declared as dates. But then when I run this code:
create or replace trigger shipment_late
before update
on SUBORDER
for each row
declare
reqdate DATE;
actualdate DATE;
begin
select s.ReqShipDate, s.ActualShipDate into reqdate, actualdate
from SUBORDER s
where ( s.OrderNo = :new.OrderNo ) and ( s.SuborderNo = :new.SuborderNo );
if reqdate > actualdate then
insert into LATE_SHIPMENT
select * from SUBORDER s
where ( s.OrderNo = :new.OrderNo ) and ( s.SuborderNo = :new.SuborderNo );
end if;
end;
the SQL console tells me:
Error(12,11): PL/SQL: ORA-00932: inconsistent datatypes: expected
NUMBER got DATE
Why is it expecting a number?
It appears that you just want
create or replace trigger shipment_late
before update
on SUBORDER
for each row
begin
if :new.ReqShipDate > :new.ActualShipDate
then
insert into LATE_SHIPMENT( <<list of columns>> )
values( :new.OrderNo, :new.SuborderNo, ... );
end if;
end;
A couple things to be aware of
It's always a good idea to explicitly list out the columns if you're doing an insert statement. Otherwise, you're relying on an implicit order that isn't going to be obvious to someone reading your code and that creates quite a bit of potential for errors. Listing out the columns, at a minimum, documents what values you are putting where. In this example, it seems unlikely that the late_shipment table has exactly the same set of columns in suborder defined in exactly the same order.
In general, a row-level trigger on a table (SubOrder in this case) cannot query that table. Doing so will generally raise a mutating table exception. That doesn't appear to be necessary here, however. You just need to reference the various attributes of the :new pseudo-record.
Related
order.date must be between item.date_from and item.date_to... what are the different ways of doing that?
CREATE TABLE "item" (
"id" SERIAL PRIMARY KEY,
"date_from" DATE NOT NULL,
"date_to" DATE NOT NULL
);
CREATE TABLE "order" (
"id" SERIAL PRIMARY KEY,
"date" DATE NOT NULL
);
CREATE TABLE "order_item" (
"order" INTEGER NOT NULL REFERENCES "order",
"item" INTEGER NOT NULL REFERENCES "item"
);
Check constraints work on simple expressions. For example, a simple sanity check on the order: check( date > '2010-01-01'). There's also exclusion constraints which check no two rows have the same value as defined by the exclusion. But, with the exception of foreign key constraints, constraints don't query other tables.
You can solve this with a trigger on insert and update, and I'll go into that below, but its better to solve this sort of problem with referential integrity. However, I can't think of a way to do that.
You can make a view of available items for the order. Here $1 is the date of the order.
create temporary view items_available_to_order
select *
-- pluralize table names to avoid conflicting with keywords and columns
from items
-- date_from and date_to has become a single daterange when_available
where items.when_available #> $1
Then only insert items from that view.
If you want to go the trigger route (you can do both) write a function which checks whether an order's item is valid. It either raises an exception or returns a trigger. new is the inserted row, or the row after an update.
I changed some of the table and column names and types to avoid common pitfalls.
create function check_item_order_is_valid()
returns trigger
language 'plpgsql'
as $body$
declare
item_is_available boolean;
begin
select
items.when_available #> orders.ordered_on into item_is_available
from item_orders
join items on items.id = new.order_id
join orders on orders.id = new.item_id;
if( not item_is_available) then
raise exception 'Item #% is not available for order #%',
new.item_id, new.order_id;
end if;
return new;
end
$body$
Then define a trigger to call the function when rows are inserted or updated in the item/order table.
create trigger check_item_orders
before insert or update
on item_orders
for each row
execute function check_item_order_is_valid();
Demonstration.
What if the valid range of an item changes? You need an update trigger on item to check that its orders are still valid. Maybe. Depends on your business logic.
A test example:
CREATE OR REPLACE FUNCTION public.item_date()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
DECLARE
order_date date;
from_date date;
to_date date;
BEGIN
select into order_date "date" from "order" where id = new.order;
select into from_date, to_date date_from, date_to from item where id = new.item;
--Use date range to test whether order date is in item date range.
if order_date <# daterange(from_date, to_date, '[]') then
return new;
else
return null;
end if;
END;
$function$
create trigger item_date_check before insert or update on order_item for each row execute function item_date();
insert into item values (1, '09/01/2021', '10/31/2021');
insert into item values (2, '07/01/2021', '08/31/2021');
insert into "order" values (1, '09/05/2021');
insert into order_item values (1, 1);
NOTICE: Order date 2021-09-05, from_date 2021-09-01, to_date 2021-10-31
INSERT 0 1
--Returning NULL causes the INSERT not to happen.
insert into order_item values (1, 2);
NOTICE: Order date 2021-09-05, from_date 2021-07-01, to_date 2021-08-31
INSERT 0 0
Note that I had to quote "order" as that is a reserved word also. You might to take a look at Key(reserved) Words. For range functions/operators see Range Function. For general information on range(s) see Range Types
From what I have read about triggers and how they work, I thought that this trigger would insert data into the relations related to my table phonenumber after I insert values into it. I'm using dbms_random to create a random 5 digit usageID not already in the usage table (or atleast that's what I had thought it would do).
create or replace TRIGGER addPhoneLine
AFTER INSERT ON phoneNumber
REFERENCING NEW AS NEW
FOR EACH ROW
DECLARE
primNum varchar(12);
acctNum numeric(5);
NEWusageID_new varchar(5);
BEGIN
if :new.primaryNumber is not NULL then
select acctID, primaryNumber into acctNum, primNum
from account A
where A.primaryNumber = :new.primaryNumber;
select to_char(round(dbms_random.value(10000, 99999),0)) into
NEWusageID_new from dual
minus
select usageID from usage;
INSERT INTO acct_num VALUES
(acctNum, primNum, :new.phonenumber);
INSERT INTO phone_usage VALUES
(NEWusageID_new, :new.phonenumber);
end if;
END;
But it throws the following errors when I attempt to insert into the phoneNumber table:
ORA-01403: no data found
ORA-06512: at "ADDPHONELINE", line 9
ORA-04088: error during execution of trigger 'ADDPHONELINE'
The relevant tables were created as follows:
create table phoneNumber(phoneNumber varchar(12) PRIMARY KEY, primaryNumber varchar(12));
create table acct_num(acctID numeric(5) references ACCOUNT, primaryNumber varchar(12) references ACCOUNT, phoneNumber varchar(12) references phoneNumber);
create table phone_usage(usageID varchar(5) references USAGE, phoneNumber varchar(12) references PHONENUMBER)
Your trigger is based on an insert on table phoneNumber and the error "No Data Found" is thrown when a SELECT INTO is used and it doesnt find any information to insert.
So the problem must be this statement.
select acctID, primaryNumber into acctNum, primNum
from account A
where A.primaryNumber = :new.primaryNumber;
Are you certain that the :new.primaryNumber exists in the account table when this trigger is active?
Maybe you are only populating the account table after this insert is complete?
The below query is returning no rows, as per error description you posted. Either your :new.primaryNumber is incorrect or there is no matching record in the account table.
Begin
select acctID, primaryNumber into acctNum, primNum
from account A
where A.primaryNumber = :new.primaryNumber;
exception when others then
insert your favorite logging method here logging out your :new.primaryNumber
end;
I show you here what will happen when dbms_random gives a value (NEWusageID_new), which exists in the usage:
DECLARE
i NUMBER;
BEGIN
SELECT 15 INTO i FROM DUAL
MINUS
SELECT 15 FROM DUAL;
END;
ORA-01403: no data found
ORA-06512: in line 4
Use a sequence instead.
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.
I've two tables purchases and customers, I need to update visits_made (number) in customers if time of purchase ptime (date) in purchases table is different from the already existing last_visit (date) in customers table.
Here is the trigger that I'm trying and I'm doing something terribly and shamefully wrong.
create or replace trigger update_visits_made
after insert on purchases
for each row
declare new_date purchases.ptime%type;
begin
select ptime into new_date
from purchases
where purchases.ptime = :new.ptime;
if new_date = customers.last_visit then
new.last_visit=old.last_visit;
else
update customers
set visits_made=visits_made+1
where purchases.ptime=:new.ptime;
end if;
end;
/
show errors
Can anybody please tell me where I'm going wrong?
I get following errors
LINE/COL ERROR
10/15 PLS-00103: Encountered the symbol "=" when expecting one of the
following:
:= . ( # % ;
11/1 PLS-00103: Encountered the symbol "ELSE"
16/1 PLS-00103: Encountered the symbol "END"
This is a scalar assignment in PL/SQL:
new.last_visit = old.last_visit;
Not only should this be done with := but new and old should have colons before their names:
:new.last_visit := :old.last_visit;
Once you have fixed that problem, then the update will pose an issue:
update customers
set visits_made=visits_made+1
where purchases.ptime = :new.ptime;
It is unclear to me what this is supposed to be doing, so I can't suggest anything, other than pointing out that purchases is not defined.
I think somehow i get your requirement. Basically its a ticker which count the vists of user based on Login dates. I have written a snippet below which replicates the same scenario as mentioned Above. Let me know if this helps.
-- Table creation and insertion script
CREATE TABLE PURCHASES
(
P_ID NUMBER,
P_TIME DATE
);
INSERT INTO PURCHASES
SELECT LEVEL,SYSDATE+LEVEL FROM DUAL
CONNECT BY LEVEL < 10;
CREATE TABLE CUSTOMERS
(
P_ID NUMBER,
P_VISITS NUMBER
);
INSERT INTO CUSTOMERS
SELECT LEVEL,NULL FROM DUAL
CONNECT BY LEVEL < 10;
-- Table creation and insertion script
--Trigger Script
CREATE OR REPLACE TRIGGER update_purchase BEFORE
INSERT ON purchases FOR EACH row
DECLARE
new_date purchases.p_time%type;
BEGIN
BEGIN
SELECT A.P_TIME
INTO new_date
FROM
(SELECT p_time,
ROW_NUMBER() OVER(PARTITION BY P_ID ORDER BY P_TIME DESC) RNK
-- INTO new_date
FROM purchases
WHERE purchases.p_id = :new.p_id
)a
WHERE A.RNK =1;
EXCEPTION WHEN OTHERS THEN
RETURN;
END;
IF :NEW.P_TIME <> new_date THEN
UPDATE customers
SET P_VISITS =NVL(P_VISITS,0)+1
WHERE p_id=:new.p_id;
END IF;
END;
--Trigger Script
--Testing Script
INSERT INTO PURCHASES VALUES
(9,TO_DATE('12/11/2015','MM/DD/YYYY'));
--Testing Script
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.