TRIGGER AND COUNT to limit the Count value - sql

i want to create a trigger that will
select staffid where dateread = this months date
to count the number of a staffID
if the count for that staff during that month is more than 5
a stop will be issued.
here is what i did that screws me up
I would like to know is this logically correct?
and here are my compiler log errors
This is my required outcome:
Meter readers can only read a maximum of 5 meters in any given calendar month
My Reading Table has
StaffID
MeterID
DateRead
ReadinID (PK)
Here is the error text:
Error(5,7): PL/SQL: SQL Statement ignored Error(5,27):
PL/SQL:ORA-00923: FROM keyword not found where expected
C:\Users\ChrisPin\AppData\Roaming\SQL Developer\assgn2 sat4.sql Error(5,7):
PL/SQL: SQL Statement ignored Error(5,27):
PL/SQL: ORA-00923: FROM keyword not found where expected
Here is the trigger code:
CREATE OR REPLACE TRIGGER LIMIT_5_REDINGS
BEFORE UPDATE OR INSERT ON reading
FOR EACH ROW
DECLARE
ReadingCount INTEGER; --# of depts for this employee
max_read INTEGER := 5; --max number of depts per employee.
BEGIN
select Reading COUNT(*) into ReadingCount
from (select *
from Reading
where to_char(DateRead, 'YYYY-MM') = to_char(sysdate, 'YYYY-MM'))
WHERE STAFFID = :NEW.STAFFID;
IF :OLD.STAFFID = :NEW.STAFFID THEN
RETURN;
ELSE
IF ReadingCount >= max_read THEN
RAISE_APPLICATION_ERROR (-20000,'Employees are limited to a max of two departments.');
END IF;
END IF;
END;

It's in this line
select Reading COUNT(*) into ReadingCount
should be
select COUNT(*) into ReadingCount

Related

Nested Loops In Oracle Not Work Correctly (Exceeds the maximum)

I wrote These Two queries For Sample Table to insert into "z_exp14_resualt" From "z_exp14_main ". The First Query Works Correctly (Null) But Second That For rows have due_date on the main table (Not Null), not works correctly! . I Think Problem is The loop. It started before the due date and even goes beyond that . For NOT Nulls Insert must start from today(year and monts from sysdate and day from opening_date) and continue until due_date
Calculate For Null DUE_DATES
declare
i number := 1;
BEGIN
for i in 1..12 loop
insert into z_exp14_resualt
select dep_id,ADD_MONTHS(ADD_MONTHS(opening_date,trunc( months_between (sysdate,opening_date))),i),rate*balance
from z_exp14_main
WHERE due_date IS null;
end loop;
END;
/
And For Not Null Due_dates
DECLARE
diff number;
x number :=1;
BEGIN
for i in (select * FROM z_exp14_main WHERE due_date IS NOT null) loop
diff :=trunc( months_between (sysdate,i.opening_date));
WHILE (ADD_MONTHS(ADD_MONTHS(i.opening_date,diff),x)<i.due_date) LOOP
insert into z_exp14_resualt
select dep_id,ADD_MONTHS(ADD_MONTHS(i.opening_date,diff),x),rate*balance
from z_exp14_main WHERE due_date is not null ;
x :=x+1;
end loop;
end loop;
end;
/
sample Date On Main (z_exp14_main)
--
DEP_ID
DUE_DATE
BALANCE
RATE
OPENING_DATE
--
20056634
null
283428
10
15-SEP-16
--
20056637
null
180222
10
07-NOV-14
--
20056639
null
58741
10
28-AUG-14
--
40000020
27-NOV-21
5000000
22
31-MAR-14
--
40000023
23-APR-21
63000000
22
25-AUG-18
The Problem Is In the "While" condition. I correct it And This Is Final Answer:
DECLARE
diff number;
x number ;
BEGIN
for i in (select * FROM z_exp14_main WHERE due_date IS NOT null) loop
diff :=trunc( months_between (sysdate,i.opening_date));
x:=0;
WHILE (add_months(sysdate,x)<i.due_date) LOOP
insert into z_exp14_resualt values (i.dep_id,ADD_MONTHS(ADD_MONTHS(i.opening_date,diff),x),(i.rate*i.balance) );
x :=x+1;
end loop;
end loop;
end;
/

trying to check record exist in table -Oracle sql [duplicate]

I need to check a condition. i.e:
if (condition)> 0 then
update table
else do not update
end if
Do I need to store the result into a variable using select into?
e.g:
declare valucount integer
begin
select count(column) into valuecount from table
end
if valuecount > o then
update table
else do
not update
You cannot directly use a SQL statement in a PL/SQL expression:
SQL> begin
2 if (select count(*) from dual) >= 1 then
3 null;
4 end if;
5 end;
6 /
if (select count(*) from dual) >= 1 then
*
ERROR at line 2:
ORA-06550: line 2, column 6:
PLS-00103: Encountered the symbol "SELECT" when expecting one of the following:
...
...
You must use a variable instead:
SQL> set serveroutput on
SQL>
SQL> declare
2 v_count number;
3 begin
4 select count(*) into v_count from dual;
5
6 if v_count >= 1 then
7 dbms_output.put_line('Pass');
8 end if;
9 end;
10 /
Pass
PL/SQL procedure successfully completed.
Of course, you may be able to do the whole thing in SQL:
update my_table
set x = y
where (select count(*) from other_table) >= 1;
It's difficult to prove that something is not possible. Other than the simple test case above, you can look at the syntax diagram for the IF statement; you won't see a SELECT statement in any of the branches.
Edit:
The oracle tag was not on the question when this answer was offered, and apparently it doesn't work with oracle, but it does work with at least postgres and mysql
No, just use the value directly:
begin
if (select count(*) from table) > 0 then
update table
end if;
end;
Note there is no need for an "else".
Edited
You can simply do it all within the update statement (ie no if construct):
update table
set ...
where ...
and exists (select 'x' from table where ...)
not so elegant but you dont need to declare any variable:
for k in (select max(1) from table where 1 = 1) loop
update x where column = value;
end loop;

PL/SQL No data found even there should be?

I'm currently learning PL/SQL atm and I have run into an issue with one of my homework questions.
In the below code, I'm getting user input for a province and isolating select results using said province in the declaration of the cursor and trying to run the visitsandtotal procedure but all I'm getting is no data found, why?
user prompt
SET SERVEROUTPUT ON
ACCEPT prov PROMPT 'Enter Province: ';
DECLARE
customerprov VARCHAR2(4000);
customername VARCHAR2(4000);
visits NUMBER;
total FLOAT;
CURSOR prov_cursor is
Select custprovince, custname
into customerprov, customername
from si.customer
where upper(custprovince) = '&prov';
BEGIN
for c in prov_cursor loop
visitsandtotal(c.custname, visits, total);
dbms_output.put_line('Name: ' || c.custname || ' Visits: ' || visits || ' Total Labor Cost: ' || total);
end loop;
END;
Procedure
CREATE OR REPLACE PROCEDURE visitsandtotal (
userinput IN VARCHAR2
, visits OUT NUMBER
, total OUT FLOAT
) IS
BEGIN
SELECT
COUNT(*) AS visits
, SUM(s.laborcost) AS totalcost
INTO
visits
, total
FROM
si.customer c
INNER JOIN si.servinv s ON c.custname = s.custname
WHERE
s.custname = userinput
GROUP BY
c.custname
, s.custname ;
END;
Error
Error report -
ORA-01403: no data found
ORA-06512: at "S6_TRAN84.VISITSANDTOTAL", line 7
ORA-06512: at line 11
01403. 00000 - "no data found"
*Cause: No data was found from the objects.
*Action: There was no data from the objects which may be due to end of fetch.
I cannot comment due to less number of reputation.
NO_DATA_FOUND error comes from the procedure where you have where clause and group by..
and if no records with parameter "userinput" leads to the exception.
I would suggest to change the procedure as we certainly don't need the group by custname as the custname is part of where clause;
CREATE OR REPLACE PROCEDURE visitsandtotal
(
userinput IN VARCHAR2
,visits OUT NUMBER
,total OUT FLOAT
)
IS
BEGIN
SELECT COUNT(*) AS visits
,SUM(s.laborcost) AS totalcost
INTO visits
,total
FROM si.customer c
INNER JOIN si.servinv s
ON c.custname = s.custname
WHERE s.custname = userinput;
--removed group by as custname is part of where clause
END visitsandtotal;
But for whatever reason if you insists to keep the group by clause, you have to handle NO_DATA_FOUND exception explicitly in the procedure visitsandtotal
CREATE OR REPLACE PROCEDURE visitsandtotal
(
userinput IN VARCHAR2
,visits OUT NUMBER
,total OUT FLOAT
)
IS
BEGIN
SELECT COUNT(*) AS visits
,SUM(s.laborcost) AS totalcost
INTO visits
,total
FROM si.customer c
INNER JOIN si.servinv s
ON c.custname = s.custname
WHERE s.custname = userinput;
GROUP BY c.custname,s.custname;
-- you dont need to mention custname from both table as join is in place
EXCEPTION
WHEN no_data_found THEN
--HERE - write your exception code whatever you like to add
END visitsandtotal;

Trigger avoiding mutating table and updating :new.values

I have a small table looking like this
table People
( name VARCHAR(20) PRIMARY KEY
,group NUMBER(4)
);
And i need to create trigger (or triggers) thats will allow below rules to work:
- 1 if there are more then 10 names in group i need to raise an error if someone tries to INSERT next people for this group.
- 2 if INSERT comes with NULL value for group field i need to assign it to group which count is less then 10.
- 3 if there are 10 names in all groups i need to generate next group number.
- 4 I need to avoid mutating table error.
This is what i've done till now :
CREATE OR REPLACE TRIGGER people_bis
BEFORE INSERT ON people
FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
g_count NUMBER(4);
g_num NUMBER(4);
g_l NUMBER(4);
g_r NUMBER(4);
BEGIN
Select count(*) into g_count from people where group = :new.group;
If g_count > 9 Then
raise_application_error (-20003,'Group reached it limit, please choose other');
End if;
If :NEW.group = '' or :NEW.group is null Then
select count (*) into g_l from (select count(imie),group from people group by group having count(name) = 10);
select count (distinct group) into g_r from people;
if g_l = g_r then
select max(group)+1 into g_num from people;
else
select group into g_num from(select group, count(name) from people having count(name) < 10 group by group order by count(group) desc) where rownum < 2;
End if;
:New.group := g_num;
End if;
End people_bis;
Code above works, but when i INSERT as select from a mirror table e.g.
INSERT INTO people(name) select concat(name,'_next') from people_mirror;
The result is that it exceed the given limit (10) for a group.
Also i know that using PRAGMA AUTONOMOUS_TRANSACTION is not a best way to avoid mutating table error, and i know i could reach this functionality if i would split row trigger into statement triggers but i'm just out off idea how to get it.
So anyone? ;)
Thanks in advance.
-------------------EDIT------------------------
Those are the triggers that works but still i have doubts about them as both of them are BEFORE and row type.
CREATE OR REPLACE TRIGGER people_bir1
BEFORE INSERT on people
FOR EACH ROW
DECLARE
V_count NUMBER(2);
BEGIN
If :NEW.group = '' or :NEW.group is null then
return;
end if;
insert into groups values(:New.group,1);
exception when dup_val_on_index then
Select count into v_count from groups where group = :New.group;
UPDATE groups set count = v_count+1 where group = :New.group;
END people_bir1;
CREATE OR REPLACE TRIGGER people_bir2
BEFORE INSERT on people
FOR EACH ROW
DECLARE
g_count NUMBER(2);
g_num NUMBER(2);
begin
if :NEW.group = '' or :NEW.group is null Then
select min(count) into g_count from groups;
if g_count = 10 Then
select max(group) into g_num from groups;
g_num := g_num+1;
Else
select min(group) into g_num from group where count = g_count;
End if;
:New.group := g_num;
Else
select count into g_count from groups where group=:New.group;
if g_count > 9 then
raise_application_error (-20003,'More then 10 people in group please select another');
end if;
end if;
end people_bir2;
as it is too long i couldn't paste it as a comment to #TonyAndrews answer.
You are right that adding PRAGMA AUTONOMOUS_TRANSACTION is no solution. One way to do this is to maintain a count of people per group in the GROUPS table (if you don't have a GROUPS table then you could add one) using triggers on PEOPLE:
After INSERT on PEOPLE: update GROUPS, add 1 to count of group they are in
After DELETE on PEOPLE: update GROUPS, subtract 1 from count of group they are in
After UPDATE on PEOPLE: update GROUPS, add 1 to new group, subtract 1 from old group
Then your BEFORE INSERT trigger doesn't need to look at the PEOPLE table, it can look at GROUPS:
Select people_count into g_count from groups where group = :new.group
for update;
Note the for update clause to lock the GROUPS row until your transaction completes.
You can use a compund trigger. It looks like this:
CREATE OR REPLACE TRIGGER people_bis
FOR INSERT ON people
COMPOUND TRIGGER
g_count NUMBER(4);
g_num NUMBER(4);
g_l NUMBER(4);
g_r NUMBER(4);
BEFORE STATEMENT IS
BEGIN
Select count(*) into g_count from people where group = :new.group;
If g_count > 9 Then
raise_application_error (-20003,'Group reached it limit, please choose other');
End if;
select count (*) into g_l from (select count(imie),group from people group by group having count(name) = 10);
select count (distinct group) into g_r from people;
if g_l = g_r then
select max(group)+1 into g_num from people;
else
select group into g_num from(select group, count(name) from people having count(name) < 10 group by group order by count(group) desc) where rownum < 2;
End if;
END BEFORE STATEMENT;
BEFORE EACH ROW IS
BEGIN
If :NEW.group = '' or :NEW.group is null Then
:New.group := g_num;
End if;
END BEFORE EACH ROW;
End people_bis;
Please note, most probably this code does not work as you wanted but it should give you a general impression how to work with a Compound Trigger.

Comparing two dates with an insert trigger

I want to make sure that the suborder date is not before the orderdate using this trigger:::
CREATE OR REPLACE TRIGGER suborder_date before
INSERT ON suborder FOR EACH row DECLARE suborder_dd DATE;
orderdate DATE;
BEGIN
SELECT reqshipdate INTO suborder_dd FROM suborder;
SELECT orders.orddate INTO orderdate FROM orders;
IF orderdate < suborder_dd THEN
raise_application_error(-20002,('Required Ship Date is before Order Date'));
END IF;
END;
This compiles now I want to test it and insert this into the table...
insert into suborder values (1,1,'12-Jan-13','13-Jan-14', 'Office Depot', 1)
Error: Error starting at line 1 in command: insert into suborder
values (1,1,'12-Jan-13','13-Jan-14', 'Office Depot', 1) Error report:
SQL Error: ORA-01422: exact fetch returns more than requested number
of rows ORA-06512: at "BB.SUBORDER_DATE", line 4 ORA-04088: error
during execution of trigger 'BB.SUBORDER_DATE'
01422. 00000 - "exact fetch returns more than requested number of rows"
*Cause: The number specified in exact fetch is less than the rows returned.
*Action: Rewrite the query or change number of rows requested
With the help of others I finally got the output that I wanted when I edited the command for myself. Thank you!
CREATE OR REPLACE TRIGGER suborder_date BEFORE
INSERT ON suborder FOR EACH row DECLARE reqship DATE; orderdate date;
BEGIN
SELECT o.orddate
INTO orderdate
FROM orders o
WHERE o.orderno = :new.orderno;
IF orderdate > :new.reqshipdate THEN
raise_application_error(-20002, ('Required Ship Date is before Order Date'));
END IF;
END;
If you follow the comments, you should get something like this:
CREATE OR REPLACE TRIGGER suborder_date
BEFORE INSERT ON suborder
FOR EACH row
DECLARE
orderdate DATE;
BEGIN
SELECT o.orddate
INTO orderdate
FROM orders o
WHERE o.order_id = :new.order_id;
IF orderdate < :new.suborder_dd THEN
raise_application_error(-20002, ('Required Ship Date is before Order Date'));
END IF;
END;