Invoke a function from a procedure - sql

I am trying to create a PL/SQL procedure that will invoke a function I called GET_HIGHORDER_FUNC which is already working:
create or replace FUNCTION GET_HIGHORDER_FUNC
return number
AS
c_hiorder number;
BEGIN
select max(sum(product.product_standardprice * orderline.ordered_quantity)) into c_hiorder
from customer, product, orderline, orders
where customer.customer_id = orders.customer_id
and orders.order_id = orderline.order_id
and orderline.product_id = product.product_id
group by customer_name;
RETURN c_hiorder;
END GET_HIGHORDER_FUNC;
create or replace procedure PRINT_CUST_PROC(
p_hiordername in number)
as
begin
/* This procedure should show the name of the customer who have the highest
amount of order which will be available upon invoking the function above */
end;

Just invoke it as you would with any function?
This is how i would try based on your latest comments
create or replace FUNCTION GET_HIGHORDER_FUNC(c_customer_name out varchar2,c_hiorder out int)
return number
AS
c_hiorder number;
BEGIN
select y.customer_name
,y.summed_price
into customer_name
,c_hiorder
from (
select x.customer_name
,x.summed_price
,row_number() over(order by x.summed_price desc) as rnk
from (
select customer_name
,sum(product.product_standardprice * orderline.ordered_quantity) as summed_price
from customer, product, orderline, orders
where customer.customer_id = orders.customer_id
and orders.order_id = orderline.order_id
and orderline.product_id = product.product_id
group by customer_name
)x
)y
where y.rnk=1;
RETURN 1;
END GET_HIGHORDER_FUNC;
create or replace procedure PRINT_CUST_PROC(
p_hiordername in number)
as
l_return int;
l_customer_name varchar2(1000);
l_hiorder int;
begin
/* This procedure should show the name of the customer who have the highest
amount of order which will be available upon invoking the function above
*/
l_return : = GET_HIGHORDER_FUNC(l_customer_name,l_hiorder);
dbms_output.put_line(l_customer_name);
dbms_output.put_line(l_hiorder);
end;

Related

PostgreSQL select sum inside procedure

I want a procedure that checks, which buyer spent more than 1600$ and than I want to print out their info, so I could send them a gift card.
I get error: SQL Error [42601]: ERROR: query has no destination for result data
when I call this procedure:
create or replace procedure bookstore.procedure1 ()
language plpgsql
as $$
declare
i integer := 0;
temp_price numeric;
temp_sum numeric;
temp_foreign_key integer;
number_of_buyers integer := (select count(*) from bookstore.buyer);
begin
while i < number_of_buyers loop
select sum (price) as temp_sum from bookstore.receipt where id_buyer = i;
if temp_sum > 1600 then
select id_buyer, name, surname, adress from bookstore.buyer where id_buyer = i;
end if;
i := i+1;
end loop;
end
$$;
The problem is in select sum (price) as temp_sum... row. I have the similar procedure in MySQL and it works. I tried running that row with id_buyer = 20 for example and it worked. How should I change it?
That's my first question on stackoverflow, I hope it's understandable.
Let SQL do all the work of serving the data. This can be done in a single statement.
select buy.buy_id
, buy.name
, buy.address
, buy.city
, buy.postal_code
, pur.purchases
from buyers buy
join ( select inv.buy_id
, sum(inv.amount) purchases
from invoices inv
group by buy_id
having sum(inv.amount) >= 1600.00
) pur
on (pur.buy_id = buy.buy_id) ;
If you must have a stored program, then wrap the above query in a SQL function that returns a table. then Select from the function.
create or replace
function preferred_buyers()
returns table ( buy_id integer
, name text
, address text
, city text
, postal_code text
, purchases numeric(7,2)
)
language sql
as $$
select buy.buy_id
, buy.name
, buy.address
, buy.city
, buy.postal_code
, pur.purchases
from buyers buy
join ( select inv.buy_id
, sum(inv.amount) purchases
from invoices inv
group by buy_id
having sum(inv.amount) >= 1600.00
) pur
on (pur.buy_id = buy.buy_id) ;
$$;
select * from preferred_buyers();
Neither of the above handles printing task. This is not something SQL very good at. Actually it cannot do so. Printing requires a programming language extension; for Postgres it is plpgsql. Also realize any printing done is not available at least in a production environment; it would be on the DB server. Handle your printing in the presentation manager (app).
You probably need to add the return statement
In the Function creation definition SQL, there must be a row written as RETURNS integer/text
Try-
language plpgsql
as $$
declare
i integer := 0;
temp_price numeric;
temp_sum numeric;
temp_foreign_key integer;
number_of_buyers integer := (select count(*) from bookstore.buyer);
begin
while i < number_of_buyers loop
select sum (price) as temp_sum from bookstore.receipt where id_buyer = i;
if temp_sum > 1600 then
select id_buyer, name, surname, adress from bookstore.buyer where id_buyer = i;
end if;
i := i+1;
end loop;
return 1;
end
$$;
Or else you can remove the initial statement of RETURNS text/integer by using CREATE OR REPLACE FUNCTION your_function ...
In plpgsql SELECT statement must have a receiver.
You must redirect the SELECT output with INTO or use direct affectation
--Fist version
SELECT SUM(price) FROM bookstore.receipt WHERE id_buyer = i INTO temp_sum;
--Second version, keep surrounding parenthesis
temp_sum = (SELECT SUM(price) FROM bookstore.receipt WHERE id_buyer = i);
Regarding your loop, you should use something more efficient and safe
DECLARE
temp_sum NUMERIC;
v_buyer bookstore.BUYER;
BEGIN
FOR v_buyer IN SELECT * FROM bookstore.buyer LOOP
--Use coalesce to manage empty result
temp_sum = (SELECT coalesce(SUM(price), 0) FROM bookstore.receipt WHERE id_buyer = v_buyer.id);
IF temp_sum > 1600 THEN
-- Your business logic
END IF;
END LOOP;
END
If your dataset is large it's better to use a cursor
DECLARE
temp_sum NUMERIC;
v_buyer_cursor CURSOR FOR SELECT * FROM bookstore.buyer;
v_buyer bookstore.BUYER;
BEGIN
FOR v_buyer IN v_buyer_cursor LOOP
temp_sum = (SELECT COALESCE(SUM(price), 0) FROM bookstore.receipt WHERE id_buyer = v_buyer.id);
IF temp_sum > 1600 THEN
-- Your business logic
END IF;
END LOOP;
END
None of the answers helped. I had to replace this row:
SELECT id_buyer, name, surname, adress FROM bookstore.buyer WHERE id_buyer = i;
with this row:
INSERT INTO bookstore.temp SELECT id_buyer, name, surname, adress FROM bookstore.buyer WHERE id_buyer = i;
Now it works.

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;

How do you call a Procedure in Block SQL

My procedure is below;
I have to create an anonymous block that CALLS the procedure and DISPLAYS all the customers total price and number of cars purchased from that PARTICULAR city
Can someone show me how to do this BLOCK
My procedure is as FOLLOW:
CREATE OR REPLACE PROCEDURE getCars
( v_custname OUT car.custname%TYPE,
v_purchcost OUT car.purchcost%TYPE,
v_city IN customer.custcity%TYPE,
v_count OUT NUMBER,
v_total OUT car.purchcost%TYPE
)
AS
BEGIN
Select c.custname, Count(c.custname), SUM(purchcost)
INTO v_custname, v_count, v_total
From car c
JOIN customer cs
ON c.custname = cs.custname
WHERE cs.custcity = v_city
Group By c.custname;
END;
/
This is what I have for the BLOCK to call procedure BUT ITS NOT WORKING
SET SERVEROUTPUT ON
SET VER OFF
ACCEPT p_city PROMPT 'Enter City Name: ';
DECLARE
CURSOR cname IS
Select customer.custname
INTO custname
From customer
Where UPPER(custcity) = UPPER('&p_city');
BEGIN
FOR
v_car in cname
LOOP
getCars(v_car.custname, v_count, v_total);
END LOOP;
DBMS_OUTPUT.PUT_LINE(v_car.custname||v_count||v_total);
END;
/
If you only want to display them the following can be done:
CREATE OR REPLACE PROCEDURE getcars (
v_city IN customer.custcity%TYPE
) AS
BEGIN
FOR c IN (
SELECT
car.custname,
COUNT(car.custname) count_custname,
SUM(purchcost) purchcost
FROM
car
JOIN customer cs ON car.custname = cs.custname
WHERE
cs.custcity = v_city
GROUP BY
car.custname
) LOOP
dbms_output.put_line('Customer '
|| c.custname
|| ' has bought '
|| c.count_custname
|| ' totaling '
|| purchcost);
END LOOP;
END;
Call the procedure:
DECLARE
v_city customer.custcity%TYPE := 'some city';
BEGIN
getcars(v_city);
END;
Otherwise if you need to return it for each customer then you should use cursors or more complicated data structures.

Oracle PL SQL, Simplifying my procedure

I have a following procedure.
PROCEDURE PROC_SELECT_ORDERTBL(
my_cursor OUT SYS_REFCURSOR,
p_ORDER_ID IN VARCHAR2
...
)
BEGIN
IF p_ORDER_ID IS NULL THEN
OPEN my_cursor FOR
[VERY LONG SELECT STATEMENT1]
ELSE
OPEN my_cursor FOR
[VERY LONG SELECT STATEMENT2]
END IF;
END PROC_SELECT_ORDERTBL
select statement 1 and 2 are almost same.
statement2 = statement1 + where clase(checking p_ORDER_ID)
I want to simplifying my procedure like next.
PROCEDURE PROC_SELECT_ORDERTBL(
my_cursor OUT SYS_REFCURSOR,
p_ORDER_ID IN VARCHAR2
...
)
BEGIN
WITH viewData AS
[VERY LONG SELECT STATEMENT1]
IF p_ORDER_ID IS NULL THEN
OPEN my_cursor FOR
viewData
ELSE
OPEN my_cursor FOR
viewData + where clause
END IF;
END PROC_SELECT_ORDERTBL
But this doesn't compile.
----------------------This in my whole procedure code-------------------
-- ORDERTBL 종이식권 주문단위로 조회
PROCEDURE PROC_SELECT_ORDERTBL (
my_cursor OUT SYS_REFCURSOR, -- CURSOR
p_AREA_ID IN VARCHAR2, -- AREA_ID
p_EQP_ID IN VARCHAR2, -- EQP_ID
p_ORDER_ID IN VARCHAR2, -- ORDER_ID
p_date_from IN VARCHAR2, -- yymmdd 조회시작일
p_date_to IN VARCHAR2, -- yymmdd 조회종료일
p_errorcode OUT NUMBER -- error code
) AS
BEGIN
p_errorcode := 0;
IF p_ORDER_ID IS NULL THEN
OPEN my_cursor FOR
SELECT ORD.ORDER_DATE AS 판매일자, ORD.ORDER_TIME AS 판매시간, ORD_ID.ORDER_ID AS 주문번호, TOTAL_SALES AS 판매금액
FROM
(
--판매일자, 판매시간, 판매금액
SELECT ORDER_DATE, ORDER_TIME, SUM(ORDER_FEE) AS TOTAL_SALES
FROM
(
SELECT DISTINCT ORDER_DATE, ORDER_TIME, ORDER_FEE FROM ORDERTBL
WHERE AREA_ID = p_AREA_ID AND EQP_ID = p_EQP_ID AND
ORDER_DATE >= p_date_from AND ORDER_DATE <= p_date_to
)
GROUP BY ORDER_DATE, ORDER_TIME
) ORD
JOIN
(
--판매일자, 판매시간, 주문번호
SELECT ORDER_DATE, ORDER_TIME, MIN(ORDER_ID) AS ORDER_ID
FROM ORDERTBL
WHERE AREA_ID = p_AREA_ID AND EQP_ID = p_EQP_ID AND
ORDER_DATE >= p_date_from AND ORDER_DATE <= p_date_to
GROUP BY ORDER_DATE, ORDER_TIME
) ORD_ID
ON ORD.ORDER_DATE = ORD_ID.ORDER_DATE AND ORD.ORDER_TIME = ORD_ID.ORDER_TIME
ORDER BY ORD.ORDER_DATE, ORD.ORDER_TIME;
ELSE
OPEN my_cursor FOR
SELECT ORD.ORDER_DATE AS 판매일자, ORD.ORDER_TIME AS 판매시간, ORD_ID.ORDER_ID AS 주문번호, TOTAL_SALES AS 판매금액
FROM
(
--판매일자, 판매시간, 판매금액
SELECT ORDER_DATE, ORDER_TIME, SUM(ORDER_FEE) AS TOTAL_SALES
FROM
(
SELECT DISTINCT ORDER_DATE, ORDER_TIME, ORDER_FEE FROM ORDERTBL
WHERE AREA_ID = p_AREA_ID AND EQP_ID = p_EQP_ID AND
ORDER_DATE >= p_date_from AND ORDER_DATE <= p_date_to
)
GROUP BY ORDER_DATE, ORDER_TIME
) ORD
JOIN
(
--판매일자, 판매시간, 주문번호
SELECT ORDER_DATE, ORDER_TIME, MIN(ORDER_ID) AS ORDER_ID
FROM ORDERTBL
WHERE AREA_ID = p_AREA_ID AND EQP_ID = p_EQP_ID AND
ORDER_DATE >= p_date_from AND ORDER_DATE <= p_date_to
GROUP BY ORDER_DATE, ORDER_TIME
) ORD_ID
ON ORD.ORDER_DATE = ORD_ID.ORDER_DATE AND ORD.ORDER_TIME = ORD_ID.ORDER_TIME
WHERE ORD_ID.ORDER_ID = p_ORDER_ID;
END IF;
EXCEPTION
WHEN OTHERS THEN
p_errorcode := SQLCODE;
END PROC_SELECT_ORDERTBL;
You can use CASE construct in the where clause. The logic would be similar to your IF-ELSE condition. But, it is not a good coding practice.
I would go with NVL and DECODE.
Update : The DECODE and NVL parameter values depends on the column data type.
If P_ORDER_ID is NUMBER then use :
WHERE
DECODE(p_order_id, NULL, 0, ORD_ID.ORDER_ID) = NVL(p_ORDER_ID, 0)
else if P_ORDER_ID is VARCHAR2 then use :
WHERE
DECODE(p_order_id, NULL, '0', ORD_ID.ORDER_ID) = NVL(p_ORDER_ID, '0')

How can I change the following procedure into an Instead Of trigger?

CREATE or REPLACE PROCEDURE UPDATE_SUBTOTAL is
v_order_no orderline.order_no%type;
v_subtotal number(15);
CURSOR product_orderline_cur is
SELECT ol.order_no, sum(p.unit_price * ol.qty) as subtotal
from product p, orderline ol
where p.product_no = ol.product_no
group by ol.order_no;
BEGIN
OPEN product_orderline_cur;
LOOP
FETCH product_orderline_cur into v_order_no, v_subtotal;
EXIT when product_orderline_cur%notfound;
-- store subtotal in orders table
UPDATE orders
SET subtotal = v_subtotal
WHERE order_no = v_order_no;
END LOOP;
--an order may be created but no orderlines added yet,insert a 0
UPDATE orders
SET subtotal = 0
WHERE subtotal is null;
CLOSE product_orderline_cur;
END;
/
show errors;
CREATE OR REPLACE TRIGGER orders_before_update
BEFORE UPDATE ON orders
FOR EACH ROW
DECLARE v_sum INT;
BEGIN
SELECT SUM(p.unit_price * ol.qty)
INTO v_sum
FROM product p INNER JOIN orderline ol
ON p.product_no = ol.product_no
WHERE ol.order_no = :new.order_no;
:new.subtotal := COALESCE(v_sum, 0);
END;