Producing a report with a stored SQL function - sql

I am trying to produce a report that calculates a discount applied to an order using a stored function in Oracle 12c. I have a table, orders with column o_id. Each line of the order is a separate order_line table, having o_id, ol_quantity, and ol_price. If the order is over $100, a discount of $10 is applied to the order.
I have created a stored function, which compiles, but when I try to run the function I get the error:
Error(10,1): PLS-00103: Encountered the symbol "FINAL_COST" when expecting one of the following: * & - + / at mod remainder rem then <an exponent (**)> and or || multiset
Here is the function:
create or replace function DiscountsReport
(order_id IN REAL) RETURN REAL
is
percent real;
final_cost real;
being
select SUM(order_line.ol_quantity * order_line.ol_price) as total from order_line
where o_id = order_id;
if total >= 100 final_cost = 90;
return final_cost;
end;
And my code to run it is:
var cost number;
execute :cost := discountsreport(1);
print cost;

Your "if" statement is absolutely incorrect. Take a look at the documentation:
if-then-else

Related

Returning multiple values using function causing multiple query runs

We have kiosks for customers to check their purchase volume for two different categories of items. They will input their mobile number, which will send an OTP to their mobile numbers and they will input it back to authenticate, the system has to check the data and display for them. As a developer, the kiosk supplier has provided us with a limited functionality development kit by which we can execute select statement on the database and display the returned values on the kiosk.
I have created an object type as follows:
CREATE OR REPLACE TYPE rebate_values
AS
OBJECT (ASales_total number,
ACurrent_Rebate_Percent number,
ANeeded_Sales number,
ANext_Rebate_Percent number,
BSales_total number,
BCurrent_Rebate_Percent number,
BNeeded_Sales number,
BNext_Rebate_Percent number);
A function to which I will pass customers' mobile to get their sales and rebate information:
CREATE OR REPLACE FUNCTION AA_rebate_function (P_phone IN NUMBER)
RETURN rebate_values
IS
A_P_Sales_total NUMBER;
A_P_Current_Rebate_Percent NUMBER;
A_P_Needed_Sales NUMBER;
A_P_Next_Rebate_Percent NUMBER;
B_P_Sales_total NUMBER;
B_P_Current_Rebate_Percent NUMBER;
B_P_Needed_Sales NUMBER;
B_P_Next_Rebate_Percent NUMBER;
P_CODE VARCHAR (10);
BEGIN
SELECT CC_CODE
INTO P_CODE
FROM CUSTOMERS
WHERE C_MOBILE = P_phone;
FOR OUTDATA
IN (
--My Query to retrieve the data
Select ................
)
LOOP
IF OUTDATA.CLASS = 'X'
THEN
A_P_Sales_total := OUTDATA.SALES_TOTAL;
A_P_Current_Rebate_Percent := OUTDATA.CURRENT_REBATE_PERCENT;
A_P_Needed_Sales := OUTDATA.NEEDED_SALES_FOR_HIGHER_REBATE;
A_P_Next_Rebate_Percent := OUTDATA.NEXT_HIGHER_REBATE_PERCENT;
END IF;
IF OUTDATA.CLASS = 'Y'
THEN
B_P_Sales_total := OUTDATA.SALES_TOTAL;
B_P_Current_Rebate_Percent := OUTDATA.CURRENT_REBATE_PERCENT;
B_P_Needed_Sales := OUTDATA.NEEDED_SALES_FOR_HIGHER_REBATE;
B_P_Next_Rebate_Percent := OUTDATA.NEXT_HIGHER_REBATE_PERCENT;
END IF;
END LOOP;
RETURN rebate_values (A_P_Sales_total,
A_P_Current_Rebate_Percent,
A_P_Needed_Sales,
A_P_Next_Rebate_Percent,
B_P_Sales_total,
B_P_Current_Rebate_Percent,
B_P_Needed_Sales,
B_P_Next_Rebate_Percent);
END;
/
The query takes 27 seconds to retrieve the values for each customer. Each customer will have 2 rows, so that's why I have used LOOP to collect the values.
When I execute the function:
SELECT AA_rebate_function (XXXXXXXXXX) FROM DUAL;
I get data as follows in a single column within 27 seconds:
(XXXX, X, XXXX, X, XXXX, X, XXXX, X)
But when I execute the function to get the values in different columns, it takes 27 x 8 seconds = 216 seconds, i.e., approximately 3.6 minutes which is a big issue as the customer cannot wait for 3.6 minutes on the kiosk to view the data.
SELECT x.c.ASales_total,
x.c.ACurrent_Rebate_Percent,
x.c.ANeeded_Sales,
x.c.ANext_Rebate_Percent,
x.c.BSales_total,
x.c.BCurrent_Rebate_Percent,
x.c.BNeeded_Sales,
x.c.BNext_Rebate_Percent
FROM (SELECT AA_rebate_function (XXXXXXXXXX) c FROM DUAL) x;
I have tried using stored procedure with OUT values but it doesn't fit in my environment as I cannot program to execute stored procedures from the kiosk development toolkit because it only supports select statements, checked with the supplier and they don't have any plan to add that support in near future.
I tried converting the single field into multiple columns using REGEXP_SUBSTR but I get a type conversion error as it is an array.
The query is very complex and has to calculate data for the last 10 years and has millions of rows, 27 seconds is actually the optimum time to get the desired results.
Interesting! I didn't realize that when you query a function that returns an object, it runs the function once for each column you reference the object in. That's awkward.
The easiest solution I could find for this is to switch your function to be PIPELINED. You'll need to create a nested table type to do this.
create type rebate_values_t is table of rebate_values;
/
CREATE OR REPLACE FUNCTION AA_rebate_function (P_phone IN NUMBER)
RETURN rebate_values_t PIPELINED
IS
... your code here ...
PIPE ROW (rebate_values (A_P_Sales_total,
A_P_Current_Rebate_Percent,
A_P_Needed_Sales,
A_P_Next_Rebate_Percent,
B_P_Sales_total,
B_P_Current_Rebate_Percent,
B_P_Needed_Sales,
B_P_Next_Rebate_Percent));
RETURN;
END;
/
SELECT x.ASales_total,
x.ACurrent_Rebate_Percent,
x.ANeeded_Sales,
x.ANext_Rebate_Percent,
x.BSales_total,
x.BCurrent_Rebate_Percent,
x.BNeeded_Sales,
x.BNext_Rebate_Percent
FROM TABLE(AA_rebate_function (XXXXXXXXXX)) x;
For some reason, this should only execute the function once, and take 27 seconds.

Returning multiple values from an Oracle 12c function

I am writing a function in Oracle 12c (and it has to be a function) that should return 3 values, order_line.o_id, a variable total that I create in the function, and a discounted_amount variable that I create in the function.
I have gotten my function to create the discounted_amount variable and return it but I am not sure how to get it to return these other two values.
CREATE OR replace FUNCTION DiscountsReport (input_o_id IN REAL)
RETURN REAL
IS
output_o_id NUMBER;
total REAL;
discounted_total REAL;
percent REAL;
BEGIN
output_o_id := input_o_id;
SELECT SUM(o.ol_quantity * o.ol_price)
INTO total
FROM order_line o
WHERE o.o_id = input_o_id;
IF total > 100 THEN
percent := 0.1;
ELSIF total > 200 THEN
percent := 0.2;
ELSE
percent := 0.0;
END IF;
discounted_total := ( total * ( 1 - percent ) );
RETURN discounted_total;
END;
Create a new type:
CREATE OR REPLACE TYPE new_type AS OBJECT(v1 type1, v2 type2, v3 type3);
and use it after RETURN (call the result output - its type is new_type).
You can access those values using:
output.v1
output.v2
output.v3

ORACLE SQL Method Produces Warning

I'm having trouble getting the following member method to compile (count_single_buses). Would appreciate any advice on what might be wrong syntactically with my code.
CREATE OR REPLACE TYPE BodyModel2_Type AS OBJECT(
ModelID INTEGER,
ModelName VARCHAR2(45),
FloorType VARCHAR2(45),
Manufacturer VARCHAR2(45),
Length NUMBER(8,2),
Width NUMBER(8,2),
NoOfAxles INTEGER,
MEMBER FUNCTION count_single_buses(ModelID INTEGER) RETURN INTEGER);
/
CREATE OR REPLACE TYPE BODY BodyModel2_Type AS
MEMBER FUNCTION count_single_buses(ModelID INTEGER) RETURN INTEGER IS
N INTEGER;
BEGIN
N := (SELECT COUNT(BODYMODELREF) FROM SINGLEDECKBUS_TABLE S
WHERE S.BODYMODELREF = ModelID);
RETURN N;
END count_single_buses;
END;
--EDIT--
Thanks to #Ravi, I managed to solve the issue my correcting my SQL syntax and setting the resultset to a NUMBER, instead of INTEGER.
CREATE OR REPLACE TYPE BODY BodyModel_Type AS
MEMBER FUNCTION count_single_buses(thisModelID INTEGER) RETURN NUMBER IS
NUM NUMBER;
BEGIN
SELECT COUNT(S.BODYMODELREF) INTO NUM FROM SINGLEDECKBUS_TABLE S WHERE S.BODYMODELREF.MODELID = thisModelID;
RETURN NUM;
END count_single_buses;
END;
/
Still not sure why #Ravi's exact code still produced the warning, and thought that resultset when returning a count value could go into an integer. At any rate, the code works now. Thanks all.
Your BodyModel2_Type Type definition looks okay. However, the body definition is syntactically incorrect.
You cannot define a SQL statement directly to a variable, thus making this statement wrong.
N := (SELECT COUNT(BODYMODELREF) FROM SINGLEDECKBUS_TABLE S
WHERE S.BODYMODELREF = ModelID);
You will have to use Select... into statement in order to assign the result set of your SQL query into a variable. So, the right syntax should look like this
SELECT COUNT(BODYMODELREF) FROM SINGLEDECKBUS_TABLE S INTO N
WHERE S.BODYMODELREF = ModelID
AFAIK you don't have END the Type followed by the Type name like this END count_single_buses. It'll produce an error. So, overall your Type body specification should look like this:
CREATE OR REPLACE TYPE BODY BodyModel2_Type AS
MEMBER FUNCTION count_single_buses(ModelID INTEGER) RETURN NUMBER IS
N NUMBER;
BEGIN
SELECT COUNT(BODYMODELREF) FROM SINGLEDECKBUS_TABLE S INTO N
WHERE S.BODYMODELREF = ModelID;
RETURN (N);
END;
END;
/
I'm writing this off without any live environment available right now so please let me know if you come across any error in the above code.
Cheers.

Error: ORA-06550 in Oracle 10g

Here i have created a package,with package specification and package body,which contains a procedure which insert a row in table,package creation is successful but execution of that package is giving me error
ORA-06550
Package specification:
create or replace package pkgEmp --package specification created..
as
procedure insertEmp(eno number,name varchar2,job varchar2,mgr varchar2,salary number);
end pkgEmp;
package body:
create or replace package body pkgEmp
as
procedure insertEmp(eno number,name varchar2,job varchar2,mgr varchar2,salary number)
is
begin
insert into emp values(eno,name,job,mgr,salary);
end insertEmp;
end pkgEmp;
Exeuting procedure insertEmp:
begin
pkgEmp.insertEmp(&eno,&name,&job,&mgr,&salary); --i m trying to get data in run time.
End;
and output:
ORA-06550: line 2, column 18:
PLS-00103: Encountered the symbol "&" when expecting one of the following:
( ) - + case mod new not null others
table avg count current exists max min prior sql stddev sum
variance execute multiset the both leading trailing forall
merge year month DAY_ hour minute second timezone_hour
timezone_minute timezone_region timezone_abbr time timestamp
interval date
1. begin
2. pkgEmp.insertEmp(&eno,&name,&job,&mgr,&salary);
3. End;
Why I am not able to get data from User,I m using SQL*Plus
I think you need to put parameters in 'single quotes' like '&name' where the datatype is non-integer.
Thanks,
Aditya

pl-sql how to perfom a simple max(Date) function

hello
i am new to oracle db , how can i simply ask for the max date?
FUNCTION get_max_date_rec(
i_value_date IN vat.value_date%TYPE := app_utilities_q.server_sys_date )
RETURN vat.rec_id%TYPE
IS
v_date vat.value_date%TYPE;
BEGIN
SELECT MAX(v.value_date)--compiler err
INTO v_date
FROM vat v
WHERE v.value_date < i_value_date
RETURN get_rec_by_date(v_date).rec_id;--compiler err
END get_max_date_rec;
EDIT
this is the err created by the compiler
Error(76,7): PL/SQL: SQL Statement ignored
Error(81,7): PL/SQL: ORA-00933: SQL command not properly ended
I want to return rec_id as writen above...
FUNCTION get_max_date_rec(
i_value_date IN vat.value_date%TYPE
default app_utilities_q.server_sys_date -- assuming this is a default
)
RETURN vat.rec_id%TYPE
IS
v_date vat.value_date%TYPE;
BEGIN
SELECT MAX(v.value_date)--compiler err
INTO v_date
FROM vat v
WHERE v.value_date < i_value_date
RETURN v_date;
END get_max_date_rec;
One risk is that if no records in vat exist for a date greater than i_value_date, the code will fail, throwing the NO_DATA_FOUND exception. You should consider how you might wish to handle that condition - or not handle it, if that's the correct thing to do.
the problem was
not adding
;
in the end of select
SELECT MAX(v.value_date)--compiler err
INTO v_date
FROM vat v
WHERE v.value_date < i_value_date ;