Limit data input based on count of instances in table - sql

Oracle 12c.
I currently have a table to hold patient visits containing a physician id, patient id, and data/time of visit.
I would like to create a constraint that, upon data entry, checks whether a specific physician has 5 appointments in that given day. If the physician does have 5 appointments, no additional appointment can be added.
Is there any way to do this other than using a stored procedure for entry?
If I were to use a stored procedure (as opposed to a trigger due issues declaring a variable) I receive the following error: Error(4,8): PLS-00103: Encountered the symbol "UPDATE" when expecting one of the following: := . ( # % ; not null range default character
I am unsure if this is because I can't use a BEFORE UPDATE on a procedure. Any thoughts?
CREATE OR REPLACE PROCEDURE doc_apt_limit_5
IS
v_visit_count
BEFORE UPDATE OR INSERT ON aa_patient_visit
FOR EACH ROW
BEGIN
SELECT (COUNT(*)) INTO v_visit_count
FROM aa_patient_visit
WHERE physid = :NEW.physid
GROUP BY physid, visittime;
IF v_visit_count > 4 THEN
RAISE_APPLICATION_ERROR(-20001, 'physician is fully booked on this date');
END IF;
END;

Go with trigger. Probably the best solution in this scenario.
CREATE OR REPLACE TRIGGER doc_apt_limit_5 BEFORE
UPDATE OR
INSERT ON aa_patient_visit FOR EACH ROW
DECLARE v_visit_count PLS_INTEGER;
BEGIN
SELECT COUNT(*)
INTO v_visit_count
FROM aa_patient_visit
WHERE physid = :NEW.physid
GROUP BY physid,
visittime;
IF v_visit_count > 4 THEN
RAISE_APPLICATION_ERROR(-20001, 'physician is fully booked on this date');
END IF;
END;

Related

Creating a Trigger that runs on two tables

I have two tables, COPY and BORROW. Their columns are:
COPY (
Copy_id,
Bk_id,
Loc_id,
Opinion
)
and
BORROW (
Cus_evo,
B_Date,
R_Date,
Fee,
Copy_id,
Cus_id
)
I want to configure a trigger that ensures Copies that stored in a Exact location (London) (from Loc-id column of COPY table) cannot be Borrowed in December (from B_Date column of BORROW table).
I have created the following trigger:
CREATE OR REPLACE TRIGGER BORROW_TRIGGER
BEFORE INSERT ON BORROW FOR EACH ROW BEGIN
IF(TO_CHAR(TO_DATE(:NEW.B_Date, 'DD-MMM-YYYY'),'MMM'= 'DEC')
AND :NEW.Loc_id='LC0001')
THEN RAISE_APPLICATION_ERROR(-20669,'CANNOT BORROW BOOKS FROM LONDON STORE DURING MONTH DECEMBER');
END IF;
END;
/
The trigger is not created and have errors please cloud give me correct trigger for this??
Error i have experiencing
Errors: TRIGGER BORROW_TRIGGER
Line/Col: 3/5 PLS-00049: bad bind variable 'NEW.LOC_ID'
Since you only need to perform the check when b_date is in December, it's more efficient to add this as a when condition at the top of the trigger. This also simplifies the trigger logic.
create or replace trigger borrow_check_trg
before insert on borrow
for each row
when (to_char(new.b_date,'MM') = '12')
declare
l_loc_id copy.loc_id%type;
begin
select c.loc_id into l_loc_id
from copy c
where c.copy_id = :new.copy_id;
if l_loc_id = 'LC0001' then
raise_application_error(-20669, 'Books cannot be borrowed from the London store during December');
end if;
end;
You need to query the COPY table to get the field you need:
CREATE OR REPLACE TRIGGER BORROW_BI
BEFORE INSERT ON BORROW
FOR EACH ROW
DECLARE
strLoc_id COPY.LOC_ID%TYPE;
BEGIN
SELECT LOC_ID
INTO strLoc_id
FROM DUAL
LEFT OUTER JOIN COPY c
ON c.COPY_ID = :NEW.COPY_ID;
IF TO_CHAR(TO_DATE(:NEW.B_Date, 'DD-MMM-YYYY'), 'MMM') = 'DEC' AND
strLoc_id = 'LC0001'
THEN
RAISE_APPLICATION_ERROR(-20669,'CANNOT BORROW BOOKS FROM LONDON STORE DURING MONTH DECEMBER');
END IF;
END BORROW_BI;
You can check out the existence by using a Select statement with COUNT aggregation from the other table(copy) through use of common column (copy_id) among tables such as
CREATE OR REPLACE TRIGGER Trg_Borrow_Trigger_BI
BEFORE INSERT ON borrow
FOR EACH ROW
DECLARE
v_exists INT;
BEGIN
SELECT COUNT(*)
INTO v_exists
FROM copy
WHERE copy_id = :NEW.copy_id
AND loc_id = 'LC0001'
AND TO_CHAR( :NEW.b_Date, 'MM' ) = '12';
IF v_exists > 0 THEN
RAISE_APPLICATION_ERROR(-20669,
'CANNOT BORROW BOOKS FROM LONDON STORE DURING MONTH DECEMBER');
END IF;
END;
/
where
TO_DATE() conversion is superfluous
to prepend loc_id with :NEW is not possible, since the trigger is created for
the table borrow has not this column, while copy table has.

"If" in a trigger comparing two columns from 2 different tables- error

Im trying to create a trigger when updating table 'test' to make sure a value in a column is not greater than another one from a different table. But I get this error on Oracle Apex: ORA-24344: success with compilation error
'test' is a table and 'chestionar' is a second one, so I want to launch that error when I insert a value in 'punctaj' which is greater than the 'punctaj_max'. And the id of the both tables must be the same . What should I modify?
here is my code:
CREATE OR REPLACE trigger trg_a
BEFORE UPDATE on test
begin
if test.punctaj > chestionar.punctaj_max and test.id=chestionar.id then
raise_application_error(234,'error, the value is grater than maximum of that id');
end if;
end;
I think the logic you want is:
create or replace trigger trg_a
before update on test
for each row
declare
p_punctaj_max chestionar.punctaj_max%type;
begin
select punctaj_max into p_punctaj_max from chestionar c where c.id = :new.id;
if :new.punctaj > p_punctaj_max then
raise_application_error(234, 'error, the value is grater than maximum of that id');
end if;
end;
/
The idea is to recover the value of punctaj_max in table chestionar for the id of the row that is being updated in test (note that this implicitely assumes that there cannot be multiple matching rows in chestionar). We can then compare that to the value being updated, and raise the error if needed.
You have three (I think) issue in your code:
You should apply trigger at time of insert also.
you need to query the table chestionar and then compare the value.
the error number in raise_application_error should be negative and between -20999 and -20000.
So, I would rewrite your code as follows:
create or replace trigger trg_a
before update or insert on test
for each row
declare
lv_cnt number := 0;
begin
select count(1) into lv_cnt
from chestionar c
where c.id = :new.id
and :new.punctaj > c.punctaj_max;
if lv_cnt > 0 then
raise_application_error(-20234, 'error, the value is grater than maximum of that id');
end if;
end;
/

SQL - Count number of rows in a table before updating it

Im doing a college project on Oracle SQL and I keep getting the error "table (...) is mutating, trigger/function may not see it" when I try to update a table. My participateTeams table has 3 columns (eventName varchar(), teamId number, teamPlace number).
The Objective here is that when I update the teamPlace of a given team in an event, the place MUST be lower or equal than the number of participant teams in that same event.
My trigger is as follows:
Before update on participateTeams
for each row
Declare participants number;
Begin
select count(*) into participants from participateteams where :New.eventName = participateteams.eventName;
if(:NEW.teamplace>participants) then
RAISE_APPLICATION_ERROR(-20250,'O lugar da equipa é inválido');
end if;
End;
/
From what I've researched it's because I'm trying to read the same table that called the trigger.
I have also have tried exporting the select part of the code into a function, but the issue persists.
The participants in the event are the teams theirselves and not the individuals that form those same teams. Example: If team A, team B and team C participate in an event E, the count for that same event E should be 3.
Any tips? Thank you.
The mutating table error is just a pain. And what you are trying to do is rather tricky. Here is one approach:
Add a column to teams with the count of participants.
Maintain this column with insert/update, and delete triggers on participateteams.
Write a user-defined function to fetch the count for a given team.
Add a check constraint in participateteams using the user-defined function.
The logically correct trigger for what you want to achieve is this :-
CREATE OR REPLACE TRIGGER TRIG_CHK
AFTER INSERT OR UPDATE OF teamPlace ON participateteams
FOR EACH ROW
DECLARE
participants NUMBER;
BEGIN
select count(*) into participants from participateteams where :New.eventName =
participateteams.eventName;
if(:NEW.teamplace>participants) then
RAISE_APPLICATION_ERROR(-20250,'O lugar da equipa é inválido');
end if;
End;
update participateteams set teamPlace = 2 where eventName = 'B';
But when it gives Mutating table error when trying to update table participateteams,to solve this tricky situation , what I did is :-
step1 :Declare the table columns needed in a package header
CREATE OR REPLACE PACKAGE PKG_TEAMS AS
v_eventName participateteams.eventName%type;
v_teamPlace participateteams.teamPlace%type;
end;
step2 : Initialize the variables in a row level trigger
CREATE OR REPLACE TRIGGER TRG_row
AFTER INSERT OR UPDATE OF teamPlace
ON participateteams
FOR EACH ROW
BEGIN
PKG_TEAMS.v_eventName := :NEW.eventName;
PKG_TEAMS.v_teamPlace := :NEW.teamPlace;
END;
step3 :Now ,rather than using :NEW pseudo columns, using globally initialized variables
CREATE OR REPLACE TRIGGER TRG_stmt
AFTER INSERT OR UPDATE OF teamPlace ON participateteams
DECLARE
v_participants NUMBER;
BEGIN
SELECT COUNT(*) INTO v_participants FROM participateteams
WHERE eventName = PKG_TEAMS.v_eventName;
if(PKG_TEAMS.v_teamPlace>v_participants) then
RAISE_APPLICATION_ERROR(-20250,'CANNOT UPDATE,AGAINST RULES');
end if;
End;

Updating the record of same table when new record is inserted or updated in oracle

I am new to learning Oracle. I have a task in which I need to update value of any previous record if new record contains its reference.
Table structure is as below :
Review_Table
(review_id number pk,
review_name varchar2,
previous_review number null,
followup_review number null
)
Here previous_review and followup_review columns are objects of same table i.e Review_table.
Now consider we have two records in Review_table A and B, A does not have any previous or followup review. When user creates/updates the record B and he selects record A as previous record, then we want to automatically update (via trigger) the value of A record's followup review with B's Review ID.
I have tried writing following trigger
create or replace trigger "REVIEW_T1"
AFTER insert or update on "REVIEW_TABLE"
for each row
begin
update REVIEW_TABLE
set review_follow_up_review = :new.REVIEW_ID
where REVIEW_ID = :new.REVIEW_PREVIOUS_REVIEW;
end;
But I am getting error as : REVIEW_TABLE is mutating, trigger/function may not see it ORA-06512
I have tried searching everything but was unable to find any solution for it
TL;DR: No trigger, no mutating. Do not use trigger to change another row in the same table.
I absolutely agree with #StevenFeuerstein's comment:
I also suggest not using a trigger at all. Instead, create a package that contains two procedures, one to insert into table, one to update. And within these procedures, implement the above logic. Then make sure that the only way developers and apps can modify the table is through this package (don't grant privs on the table, only execute on the package).
Take a look at the following example.
Prepare the schema:
create table reviews (
id number primary key,
name varchar2 (32),
previous number,
followup number
);
create or replace procedure createNextReview (name varchar2, lastId number := null) is
lastReview reviews%rowtype;
nextReview reviews%rowtype;
function getLastReview (lastId number) return reviews%rowtype is
begin
for ret in (
select * from reviews where id = lastId
for update
) loop return ret; end loop;
raise_application_error (-20000, 'last review does not exist');
end;
procedure insertReview (nextReview reviews%rowtype) is
begin
insert into reviews values nextReview;
exception when others then
raise_application_error (-20000, 'cannot insert next review');
end;
procedure setFollowUp (nextId number, lastId number) is
begin
update reviews set
followup = nextId
where id = lastId
;
exception when others then
raise_application_error (-20000, 'cannot update last review');
end;
begin
if lastId is not null then
lastReview := getLastReview (lastId);
end if;
nextReview.id := coalesce (lastReview.id, 0)+1;
nextReview.name := name;
nextReview.previous := lastId;
insertReview (nextReview);
if lastReview.Id is not null then
setFollowUp (nextReview.id, lastReview.Id);
end if;
exception when others then
dbms_output.put_line (
'createNextReview: '||sqlerrm||chr(10)||dbms_utility.format_error_backtrace ()
);
end;
/
Execute:
exec createNextReview ('first review')
exec createNextReview ('next review', 1)
See the outcome of work done:
select * from reviews;
ID NAME PREVIOUS FOLLOWUP
---------- ---------------- ---------- ----------
1 first review 2
2 next review 1
First you need to read about triggers, mutating table error and compound triggers: http://docs.oracle.com/cd/E11882_01/appdev.112/e25519/triggers.htm#LNPLS2005
Your trigger is AFTER UPDATE OR INSERT. Means if you run UPDATE OR INSERT statements on this table, the trigger will fire. But you are trying to update the same table again inside your trigger, which is compl. wrong.
I think you can fix this by rewriting this as a before trigger, rather than an after trigger.

How to write a procedure to display the contents of a table in sql

I have a created a procedure as
create or replace procedure availability(num in number) as
begin
delete from vehicle_count;
insert into vehicle_count from select engine_no,count(engine_no)
from vehicle
where engine_no = num
group by engine_no;
end;
/
The procedure was created successfully but now i have to write a separate query to view the contents of vehicle_count as
select * from vehicle_count;
I tried inserting the select statement into the procedure after insertion but it showed a error stating "an INTO clause is expected in the select statement".
How can I create procedure to select the required contents and display it in a single execute statement?
Table schema
vehicle(vehicle_no,engine_no,offence_count,license_status,owner_id);
vehicle_count(engine_no,engine_count);
Check this (MS SQL SERVER)-
create or alter procedure availability(#num as int) as
begin
delete from vehicle_count;
insert into vehicle_count
output inserted.engine_no,inserted.count_engine_no
select engine_no,count(engine_no) as count_engine_no
from vehicle
where engine_no=#num
group by engine_no;
end;
If you want to use a SELECT into a PL/SQL block you should use either a SELECT INTO or a loop (if you want to print more rows).
You could use something like this:
BEGIN
SELECT engine_no, engine_count
INTO v_engine, v_count
FROM vehicle_count
WHERE engine_no = num;
EXCEPTION
WHEN NO_DATA_FOUND THEN
v_engine := NULL;
v_count := NULL;
END;
v_engine and v_count are two variables. You can declare them in your procedure, and they will contain the values you want to print.
You said that the procedure you wrote (actually, you posted here) compiled successfully. Well, sorry to inform you - that's not true. This is not a valid syntax:
insert into vehicle_count from select engine_no,count(engine_no)
----
from? Here?
Consider posting true information.
As of your question (if we suppose that that INSERT actually inserted something into a table):
at the beginning, you delete everything from the table
as SELECT counts number of rows that share the same ENGINE_NO (which is equal to the parameter NUM value), INSERT inserts none (if there's no such NUM value in the table) or maximum 1 row (because of aggregation)
therefore, if you want to display what's in the table, all you need is a single SELECT ... INTO statement whose result is displayed with a simple DBMS_OUTPUT.PUT_LINE which will be OK if you're doing it interactively (in SQL*Plus, SQL Developer, TOAD and smilar tools). Regarding table description, I'd say that ENGINE_NO should be a primary key (i.e. that not more than a single row with that ENGINE_NO value can exist in a table).
create or replace procedure availability (num in number) as
l_engine_no vehicle_count.engine_no%type;
l_engine_count vehicle_count.engine_count%type;
begin
delete from vehicle_count;
insert into vehicle_count (engine_no, engine_count)
select engine_no, count(engine_no)
from vehicle
where engine_no = num
group by engine_no;
-- This query shouldn't return TOO-MANY-ROWS if ENGINE_NO is a primary key.
-- However, it might return NO-DATA-FOUND if there's no such NUM there, so you should handle it
select engine_no, engine_count
into l_engine_no, l_engine_count
from vehicle_count
where engine_no = num;
dbms_output.put_line(l_engine_no ||': '|| l_engine_count);
exception
when no_data_found then
dbms_output.put_line('Nothing found for ENGINE_NO = ' || num);
end;
/
There are numerous alternatives to that (people who posted their answers/comments before this one mentioned some of those), and the final result you'd be satisfied with depends on where you want to display that information.