plsql - can anyone help me out how do we reset a sequence in oracle 11 g database at two particular times in one single day? - oracle

Let me explain you the scenario of hospital management tool.
In every hospital, we have n no. of doctors, n no. of admins, n no. of security, etc departments respectively. Each and every hospital we have an out-patient consultations in the morning time approximately around 8:00 am to 10:00 am, from 10:00 am to evening 5:00 pm doctors will undertake operations and treatments for In-patients in "Intensive care unit" (ICU). So now after 5:00 pm again the doctors will have an out-patients consultation in the hospital from 18:00 pm to 20:00 pm.
Now, let me explain the same in technical terminology to you.
When the out-patients come ask a token number of so and so doctor. The admin will then select particular department in the UI and select particular doctor as per patient's problem. For this, i'm maintaining a each doctor's tables in the database which doctor name itself..
example :
1)Neurology Department
i) Dr. Sarath Kumar
ii) Dr. anil kumar
2)Cardiology Department
i) Dr. Madhu
ii) Dr. Anji Reddy
3)Orthopedics Department
i) Dr. Murali
ii) Dr. Sirisha
etc...
Creation of a doctor table :
create table sarath_Kumar(token_no not null primary key,
patient_name char(50) not null ,
patient_age number(3) not null ,
patient_phonenumber number(12) not null unique ,
patient_email varchar2(50) not null unique,
patient_gender char(1) not null,
patient_location varchar2(50) not null,
patient_dateofappointment date not null,
CONSTRAINT sk_token_no CHECK (token_no<=20);
Note:
if we think generally admin doesn't know which token number is going on for each and every doctor.
As we have the same table structure for each and every doctor by their name. But now the thing is the first column in each doctor table has to generate automatically from 1, to do this i created a sequence and a procedure to execute the sequence before an insertion happens by the admin.
let's take morning session of out-patients consultation from 8:00 am to 10:00 am. Each doctor will only have a 20 patients for consultation.
Sequence Creation :
create sequence appointment_sequence
start with 1
increment by 1
minvalue 1
maxvalue 20
cache 5
;
Procedure Creation :
create or replace trigger appointment_sequence before insert on sarath_kumar for each row
begin
:new.token_no := appointment_sequence.NEXTVAL;
end;
/
what i need from you is :
After reaching 20 patients for any doctor during consultation i.e., the token number reached it's maximum level between 8:00 am to 10:00 am. If any person asks for a appointment for that particular doctor. The admin shouldn't able to provide any kind of appointment for that doctor and insist the patient to come in evening time consultation which is from 18:00 pm to 20:00pm .
I need a procedure or function in which the doctor table should get truncated and the sequence should get reset back to minvalue at 10:00 am and in the evening after 20:00 pm respectively.

First of all, You should have the patient_appoint table instead of a separate table with the doctor's name and just pass the doctor's ID in that table.
create table patient_appoint(token_no not null primary key,
doctor_id number not null,
patient_name char(50) not null ,
patient_age number(3) not null ,
patient_phonenumber number(12) not null unique ,
patient_email varchar2(50) not null unique,
patient_gender char(1) not null,
patient_location varchar2(50) not null,
patient_dateofappointment date not null,
CONSTRAINT sk_token_no CHECK (token_no<=20);
For resetting the sequence to 1, Use CYCLE property of sequence. Use below code to generate the sequence -
create sequence appointment_sequence
start with 1
increment by 1
minvalue 1
maxvalue 20
cycle
cache 5
;
For restricting to only 20 patients per day, you may use below trigger -
CREATE OR REPLACE TRIGGER TR_PATIENT_APPOINT
AFTER INSERT ON PATIENT_APPOINT
DECLARE
v_count NUMBER;
BEGIN
SELECT COUNT(*)
INTO v_count
FROM PATIENT_APPOINT
WHERE TRUNC(patient_dateofappointment) = TRUNC(SYSDATE);
IF (v_count > 20) THEN
raise_application_error(-20000, 'Maximum 20 appointments allowed per day.');
END IF;
END TR_PATIENT_APPOINT;

As others have pointed out or at lest hinted at this will be a maintenance nightmare, with each doctor having their own table and their own sequence. Consider what happens when a patient cancels. You don't get that sequence value back, so that doctor can only see 19 patients. And that is an easy situation to handle. There is an easier way: don't use sequences.
If you break it down each patent is essentially given a 6min time slot (120min/20slots). With his you generate a skeleton schedule for each doctor that does not have patient information initially. Admins then fill in patient information when needed, and can actually view the available time for each doctor. The following shows how to generate such a schedule. (Note it assumes you have normalized you doctor table (1 table containing all doctors) and created a patient table (1 table containing all patients).
--- prior Setup
create table doctors(doc_id integer, name varchar2(50), ..., constraint doc_pk primary key (doc_id));
create table patients(pat_id integer, name varchar2(50), ..., constraint pat_pk primary key (pat_id));
--- Daily Out patient Schedule.
create table out_patient_schedule (
ops_id integer
, doc_id integer not null
, pat_id integer
, apt_schedule date
, constraint ops_pk primary key (ops_id)
, constraint ops2doc_fk foreign key (doc_id) references doctors(doc_id)
, constraint ops2pat_fk foreign key (pat_id) references patients(pat_id)
);
--- Generate skeleton schedule
create or replace procedure gen_outpatient_skeleton_schedule
as
begin
insert into out_patient_schedule( doc_id, apt_schedule)
with apt_times as
( select trunc(sysdate, 'day') + 8/24 + (120/20)*(level-1)/(60*24) apt_time from dual connect by level <= 20
union all
select trunc(sysdate, 'day') + 18/24 + (120/20)*(level-1)/(60*24) from dual connect by level <= 20
)
select doc_id, apt_time from doctors, apt_times;
end gen_outpatient_skeleton_schedule;
Now create an Oracle Job, or an entry for what ever job schedule you have, that executes the above procedure between midnight and 8:00.
There is a race condition you need to handle, but doing so would be much easier that trying it with sequences.
Good Luck either way.

Related

plsql how to fire trigger only when a function X is activated?

I have two functions. The first is to withdraw money (the traditional way, I mean when someone withdraw from agency). The function will not allow the withdrawal when client balance is less than amount asked.
Then I create another function to withdraw money from DAP using visa card.
The task is to write a trigger to throw an exception in the case of an expiry of the date of a card.
That means this trigger will fire only when the second function is activated (the function that make withdrawal using card) because the client can have an expired card but he can still use the first function, i.e. the withdrawal from agency.
These are my tables:
CREATE TABLE COMPTE(
NUMEROCOMPTE VARCHAR2(10),
SOLDE NUMBER(38,3),
SEUILDEBIT NUMBER(38,3),
DATESOLDE DATE,
ETAT VARCHAR2(10),
CONSTRAINT pk_NUMEROCOMPTE PRIMARY KEY (NUMEROCOMPTE)
);
CREATE TABLE CARTE(
NUMEROCARTE VARCHAR2(16),
TYPECARTE VARCHAR2(10),
PLAFOND NUMBER(38,3),
DATEEXPIRATION DATE,
NUMEROCOMPTE VARCHAR2(10),
CONSTRAINT pk_NUMEROCARTE PRIMARY KEY (NUMEROCARTE),
CONSTRAINT fk_NUMEROCOMPTE FOREIGN KEY (NUMEROCOMPTE) REFERENCES COMPTE (NUMEROCOMPTE)
);
If you're trying to update the compte table (i.e. withdraw some money), you have to know how is someone doing it - by using a card, or at the agency. I presume that's what the etat column says (see line #4). If not, how do you know it, then? Include that condition into the trigger.
Therefore: if you're using a card, check its expiration date and raise an error if it is lower than right now:
SQL> create or replace trigger trg_bu_compte
2 before update on compte
3 for each row
4 when (new.etat = 'CARD') --> I have no idea whether that's it, but - you have
5 declare --> to have "something" what says wheteher you're
6 l_dateexpiration date; --> using a card or not
7 begin
8 select ca.dateexpiration
9 into l_dateexpiration
10 from carte ca
11 where ca.numerocompte = :new.numerocompte;
12
13 if l_dateexpiration < trunc(sysdate) then
14 raise_application_error(-20000, 'Card has expired');
15 end if;
16 end trg_bu_compte;
17 /
Trigger created.
SQL>

Oracle SQL - Help putting a table together

part of my learning process, I am taking as much data I have on my computer and trying to use it to practice Oracle SQL. So I am in charge of keeping track of the Home Owner association money, I created a table with the following columns:
Name Null? Type
------------------ ----- ------------
OWNER_NAME VARCHAR2(20)
APPARTEMENT_NUMBER NUMBER(4)
TELEPHONE_NUMBER NUMBER(15)
JULY NUMBER(5)
AUGUST NUMBER(5)
SEPTEMBER NUMBER(5)
OCTOBER NUMBER(5)
NOVEMBER NUMBER(5)
DECEMBER NUMBER(5)
but I don't know how to do the following:
I need a way to track if the apt number paid its HOA fees, my idea is to have a check box and check it if the fee is paid and uncheck it if the fee is not paid. I'm just thinking out loud.
Please help
Thank you
Data model looks wrong. You shouldn't have months as column names - use only one DATE datatype column because - this one will be OK for this year. What about the next year? How will you know which "July" is it? Or, if that column contains only [month + year] info, make it VARCHAR2(6).
Phone number (and probably apartment number as well) should be strings because of leading zeros (in phone numbers) or letters in apartment numbers.
As of the "checkbox", there's no such datatype, but you can use NUMBER (1 - checkbox checked; 0 - not checked) or CHAR (Y / N) for that purpose.
Something like this:
create table hoa_fees
(owner_name varchar2(20),
apartment_number varchar2(4),
telephone_number varchar2(15),
month_year varchar2(6), -- e.g. 102020
fee number, -- money here
cb_paid number(1) default 0 not null -- e.g. 1 - paid
);

What is the right PL/SQL for updating rows without a need to reinsert it?

I new at using PL/SQL and I want the following:
I have this table on Oracle SQLcl
create table Child (
id varchar not null,
name varchar not null,
gender varchar not null,
YearOfBirth number(4) not null,
YearsOfAge number(4) null,
CONSTRAINT Pk primary key (id)
);
And I want a PL/SQL (preferred anonymous) that update field of "YearsOfAge" by minusing 2020 from the "YearOfBirth" field. I could do that but my problem is that the table won't be updated until I insert the PL/SQL block again. So whenever I insert a new row, I have to insert my PL/SQL block again. I want to get the table updated whenever I insert/update a row, without a need to insert this block following a new row.
To be clearer, I just want to insert SL/SQL block one time after creating the table, then get the table's "YearsOfAge" updated whenever I insert/update/delete a row. So when I write "select * from Child;" I need to see the "YearsOfAge" with the new value that computed from subtracting 2020 from "YearOf Birth".
My current PL/SQL is below:
begin
IF INSERTING THEN
update Child set YearsOfAge = 2020 - YearOfBirth;
ELSIF DELETEING THEN
update Child set YearsOfAge = 2020 - YearOfBirth;
ELSE
update Child set YearsOfAge = 2020 - YearOfBirth;
END IF;
END;
/
If you really need to store the age this way, some options are virtual columns, views, and triggers.
Virtual Column
With a virtual column, Oracle will automatically perform the calculation on the fly.
SQL> create table Child
2 (
3 id number not null,
4 name varchar2(10) not null,
5 gender varchar2(10) not null,
6 YearOfBirth number(4) not null,
7 YearsOfAge number generated always as (2020 - yearOfBirth) null,
8 constraint pk_child primary key (id)
9 );
Table created.
SQL> insert into child(id, name, gender, yearOfBirth) values(1, 'A', 'female' , 1990);
1 row created.
SQL> insert into child(id, name, gender, yearOfBirth) values(2, 'B', 'male' , 2000);
1 row created.
SQL> insert into child(id, name, gender, yearOfBirth) values(3, 'C', 'non-binary', 2010);
1 row created.
SQL> select * from child;
ID NAME GENDER YEAROFBIRTH YEARSOFAGE
---------- ---------- ---------- ----------- ----------
1 A female 1990 30
2 B male 2000 20
3 C non-binary 2010 10
View
One downside of virtual columns is that they cannot use functions like SYSDATE, so the year has to be hard-coded. With a view, the expression can reference SYSDATE and will always be up-to-date:
create or replace view child_view as
select id, name, gender, yearOfBirth, extract(year from sysdate) - yearOfBirth yearsOfAge
from child;
Trigger (Warning)
You can also use a trigger to create the value when a row is inserted or updated:
create or replace trigger child_trg
before update or insert on child
for each row
begin
if updating('YEAROFBIRTH') or inserting then
:new.yearsOfAge := extract(year from sysdate) - :new.yearOfBirth;
end if;
end;
/
But in practice, triggers are a pain to maintain. Which leads to the question: why do you want to store this information in the first place?
Good database design should minimize the amount of redundant data. There are always exceptions, but you should have a good reason for those exceptions, like an especially complicated calculation that you don't want others to get wrong, you can't create a PL/SQL function because of an unusual security constraint, etc. Calculating something as trivial as the age may cause more problems than it solves.

Why oracle sql not allow sysdate in table creation time for count age?

Query....
I want to try to make check constraints on birthdate and check age should be greater than 18.
Create table emp
(
Birthdate date,
Check( MONTHS_BETWEEN(SYSDATE,Birthdate))
);
Error on above query....why?
Anyone help me...
Why oracle sql not allow sysdate in table creation time for count age?
SYSDATE is not allowed because the constraint must be either "true" or "false" at any time you look at the data. If you were able to use SYSDATE in a check constraint, you could insert a row that satisfied the constraint at that time, but the constraint would be violated later. No good!
In your example, once the constraint is satisfied at insert time, it can't become "not satisfied" later. But here you are asking Oracle to think. It can't. It just doesn't allow you to use SYSDATE in constraints. Period.
Instead, you should write a simple trigger to do the check for you. Note that you are missing the comparison to 18 * 12 in your purported check constraint; MONTHS_BETWEEN may give some weird results in some cases; and it is always best to write code that mirrors your thinking: in this case the condition (in a trigger, not a check constraint) should be ***
sysdate >= birthdate + interval '18' year
*** EDIT: As Alex Poole points out below, adding INTERVAL to a date may sometimes be as weird as MONTHS_BETWEEN. The safe way to write the check is
sysdate >= add_months ( birthdate, 18 * 12 ) -- age >= 18 years or 18 * 12 months
(That is how I would write it - with the comment to explain the purpose, and 18 * 12.)
Maybe try:
SQL> create table person
(name varchar2(100),
dob date,
created_date date default sysdate not null,
constraint dob_check check
(
dob <= add_months(trunc(created_date), (12*18)*-1)
)
)
Table created.
SQL> insert into person(name,dob) values ('Bob', to_date('19740101','YYYYMMDD'))
1 row created.
SQL> commit
Commit complete.
SQL> insert into person(name,dob) values ('Jane', to_date('20050101','YYYYMMDD'))
insert into person(name,dob) values ('Jane', to_date('20050101','YYYYMMDD'))
Error at line 17
ORA-02290: check constraint (MYUSER.DOB_CHECK) violated
This is because of Oracle limitation. The reason is SYSDATE is non-deterministic. You can get a different result every time you call it. So the outcome (true/false) can (will) change over time. So Oracle can't guarantee that the expression is always true for every row.
See also https://asktom.oracle.com/pls/apex/asktom.search?tag=sysdate-in-check-constraints

Oracle trigger that update record after insert

I want update book ant set amount-1, when after insert record to sell table.
create table book (
id number(3) not null,
name varchar(20),
author varchar(12),
amount number(3) not null,
constraint book_pk primary key(id)
);
create table sell (
id number(3) not null,
date varchar(20),
book_id number(3),
constraint sell_pk primary key(id)
);
I want after insert to table sell record update book table amount-1;
CREATE OR REPLACE TRIGER changes_amount_trigger
AFTER INSERT ON sell
FOR EACH ROW
BEGIN
UPDATE BOOK SET amount = amount-1 WHERE id = book_id
END;
I not know how to get inserted record book id, to update this record in book table.
Try like this,
CREATE OR REPLACE TRIGGER changes_amount_trigger
AFTER INSERT ON sell
FOR EACH ROW
BEGIN
UPDATE BOOK SET amount = amount-1 WHERE id = :new.book_id;
END;
/
Data Model Assumptions:
I am assuming you will register transactions by changing the data in the SELL table through INSERT DML SQL operations. This is also supported by your set up of a DML trigger on SELL to pass its changes as SALES information to the BOOK table. This is workable.
By accident, I tried setting up the trigger a little differently and I'd like to suggest a different approach:
Consider possibly working in the opposite direction: Change book quantities directly on the BOOK table, so a single purchase of book_id = 5 would handle queries that could:
UPDATE book SET amount = amount -1
WHERE id = 5; COMMIT;
Restocking would mean increasing the quantity of available books
by incrementing the AMOUNT value instead.
There are a few additional changes that might tighten up this two-table design and protect the integrity of the data within them for the longer term:
CREATE TABLE book (
id number(3) not null,
name varchar(20),
author varchar(12),
amount number(3) not null,
CONSTRAINT book_pk PRIMARY KEY(id)
);
ALTER TABLE book
ADD CONSTRAINT book_amt_ck CHECK (amount > 0);
ALTER TABLE book
ENABLE CONSTRAINT book_amt_ck;
To prevent negative book amount (quantity) values, a TABLE CHECK CONSTRAINT would prevent the entry of values by means of arithmetic errors in DML operations such as:
UPDATE book SET amount := amount - 1
In the example above, there is no control over decrementing the book inventory even if the quantity on hand has reached 0. Check out a few references on TABLE CHECK CONSTRAINTS to get a better understanding of what it can do for specific design situations.
Here are some design suggestions for the trigger:
Changes in book quantities should be the only triggering data element that affects the SELL table.
The trigger should account for changes in book quantities > 1.
CREATE OR REPLACE TRIGGER orders_after_update
AFTER UPDATE
ON book
FOR EACH ROW
DECLARE
v_amount number;
BEGIN
IF (:new.amount < :old.amount ) THEN
FOR v_amount in 1 .. (:old.amount - :new.amount)
LOOP
INSERT INTO sell (id, date, book_id)
VALUES (sell_seq.nextval, sysdate, :new.id);
COMMIT;
END LOOP;
END IF;
END;
For more information on triggers and their design, check a few instances to get a better understanding of how they are designed and set up.
CREATE SEQUENCE sell_seq
MINVALUE 1
START WITH 1
INCREMENT BY 1
CACHE 20;
We needed a sequence to populate the primary key/index of the SELL table. Oracle Sequences are useful for this purpose.
By watching the table changes with a trigger on the BOOK table, you can use the built in references which already exist when a table trigger fires. For example, BOOK.ID does not require an additional query because a trigger automatically is made aware of the beginning and ending value of each trigger monitored record.
Some useful discussions on triggers are discussed in more detail through an Internet search.
Setting Up a Foreign Key Relationship
Although the trigger will probably keep this relation clean, a Foreign Key relation between elements BOOK.ID and SELL.BOOK_ID would be good, otherwise queries on Sales transactions may yield book sales without any descriptive production information. The following is a reference on Foreign Keys and their use.
CREATE TABLE sell (
id number(3) not null,
date varchar(20),
book_id number(3)
);
ALTER TABLE table_name
ADD CONSTRAINT sell_fk
FOREIGN KEY (book_id)
REFERENCES book(id);
Do you already have records in sell table and want to update the amount in book table ?
if this is your case you can update your book.amount as following:
update book b
set b.amount = b.amount - (select count(*) from sell s where b.id = s.book_id);

Resources