Oracle SQL ORA-01403: no data found error - sql

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.

Related

Change number of Rows Affected by Update

I am trying to achieve here is to basically override 0 rows Updated, when UPDATE is issued in-case the actual PK/UK value doesn't exist in the table. This is what I have done:
Actual Table:
CREATE TABLE fdrgiit.vereine(
team numeric(10) primary key,
punkte int not null,
serie int not null
);
Dummy Table:
CREATE TABLE fdrgiit.dummyup
(
id numeric(1) PRIMARY KEY,
datetest timestamp
);
Inserted records in both the tables:
insert into vereine(team,punkte,serie) values(1, 50, 1);
insert into vereine(team,punkte,serie) values(2, 30, 1);
insert into vereine(team,punkte,serie) values(3, 25, 1);
insert into vereine(team,punkte,serie) values(4, 37, 2);
insert into dummyup values(1, now());
Created the following function and trigger:
create or replace function updateover()
returns trigger as
$BODY$
begin
if EXISTS (select 1 FROM vereine WHERE team = new.team ) then
RETURN NEW;
else
UPDATE fdrgiit.dummyup set datetest=now() where id=1;
RETURN NULL;
end if;
end;
$BODY$
LANGUAGE plpgsql;
create trigger update_redundancy
before update on vereine
for each row
execute procedure updateover() ;
But when I execute an UPDATE like this on the , I am still get 0 rows affected
update vereine set punkte=87 where team=5;
Kindly review and please suggest if this is something that can be done.
You cannot trigger anything with an UPDATE that does not affect row as triggers are only fired for affected rows.
But you could wrap your alternative UPDATE into a function:
CREATE OR REPLACE FUNCTION updateover()
RETURNS int AS
$func$
UPDATE dummyup
SET datetest = now()
WHERE id = 1
RETURNING 2;
$func$ LANGUAGE sql;
... and run your UPDATE nested like this:
WITH upd AS (
UPDATE vereine
SET punkte = 87
WHERE team = 5 -- does not exist!
RETURNING 1
)
SELECT 1 FROM upd
UNION ALL
SELECT updateover()
LIMIT 1;
db<>fiddle here
If no row qualifies for an UPDATE, then 1st outer SELECT 1 FROM upd returns no row and Postgres keeps processing the 2nd SELECT updateover(). But if at least one row is affected, the final SELECT is never executed. Exactly what you want.
This updates dummyup one time if the UPDATE on vereine does not affect any rows; never several times. But that's ok, since now() is STABLE for the duration of the transaction.
Related:
Return a value if no record is found

How to prevent invalid data insert SQL

In my project, I need to check conditions dynamically. To achieve this create table as follows.
CREATE TABLE myconditions
(
conditionid INT IDENTITY PRIMARY KEY CLUSTERED,
minvalue INT,
maxvalue INT,
result INT
)
and there data containing as follows,
insert into MyConditions (MinValue, MaxValue, Result)
values (10, 20, 1), (20, 30, 2), (null, 10, 3), (30, null, 3)
I use this table data to check the range of the age,
declare #age int = 25 --this represents user age
select *
from MyConditions
where #age > isnull(MinValue, #age - 1)
and #age <= isnull(MaxValue, #age)
but now the problem is, suppose if someone inserts an invalid range, such as values (5, 25, 4) i mean this is invalid, because in the database already have (10, 20, 1) this values. when the #age = 15 both condition will be through. so I need to prevent (5, 25, 4) this values addition. if someone needs to add this (5, 25, 4) range, This range values (10, 20, 1) should be deleted.
I insert those data into the database using an ASP.NET MVC application. How can I do this? In my project Is using Oracle. (in this question I used MS SQL sample code, but I need oracle)
This sort of data integrity validation is very difficult to implement in a robust and performative fashion.
For starters, much depends upon the definition of overlapping range. For instance it could be argued that all your sample data ranges are invalid: maxvalue = 10 overlaps with minvalue = 10, assuming bounds are testing with >= and <= which is default. Likewise, null bounds create complexity: if you have an existing range (30, null) is (40,50) valid?
So once you have sorted out your business logic there's the matter of implementing them. In Oracle we can do something with a compound trigger. For each row we store the ID of the inserted / updated row in an array. Then at the end of the statement we loop through the array and query the table in a cross join to compare the date ranges.
create or replace trigger myconditions_trg
for insert or update of minvalue, maxvalue
on myconditions
compound trigger
type condition_array is table of int
index by binary_integer;
conditions condition_array;
procedure validate_range (p_id in int) is
overlapping_range exception;
dummy char(1);
begin
begin
select null into dummy
from myconditions t1
, myconditions t2
where t1.conditionid = p_id
and t2.conditionid != p_id
and t1.minvalue != t2.minvalue
and (
t1.minvalue between t2.minvalue and t2.maxvalue
or
t1.maxvalue between t2.minvalue and t2.maxvalue
)
and rownum = 1;
raise overlapping_range;
exception
when no_data_found then
-- what we're hoping for, no overlaps found
null;
end;
exception
when overlapping_range then
raise_application_error(-20000,
'overlapping range for id #' || p_id);
end validate_range;
procedure validate_ranges is
l_id int;
begin
l_id := conditions.first;
loop
exit when l_id is null;
validate_range (l_id);
l_id := conditions.next(l_id);
end loop;
conditions.delete;
exception
when others then
conditions.delete;
raise;
end validate_ranges;
BEFORE EACH ROW is
begin
-- store id to validate
conditions(:new.conditionid) := 1;
end before each row;
AFTER STATEMENT is
begin
validate_ranges;
end after statement;
end myconditions_trg;
This trigger doesn't attempt to handle multi-user scenarios. To be honest there's not much we can do to prevent two different sessions creating overlapping ranges. The only thing which is guaranteed is to lock the whole table, but that may not be desirable.
If you're interested I have a published a demo on Oracle LiveSQL (free login required, sorry!). Find it here.
You need to use either a trigger or a user-defined function for this.
A simple check constraint can only check the values in a single. Frankly, I think a trigger would be the more common approach.
The exact syntax for either depends on the database -- and you have specified two of them -- so a more detailed answer isn't feasible.

PostgreSQL inherited table and insert triggers

I'm trying to follow the advice here to create a vertically partitioned table for storing time series data.
So far, my schema looks like this:
CREATE TABLE events
(
topic text,
t timestamp,
value integer,
primary key(topic, t)
);
CREATE TABLE events_2014
(
primary key (topic, t),
check (t between '2014-01-01' and '2015-01-01')
) INHERITS (events);
Now I'm trying to create an INSTEAD OF INSERT trigger so that events can be inserted on the events table and the row will end up in the right sub-table. But the documentation says that INSTEAD OF INSERT triggers can only be created on views, not tables (or subtables):
CREATE OR REPLACE FUNCTION insert_events () RETURNS TRIGGER AS $insert_events$ BEGIN
IF new.t between '2014-01-01' and '2015-01-01' THEN
INSERT INTO events_2014 SELECT new.*;
...
END IF
RETURN NULL;
END;
$insert_events$ LANGUAGE PLPGSQL;
CREATE TRIGGER insert_events INSTEAD OF INSERT ON events FOR EACH ROW EXECUTE PROCEDURE insert_events();
ERROR: "events" is a table
DETAIL: Tables cannot have INSTEAD OF triggers.
What's the right way of doing this?
You need to declare BEFORE INSERT triggers.
Documentation on partitioning is a great source of knowledge in this matter and is full of examples.
Example function from docs
CREATE OR REPLACE FUNCTION measurement_insert_trigger()
RETURNS TRIGGER AS $$
BEGIN
IF ( NEW.logdate >= DATE '2006-02-01' AND
NEW.logdate < DATE '2006-03-01' ) THEN
INSERT INTO measurement_y2006m02 VALUES (NEW.*);
ELSIF ( NEW.logdate >= DATE '2006-03-01' AND
NEW.logdate < DATE '2006-04-01' ) THEN
INSERT INTO measurement_y2006m03 VALUES (NEW.*);
...
ELSIF ( NEW.logdate >= DATE '2008-01-01' AND
NEW.logdate < DATE '2008-02-01' ) THEN
INSERT INTO measurement_y2008m01 VALUES (NEW.*);
ELSE
RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!';
END IF;
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
Example trigger from docs
CREATE TRIGGER insert_measurement_trigger
BEFORE INSERT ON measurement
FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();
Returning NULL from BEFORE trigger will keep the parent table empty.

Why doesn't this trigger work properly?

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.

check age trigger from two separate tables in oracle sql

I need to create a trigger that checks that the age (in years) of the participant is higher than the minimum age of the race he/ she participates in. To do this I need to get information from the participants table and the race table. I need to add this trigger to the results table
here are my tables:
CREATE TABLE race
(
RaceID int,
Location varchar2 (60),
StartDate Date,
RaceTime Date,
MinAge int,
);
CREATE TABLE participant
(
ParticipantID int,
"Name" varchar2 (60),
DateOfBirth date,
Sex varchar2 (1),
Age int
);
CREATE TABLE results
(
RaceID int not null unique,
ParticipantID int not null unique,
position number (2)
);
here is what I have created so far:
create or replace TRIGGER dob_trg
BEFORE INSERT OR UPDATE ON RESULTS
FOR EACH ROW
DECLARE
RACE_MINAGE NUMBER;
PARTICIPANT_AGE NUMBER;
BEGIN
SELECT RACE.MINAGE, PARTICIPANT.AGE
INTO RACE_MINAGE, PARTICIPANT_AGE
FROM RACE PARTICIPANT
WHERE RACE.RACEID = :NEW.RACEID;
IF : NEW.AGE < : NEW.MINAGE THEN
RAISE_APPLICATION_ERROR(-20000, 'Participant too young!');
END IF;
enddob_trg;
However I'm getting the following error
Error(9,6): PLS-00103: Encountered the symbol ":"
Any help greatly appreciated!
Erros :
Table race - there is an extra comma at the end of you MinAge col
CREATE TABLE race
(
RaceID int,
Location varchar2 (60),
StartDate Date,
RaceTime Date,
MinAge int,
);
Inside trigger errors:
IF : NEW.AGE < : NEW.MINAGE THEN should be
IF :NEW.AGE < :NEW.MINAGE THEN
Your create trigger is on table RESULTS but the action is made on a column called AGE that belongs to the participant table .
Fix this logic before going forward.
One more error inside your trigger :
FROM RACE PARTICIPANT
add a comma between tables
Fixed trigger
-here i what i think your trigger should look like !
Compile it and give it a try
See my logic :
This query will store the values of minage using the predicate "NEW.ParticipantID"
SELECT RACE.MINAGE, PARTICIPANT.AGE
INTO RACE_MINAGE, PARTICIPANT_AGE
FROM RACE, PARTICIPANT
WHERE RACE.RACEID = :NEW.ParticipantID;
This part :- if the RACE_MINAGE is bigger or equal then
IF :NEW.AGE <= RACE_MINAGE THEN
RAISE_APPLICATION_ERROR(-20000, 'Participant too young!');
END IF;
Complete trigger
CREATE OR REPLACE TRIGGER dob_trg
BEFORE INSERT OR UPDATE ON participant
FOR EACH ROW
DECLARE
RACE_MINAGE NUMBER;
PARTICIPANT_AGE NUMBER;
BEGIN
SELECT RACE.MINAGE, PARTICIPANT.AGE
INTO RACE_MINAGE, PARTICIPANT_AGE
FROM RACE, PARTICIPANT
WHERE RACE.RACEID = :NEW.ParticipantID;
exception
when NO_DATA_FOUND then
NULL; -- or do something else you choose
IF :NEW.AGE <= RACE_MINAGE THEN
RAISE_APPLICATION_ERROR(-20000, 'Participant too young!');
END IF;
end;
IF : NEW.AGE < : NEW.MINAGE THEN
You should remove the whitespace after the : symbol.
IF :NEW.AGE < :NEW.MINAGE THEN
Also, it looks like you're missing a comma in your FROM clause:
FROM RACE PARTICIPANT
But that wouldn't make your code work yet, because you're testing the wrong fields in your IF statement. Also, you're not filtering the participantId.