How to execute trigger and procedure in oracle - oracle

I am working on my Project 'Supermarket Billing Management System' since I am a beginner I m facing a lot of issues while making project. Here I've already created a trigger and a procedure but I don't know how I can execute it, I've created a trigger for a total price of a single Product i.e ProdTotal = ProdPrice * ProdQuantity;.
That means whenever a user enters some data in the Products table, then this trigger must get executed, but I don't know how to execute it, similarly, I've created a procedure to calculate the total price of all the products purchased by a single customer. Just like when you go to the supermarket or any store then after purchasing the items, you get a bill and there is the final total amount. I m not even exactly sure whether my procedure code is right or wrong, though it was created successfully, I m not sure whether it will give me the exact output which I want, So if you can help me then, please let me know, I've researched a lot from different websites and also from many youtube videos but seriously I am not getting how to solve it, so please help me!
Code:
Products table
create table Products
( ProdId number primary key,
ProdNum number not null unique,
ProdName varchar2(15),
ProdPrice int,
ProdQuantity int,
ProdCustId int references Customers,
ProdOrdId int references Orders,
ProdStoreId int references Stores
);
Payments table
create table Payments
( PayId int primary key,
PayDate date,
ProdTotal int,
FinalTotal int,
PayOrdId int references orders,
PayProdId int references Products,
PayCustId int references Customers
);
Trigger code
create trigger PROD_TOTAL
AFTER INSERT ON Products
BEGIN
UPDATE Payments
SET ProdTotal = (SELECT Products.ProdPrice * Products.ProdQuantity FROM Products);
END;
/
Procedure code
create procedure FINAL_TOTAL(C IN NUMBER, T OUT NUMBER)
IS
BEGIN
UPDATE Payments
SET FinalTotal = FinalTotal + ProdTotal
WHERE PayCustId = C;
Commit;
SELECT FinalTotal into T FROM Payments WHERE PayCustId = C;
END;
/
Insert statement in Product table:
insert into Products values(1,1001,'Syrup',30,4,1,1,1);
Insert statements in Payments table:
insert into Payments(PayId, PayDate, PayOrdID, PayProdId, PayCustId)
values(1,date'2020-10-07',1,1,1);
Output:
select * from products;
PRODID PRODNUM PRODNAME PRODPRICE PRODQUANTITY PRODCUSTID
---------- ---------- --------------- ---------- ------------ ----------
PRODORDID PRODSTOREID
---------- -----------
1 1001 Syrup 30 4 1
1 1
select * from Payments;
PAYID PAYDATE PRODTOTAL FINALTOTAL PAYORDID PAYPRODID PAYCUSTID
---------- --------- ---------- ---------- ---------- ---------- ----------
1 07-OCT-20 1 1 1
Now here, as you can see PRODTOTAL and FINALTOTAL column is blank, I know why it is blank because I didn't enter any value. And the reason why I didn't enter any value in these two columns is that I want, the system should automatically calculate that calculation with the help of trigger and procedure and, I can't even remove trigger and procedure because it's mandatory in our project to use both of these concepts. So please help me!!!

As already proposed, first try to get the design right with respect to your requirements. You can implement many constraints just by designing correctly your database schema.
Stay away from triggers and PL/SQL for as long as possible. It will force you to better design in the end and will pay off.
Before using triggers for business logic, try to use views for things that can be selected. That's what the database is for.
When you are "done", test for the performance and if it's suboptimal, improve your schema. If nothing helps, start using triggers for business logic.
I've put together a sample with views I am talking about. I hope it can get you started.
create table Products (
ProdId number generated always as identity primary key
, ProdName varchar2(20) not null
);
create table Stores (
StoreId number generated always as identity primary key
, StoreName varchar2(20) not null
);
create table Customers (
CustomerId number generated always as identity primary key
, CustomerName varchar2(20) not null
);
create table Prices (
PriceId number generated always as identity primary key
, ProdId number not null
, Price number
, ValidFrom date default on null sysdate
, constraint fk_Prices_Product foreign key (ProdId) references Products (ProdId)
);
create unique index uniq_prices_product_price on Prices (ProdId, ValidFrom);
create table Orders (
OrderId number generated always as identity primary key
, CustomerId number not null
, StoreId number not null
, OrderedAt date default on null sysdate
, constraint fk_Orders_Customer foreign key (CustomerId) references Customers (CustomerId)
, constraint fk_Orders_Store foreign key (StoreId) references Stores (StoreId)
);
create table OrderLines (
OrderLineId number generated always as identity primary key
, OrderId number not null
, ProdId number not null
, ProdQuantity number not null
, constraint fk_OrderLines_Order foreign key (OrderId) references Orders (OrderId)
, constraint fk_OrderLines_Prod foreign key (ProdId) references Products (ProdId)
);
create table Payments (
PaymentId number generated always as identity primary key
, OrderId number not null
, PaidAt date default on null sysdate
, PaidAmount number not null
, constraint fk_Payments_Order foreign key (OrderId) references Orders (OrderId)
);
create view Prices_V as
select
p.*
, coalesce(
lead(p.ValidFrom) over (partition by p.ProdId order by p.ValidFrom)
, to_date('9999', 'YYYY')
) ValidTo
from Prices p;
create view Orders_V as
select
o.*
, (
select sum(ol.ProdQuantity * p.Price)
from OrderLines ol
join Prices_V p on (p.ProdId = ol.ProdId and o.OrderedAt between p.ValidFrom and p.ValidTo)
where o.OrderId = ol.OrderId
) Total
, (
select sum(PaidAmount)
from Payments p
where p.OrderId = o.OrderId
) TotalPaid
from Orders o;
insert into Products(ProdName)
select 'Prod A' from dual union all
select 'Prod B' from dual;
insert into Stores(StoreName) values ('Store A');
insert into Customers(CustomerName)
select 'Customer A' from dual union all
select 'Customer B' from dual;
insert into Prices(ProdId, Price, ValidFrom)
select 1, 10, sysdate - 10 from dual union all
select 1, 12, sysdate - 2 from dual union all
select 1, 14, sysdate + 3 from dual union all
select 2, 100, sysdate - 10 from dual union all
select 2, 90, sysdate - 2 from dual union all
select 2, null, sysdate + 5 from dual;
insert into Orders(CustomerId, StoreId, OrderedAt)
select 1 cid, 1 stoid, sysdate - 5 from dual union all
select 2, 1, sysdate - 5 from dual union all
select 2, 1, sysdate - 1 from dual;
insert into OrderLines(OrderId, ProdId, ProdQuantity)
select 1 ordid, 1 prodid, 3 prodquant from dual union all
select 1, 2, 2 from dual union all
select 2, 2, 10 from dual union all
select 3, 2, 10 from dual;
insert into Payments(OrderId, PaidAmount) values (2, 500);
select * from Prices_V order by ProdId, ValidFrom;
select * from OrderLines order by OrderId, ProdId;
select * from Orders_v order by OrderId;
Some of the ideas in there:
Prices are stored in separate table, reference the product and have validity so that product price can change over time. Price view have ValidTo column added so it's easier to work with
There is a unique index on Prices so that we cannot have 2 prices for the same product at the same time
You can have many items in order, so that's why there is Orders and OrderLines tables in 1-to-many relationship
In Order_V the total paid is shown (using a subquery on Payments) and the total order values is shown (using a subquery on OrderLines and Prices, date of order is used to get prices form the correct period)
Based on the schema you will se what things you can represent and which you cannot. It's your job to make it match your requirements :)
And now I've come to the point you say triggers and procedures are mandatory in your project. Hence I have a proposal:
Create a procedure that will allow users to create new price for a product. It should definitely check that the validity does not start in the past. Then implement another one that allows for changing the valid to date (also cannot end in the past). You can than revoke any insert/update privileges on Products table and force users to use your procedures that will contain this business logic.
Create a table PricesLog and trigger on Prices that will insert the PriceId, old.Price, new.Price, sysdate and User to the log on any inserts/updates to the prices table.

Related

In oracle SQL DB same primary Id is present more then once with different batch_id. How can I know the batch ID just before the current batch ID

I am working on oracle database.
We load customer data in source table which eventually migrates to target table.
Every time customer data is loaded in source table it is having a unique batch_id.
If we want to update some field in customer table, then we again load the same customer in source table but this time with different batch_id.
Now I want to know batch_id of the customer just before the latest batch_id.
Batch_id we take is usually the current date.
Use ROW_NUMBER analytic function
your sample data
select * from tab
order by 1,2
CUSTOMER_ID BATCH_ID
----------- -------------------
1 09.12.2019 00:00:00
1 10.12.2019 00:00:00
2 10.12.2019 00:00:00
Row_number assihns sequence number starting from 1 for each customer order descending on BATCH_ID - you are interested on one before the latest, i.e. the rows with the number 2.
with cust as (
select
customer_id, batch_id,
row_number() over (partition by customer_id order by batch_id desc) rn
from tab)
select CUSTOMER_ID, BATCH_ID
from cust
where rn = 2;
CUSTOMER_ID BATCH_ID
----------- -------------------
1 09.12.2019 00:00:00
It seems that you're basically looking for the second biggest value in the SOURCE table.
In this example code the SOURCE_TABLE represents the table containing same CUSTOMER_NO with different BATCH_NO:
create table source_table (customer_no integer, batch_no date);
insert into source_table values ('1', SYSDATE-2);
insert into source_table values ('1', SYSDATE-1);
insert into source_table values ('1', SYSDATE);
SELECT batch_no
FROM (
SELECT batch_no, row_number() over (order by batch_no desc) as row_num
FROM source_table
) t
WHERE row_num = 2
Where row_num = 2 represents the second biggest value in the table.
The query returns SYSDATE-1.

Okay so I am trying to a rownum into a variable but I need it to give me only one value, so 2 if it's the second number in the row

select rownum into v_rownum
from waitlist
where p_callnum=callnum
order by sysdate;
tried doing this but gives too many values.
and if I do p_snum=snum, it will keep returning 1. I need it to return 2 if it's #2 on the waitlist.
select rn into v_rownum
from (select callnum,
row_number() over (order by sysdate) rn
from waitlist)
where p_snum=snum;
Almost got it to work. Running into issues in the first select. I believe I might have to use v_count instead. Also Ordering by Sysdate even if a second apart will order it correctly.
SNU CALLNUM TIME
--- ---------- ---------
101 10125 11-DEC-18
103 10125 11-DEC-18
BTW time is = date which I entered people into waitlist using sysdate. So I suppose ordering by time could work.
create table waitlist(
snum varchar2(3),
callnum number(8),
time date,
constraint fk_waitlist_snum foreign key(snum) references students(snum),
constraint fk_waitlist_callnum foreign key(callnum) references schclasses(callnum),
primary key(snum,callnum)
);
is the waitlist table.
I used Scott's DEPT table to create your WAITLIST; department numbers represent CALLNUM column:
SQL> select * From waitlist;
CALLNUM WAITER
---------- --------------------
10 ACCOUNTING
20 RESEARCH
30 SALES
40 OPERATIONS
How to fetch data you need?
using analytic function (ROW_NUMBER) which orders values by CALLNUMs, you'll know the order
that query will be used as an inline view for the main query that returns number in the waitlist for any CALLNUM
Here's how:
SQL> select rn
2 from (select callnum,
3 row_number() over (order by callnum) rn
4 from waitlist
5 )
6 where callnum = 30;
RN
----------
3
SQL>
rownum in oracle is a generated column, it does not refer to any specific row, it is just the nth row in a set.
With a select into it can only return one row (hence the two many rows error) so rownum will always be 1.
Without more details about your table structure, and how you are uniquely identifying records it is hard to give assist you further with a solution.

Need help in table creation with constraints

I have a requirement in which I have to create,
a person table with person id, Fname, Lname attributes
and also a person can be an employee or manager.
There is a constraint, a manager should be mapped to 1 or more emp (at least one emp).
Now, in this case, do we need to create a separate table for employee and manager and link it to person, if so how do we link both the table to person table and maintain the above-mentioned constraint
Note: There are no specific attributes for emp or manager table
That looks like a standard Scott's EMP table (if you're familiar with it). Something like this:
ID_EMP column is common to all persons
ID_MGR shows who is that person's manager
SQL> create table person
2 (id_emp number constraint pk_pers primary key,
3 fname varchar2(20),
4 lname varchar2(20),
5 id_mgr number constraint fk_mgr_emp references person (id_emp)
6 );
Table created.
SQL> insert into person (id_emp, fname, lname, id_mgr)
2 select 1, 'Little' , 'Foot' , null from dual union
3 select 2, 'Charles', 'Leclerc' , 1 from dual union
4 select 3, 'Rio' , 'Haryanto', 1 from dual union
5 select 4, 'Seb' , 'Vettel' , 2 from dual union
6 select 5, 'Romain' , 'Grosjean', 2 from dual;
5 rows created.
Hierarchical view:
SQL> select lpad(' ', 2 * level) || lname employee
2 from person
3 start with id_mgr is null
4 connect by prior id_emp = id_mgr;
EMPLOYEE
-------------------------------------------------------------------------------
Foot
Leclerc
Vettel
Grosjean
Haryanto
SQL>

How to bind horizontal values of a table to a vertical values of another table in oracle database

i have 2 tables .
The columns start with attributes are change based on department. the description of attributes are here
My requirement is to get the values of each attributes with its primary key based on the department as table bellow.
Honestly i am stuck on this problem in my program. I have no permission to change the tables and there is no common unique key column.i would appreciate if anyone could provide me a suggestion.
with a as (
select a.*, row_number() over (partition by department order by attributeID) rn
from attributes a),
e as (
select employeeId, department, attribute1, 1 rn from employees union all
select employeeId, department, attribute2, 2 rn from employees union all
select employeeId, department, attribute3, 3 rn from employees
)
select e.employeeId, a.attributeid, e.department, a.attribute, a.meaning,
e.attribute1 as value
from e join a on a.department=e.department and a.rn=e.rn
order by e.employeeId, a.attributeid
Test data and output:
create table employees (employeeID number(3), name varchar2(10), department varchar2(5), age number(3), attribute1 varchar2(10), attribute2 varchar2(10), attribute3 varchar2(10));
insert into employees values (1, 'john', 'IT', 22, 'attr1val1', 'attr2val2', null);
insert into employees values (2, 'jane', 'HR', 32, 'attr1val3', 'attr2val4', 'attr3val5');
insert into employees values (3, 'joe', 'HR', 23, 'attr1val6', 'attr2val7', 'attr3val8');
insert into employees values (4, 'jack', 'IT', 45, 'attr1val9', 'attr2val10', null);
create table attributes (attributeID number(3), department varchar2(10), attribute varchar2(10), meaning varchar2(10));
insert into attributes values (1, 'IT', 'attribute1', 'laptoptype');
insert into attributes values (2, 'IT', 'attribute2', 'networkloc');
insert into attributes values (3, 'HR', 'attribute1', 'location');
insert into attributes values (4, 'HR', 'attribute2', 'position');
insert into attributes values (5, 'HR', 'attribute3', 'allocation');
EMPLOYEEID ATTRIBUTEID DEPARTMENT ATTRIBUTE MEANING VALUE
---------- ----------- ---------- ---------- ---------- ----------
1 1 IT attribute1 laptoptype attr1val1
1 2 IT attribute2 networkloc attr2val2
2 3 HR attribute1 location attr1val3
2 4 HR attribute2 position attr2val4
2 5 HR attribute3 allocation attr3val5
3 3 HR attribute1 location attr1val6
3 4 HR attribute2 position attr2val7
3 5 HR attribute3 allocation attr3val8
4 1 IT attribute1 laptoptype attr1val9
4 2 IT attribute2 networkloc attr2val10
Edit: Explanation
In answer I used with
clause just to divide solution into readable steps. You can move them into from clause of main query if it is
more comfortable for you. Anyway: subquery a reads data from table attributes and adds number for rows,
so for each department they are allways numbered from 1. I used row_number() for that. Subquery e unions (all) required attributes and numbers
them accordingly. Numbers generated in both subqueries are then used in main join: a.department=e.department and a.rn=e.rn.
Alternative 1 - if you are using Oracle 11g you could use the unpivot. See what is generated by subquery, and how it is joined with attributes table:
with e as (
select employeeId, name, department, attribute, value from employees
unpivot (value for attribute in ("ATTRIBUTE1", "ATTRIBUTE2", "ATTRIBUTE3"))
)
select e.employeeId, a.attributeid, e.department, a.attribute,
a.meaning, e.value
from e join attributes a on a.department=e.department
and lower(a.attribute)=lower(e.attribute)
order by e.employeeId, a.attributeid;
Alternative 2 - with hierarchical subquery generator (subquery r), realised by connect by which simple creates numbers from 1, 2, 3 which are next joined with employees and proper attribute
is attached as value in case clause. Rest is made in similiar way like in original answer.
with a as (
select a.*, row_number() over (partition by department order by attributeID) rn
from attributes a),
r as (select level rn from dual connect by level<=3),
e as (
select employeeId, department, rn,
case when r.rn = 1 then attribute1
when r.rn = 2 then attribute2
when r.rn = 3 then attribute3
end value
from employees cross join r
)
select e.employeeId, a.attributeid, e.department, a.attribute,
a.meaning, e.value
from e join a on a.department=e.department and a.rn=e.rn
order by e.employeeId, a.attributeid
All three versions gave me the same output. I also tested first option on similiar table with 100k rows and get output in few seconds (for 5 attributes). Please test all solutions and try to understand them. If you can use unpivot version I would prefer this.
Sorry for delayed explanation and any language mistakes.
The WITH clause was added with Oracle 9.2 and should do the trick. For the other attributes just add more sub queries where the filter is att.attribute = 'attribute2' or 'Attribute3'...
WITH e AS
(SELECT emp.employee_ID, emp.department, emp.attribute1
FROM employee emp),
a AS (SELECT att.attribute_id, att.attribute, att.meaning
FROM attribute_TYPE att
WHERE att.attribute = 'attribute1')a
SELECT e.employeeid, att.attributeid, e.department, a.attribute,
a.meaning e.attribute1
FROM e JOIN a ON e.department = a.department

ORACLE: How to select previous different value?

I have table that stores employee job name, it has the following columns:
id; date_from; date_to; emp_id; jobname_id; grade;
Each emp_id can have many consecutive records with the same jobname_id due to many grade changes.
How can I select previous different jobname_id omitting those that are the same like the most current one?
This solution uses the FIRST_VALUE() analytic function to identify each employee's current job. It then filters for all the jobs which dfon't match that one:
select distinct id
, jobname_id
from ( select id
, jobname_id
, first_value(jobname_id) over (partition by id
order by from_date desc) as current_job
from employee
where emp_id = 1234 )
where jobname_id != current_job
order by id, jobname_id
/
Will this work for your issue:
SELECT DISTINCT
e1.emp_id,
e1.jobname_id
FROM employee e1
WHERE NOT EXISTS
(SELECT 1
FROM employee e2
WHERE e1.emp_id = e2.emp_id
AND SYSDATE BETWEEN e2.date_from
AND NVL(e2.date_to, SYSDATE + 1));
(This asumes your table is named "employee" and emp_id is the PK value).
It selects unique emp_id, jobname_id values where the emp_id, jobname_id values are not current.
EDIT: I agree with Chin Boon that fundamentally this is a design issue and perhaps that should be addressed rather than working around the problem.

Resources