Trigger to check over dues - sql

I need a trigger to check over-dues.
If it is over due it should put some details from a table called loan to a table called fine with a fine amount but, the trigger I created is giving compilation errors.
SQL> CREATE OR REPLACE TRIGGER Over_Due
2 AFTER INSERT OR UPDATE ON loan_table
3 FOR EACH ROW
4 DECLARE due_date DATE;
5 BEGIN
6 SELECT COUNT(*) INTO due_date FROM loan_table l
7 WHERE l.date_due = :new.date_due;
8 IF(date_due > SYSDATE)
9 THEN
10 INSERT INTO fine_table VALUES(fine_id, :old.loan_id,:old.book_id,:old.student_id,amount);
11 END IF;
END; 12
13 /
Warning: Trigger created with compilation errors.
SQL> show errors;
Errors for TRIGGER OVER_DUE:
LINE/COL ERROR
-------- -----------------------------------------------------------------
3/1 PL/SQL: SQL Statement ignored
3/8 PL/SQL: ORA-00932: inconsistent datatypes: expected DATE got
NUMBER

There is a big problem with your design: Triggers are event based - yours only fires when a new loan is made. What happens if a book is overdue and no one loans a book for a while? The answer is, nothing! The overdue detection is not made until a book is borrowed. Also, the same check is made every time a book is borrowed, which is too often.
Instead, what you need is a periodic check of all records - I would recommend once per day, timed to run after the library has closed (so your processing does not affect transaction performance) that checks for the existence of overdue books.

You are assigning the value of Count(*) (a number) into a DATE field (due_date).
I suspect what you intend to do here is:
select l.due_date into due_date from ...

Your problem seems to be
SELECT COUNT(*) INTO due_date
COUNT(*) will never be of a DATE type.
You probably want to modify it like
...
DECLARE due_date DATE;
BEGIN
SELECT l.date_due INTO due_date FROM loan_table l
WHERE l.date_due = :new.date_due ORDER BY l.date_due ASC LIMIT 1;
IF(due_date > SYSDATE)
THEN
...
which will check the oldest l.date_due against SYSDATE and do the insert accordingly. If you need something else, you need to be more specific.

Related

Converting a date to a number for a Procedure

Hi i have a procedure that has in it
Select
(ROUND(A.START - A.END, 0) AS DAYS_WORKED
FROM WORKINGTABLE A
THIS RETURNS
5
super
when i put it in a procedure and update another table with the DATATYPE as NUMBER the info it says this it cant do it becasue the data type is not a number it is a date. fair enough. so I changed the column to a DATETYPE but it didnt like it and gave an error saying invalid month (as the date and time is 5 im not suppriesed it didnt like it)
so I set the column back to NUMBER
and tried this
TO_NUMBER(ROUND(A.START - A.END, 0))) AS DAYS_WORKED
and this
TO_NUMBER(TO_CHAR(ROUND(A.START - A.END, 0))) AS DAYS_WORKED
but it still thinks it is a date and gives this messag
ORA-00932: inconsistent datatypes: expected NUMBER got DATE.
shows fine in a query output/report it is just the procedure being fussy
any ideas how i can get this to line up?
You did something wrong (can't tell what exactly). Have a look at the following test case.
Sample table; days_worked is - as you said - a number:
SQL> create table test (id number, days_worked number);
Table created.
SQL> insert into test (id) values (1);
1 row created.
Procedure accepts date datatype parameters. Difference of two dates is number of days between.
SQL> create or replace procedure p_test (par_id in number,
2 par_date_start in date, par_date_end in date) is
3 begin
4 update test set
5 days_worked = round(par_date_end - par_date_start, 0)
6 where id = par_id;
7 end;
8 /
Procedure created.
Testing:
SQL> begin
2 p_test(1, to_date('05.12.2022 13:43:22', 'dd.mm.yyyy hh24:mi:ss'),
3 to_date('28.12.2022 08:23:32', 'dd.mm.yyyy hh24:mi:ss'));
4 end;
5 /
PL/SQL procedure successfully completed.
Result:
SQL> select * from test;
ID DAYS_WORKED
---------- -----------
1 23
SQL>
So, yes - it works when properly used.
It turned out it was me not realising how insert into works.
I thought it was inserting on the name of the select, but it wasn't. It was inserting in order of the column (something I can't change in oracle), so when it was saying it was the wrong format, it was because the column it was supposed to be going into wasn't the right one. It ignores the names and does it in order.
Thanks to Lightfoot for pointing me in the right direction.

Trigger for before insert to add in x number of days to a date

I am trying to add x number of days to a variable within a table by deriving another date from the same table.
For example, in my BILLING table, it has 2 dates - BillDate and DueDate.
And so, I am trying to add a trigger before the insertion, such that it will takes in the BillDate and add 30 days to derive the DueDate.
While doing so, I got a bunch of errors, as follows:
dbfiddle
CREATE TABLE BILLING
(
BillDate DATE NOT NULL,
DueDate DATE NULL
);
-- Got ORA-24344: success with compilation error
CREATE TRIGGER duedate_trigger BEFORE INSERT ON BILLING
FOR EACH ROW
begin
set DueDate = :new.DueDate: + 30
end;
-- Got ORA-04098: trigger 'FIDDLE_FBHUOBXMWRPYBBXPIKTW.DUEDATE_TRIGGER' is invalid and failed re-validation
INSERT INTO BILLING
VALUES ((Date '2020-07-23'), NULL);
For the insertion, I have tried removing the NULL, but still I am getting a bunch of errors.
Any ideas?
Also, in the event, if the insertion statement also does includes in the due date too, will this affects the trigger? Trying to cater for 2 scenarios, generate a due date if not give, else if given, check if it is within 30 days from BillDate and update it... (likely I may have overthink/ overestimated that this is doable?)
CREATE OR REPLACE TRIGGER duedate_trigger BEFORE INSERT ON BILLING
FOR EACH ROW
begin
:new.DueDate := :new.BillDate + 30;
end;
INSERT INTO BILLING (BillDate ) values (sysdate);
CREATE OR REPLACE TRIGGER duedate_trigger
BEFORE INSERT ON billing
FOR EACH ROW
DECLARE
v_dueDate_derive NUMBER;
BEGIN
v_dueDate_derive = 30;
:new.DueDate = :new.BillDate + v_dueDate_derive;
END;
Days can be easily added by +, so it should not be the problem.
I believe there may be something wrong with INSERT itself.
Could you try to put INSERT like this?
INSERT INTO BILLING
VALUES (TO_DATE('2020-07-23'), NULL);

How do I limit number of records in table, so that old records are deleted, but new rows are kept?

I have a table in our oracle database that is tracking downloads based on the users session in a web application. Over the past few years, this table has grown to millions of records, which is slowing down the application.
I would like to limit this table to only keep the items from the past week, and have it automatically remove the older records. The table has a date field called DOWNLOAD_DATE which could be used for this query.
Is this something I can do with triggers? If so, what is the best way to do so?
I don't think that a trigger is the right thing for that, as it is executed on every insert/update/delete. I would recommend to create a procedure for that which performs a statement like "delete from table_name where trunc(download_date) < trunc(sysdate - 7);"
Then you schedule that procedure to be executed every hour or day based on your needs.
After the delete you may want to refresh the table stats with "DBMS_STATS.GATHER_TABLE_STATS ('schema_name', 'table_name');"
More regarding gathering stats can be found here: http://www.dba-oracle.com/t_dbms_stats_gather_table_stats.htm
Is this something I can do with triggers? If so, what is the best way
to do so?
Well i would say, a Statement level trigger is well suited for this problem. Read about statement level trigger Here
See demo:
SQL> CREATE TABLE tab2 (
col VARCHAR(1),
start_date DATE,
end_date DATE
);
/
Table created
SQL> Select * from tab2;
COL START_DATE END_DATE
--- ----------- -----------
A 11-07-2019 18-07-2019
A 11-07-2019 31-07-2019
A 06-07-2019 31-07-2019
A 01-07-2019 31-07-2019 --- See this row is 10 days older than Sysdate
-- Created Statement level trigger
SQL> CREATE OR REPLACE TRIGGER t1
2 BEFORE INSERT ON tab2
3 BEGIN
4 DELETE FROM tab2
5 WHERE start_date <= SYSDATE - 7; -- Deleting records older that 7 days from sysdate
6 END;
7 /
Trigger created
SQL> Insert into tab2 values('A',sysdate+1,sysdate+10);
1 row inserted
SQL> commit;
Commit complete
-- You can see the records older that 7 days from sysdate is deleted
SQL> Select * from tab2;
COL START_DATE END_DATE
--- ----------- -----------
A 11-07-2019 18-07-2019
A 11-07-2019 31-07-2019
A 06-07-2019 31-07-2019
A 12-07-2019 21-07-2019
IF I had requirement like this, I would go like this:
PROCEDURE p1
IS
BEGIN
LOOP
DELETE FROM table123 --- you can give your table name here
WHERE download_date <= SYSDATE - 7 AND ROWNUM <= 10000; ---if you want to restrict to delete the rows at a time
EXIT WHEN SQL%ROWCOUNT = 0;
COMMIT;
DBMS_LOCK.sleep (10); ----- if you want to give sleep time as per your requirement
END LOOP;
END;
this job will run for 24*7 and delete all the records which are older than 7 days

Limit data input based on count of instances in table

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;

Trigger compilation error reasons

Hi guys I have two triggers I am meant to be be creating but I am getting compilation errors on both
this first is supposed to record evaluations of 0 to an audit table and the second is supposed to prevent the deletion of entries in which the date is less than todays date.
SQL> CREATE TABLE EVALUATION_AUDIT
2 (C_NAME VARCHAR (15), CO_ID NUMBER(7), E_DATE DATE,
3 V_ID NUMBER (7), C_EVALUATION NUMBER(1));
Table created.
SQL> CREATE OR REPLACE TRIGGER ZERO_EVAL
2 BEFORE INSERT OR UPDATE OF C_EVALUATION ON CUSTOMER_EVENT
3 FOR EACH ROW
4 WHEN (NEW.C_EVALUATION = 0)
5 BEGIN
6 SELECT C_NAME, CO_ID, E_DATE, V_ID, C_EVALUATION
7 FROM CUSTOMER_EVENT CE, CUSTOMER C, EVENT E
8 WHERE CE.C_ID = C.C_ID
9 AND CE.EVENT_ID = E.EVENT_ID
10 AND C_EVALUATION = NEW.C_EVALUATION;
11 INSERT INTO EVALUATION AUDIT
12 VALUES (:NEW.C_NAME, :NEW.CO_ID, :NEW.E_DATE, :NEW.V_ID, :NEW.C_EVALUATION);
13 END;
14 /
Warning: Trigger created with compilation errors.
SQL> CREATE OR REPLACE TRIGGER PASTEVENTS
2 BEFORE DELETE
3 ON EVENT
4 FOR EACH ROW
5 BEGIN
6 IF :OLD.E_DATE =< SYSDATE
7 THEN RAISE_APPLICATION_ERROR (-20002, 'CAN NOT DELETE PAST EVENT RECORDS');
8
9 END IF;
10 END;
11 /
Warning: Trigger created with compilation errors.
As Justin said, when you get created with compilation errors for any stored PL/SQL, type show errors, or you can query the user_errors table to see all outstanding errors on your objects.
From a quick scan, the first trigger is missing a colon when you reference NEW.C_EVALUATION in the select:
AND C_EVALUATION = :NEW.C_EVALUATION;
You need to select into something, though I'm not sure if it's necessary here as you have the values from the :NEW psuedorecord; not sure why you're selecting at all?
And the second has an incorrect operator, =< instead of <=:
IF :OLD.E_DATE <= SYSDATE
It's generally a good idea to prefix column names with the table alias to avoid ambiguity, e.g. SELECT C.C_NAME, ... if that column comes from the CUSTOMER table, etc. You could have another error in there is you have the same column on multiple tables. And it's good practise to list the column names in your INSERT too, i.e. INSERT INTO EVALUATION_AUDIT (C_NAME, ...) VALUES (...). With the missing underscore that #Dba spotted!
In your first trigger code you don't need to SELECT from the tables as you are just inserting the values from the table CUSTOMET_EVENT to EVALUATION_AUDIT. Also you have missed and underscore _ in table_name in EVALUATION_AUDIT in line 11.
CREATE OR REPLACE TRIGGER zero_eval
BEFORE INSERT OR UPDATE OF c_evaluation ON customer_event
FOR EACH ROW
WHEN (NEW.c_evaluation = 0)
BEGIN
INSERT INTO evaluation_audit(c_name, co_id, e_date, v_id, c_evaluation)
VALUES (:NEW.c_name, :NEW.co_id, :NEW.e_date, :NEW.v_id, :NEW.c_evaluation);
END;
/
In Your second code, it should be <= instead of =<
CREATE OR REPLACE TRIGGER pastevents
BEFORE DELETE
ON event
FOR EACH ROW
BEGIN
IF :OLD.e_date <= SYSDATE THEN
raise_application_error (-20002, 'CAN NOT DELETE PAST EVENT RECORDS');
END IF;
END;
/