Function/Package issue PL/SQL - sql

I am trying to create a package with four functions. Each function will add a set of numbers and subtract one from the total. I have been having lots of trouble getting the syntax correct. The functions below work on their own, and i try calling the first function at the end.
When I try to create the package i get an error where on line 7, " Encountered the symbol "END" when expecting one of the following: begin function pragma procedure subtype type current cursor delete exists prior 0.05 seconds"
In the package body it says "name is already in use by an existing object". I don't understand as it has to be declared in the specification of the package anyway, and create or replace should solve this if the error is that there is already a package named functionbyfour.
And finally, when I try to use a function in the package, it says " Encountered the symbol "BEGIN" when expecting one of the following: := . ( # % ; not null range default character The symbol ";" was substituted for "BEGIN" to continue. ORA-06550: line 5, column 43: PLS-00103: Encountered the symbol "FROM" when expecting one of the following: . ( * % & = - + ; < / > at in is mod remainder not rem <> or != or ~= >= <= <> and or like like2 like4 likec between || multiset me".
I am using ORACLE EXPRESS edition 11g and am new to PL/SQL(4 weeks).
Any input is greatly appreciated.
CREATE OR REPLACE FUNCTION functionbyfour AS
FUNCTION functone( first number, second number) RETURN NUMBER ;
FUNCTION functtwo( first number, second number, third number) RETURN NUMBER ;
FUNCTION functthree(first number, second number, third number, fourth number) RETURN NUMBER ;
FUNCTION functfour( first number, second number, third number, fourth number,fifth number) RETURN NUMBER ;
END functionbyfour;
/
CREATE OR REPLACE PACKAGE functionbyfour AS
FUNCTION functone (first number, second number ) RETURN number AS total number;
BEGIN
total:=first + second – 1;
RETURN total;
DBMS_OUTPUT.PUT_LINE(total);
END functone;
FUNCTION functtwo (first number, second number, third number ) RETURN number AS total number;
BEGIN
total:=first + second + third – 1;
RETURN total;
DBMS_OUTPUT.PUT_LINE(total);
END functtwo;
FUNCTION functthree (first number, second number,third number, fourth number ) RETURN number AS total number;
BEGIN
total:=first + second + third + fourth – 1;
RETURN total;
DBMS_OUTPUT.PUT_LINE(total);
END functthree;
FUNCTION functfour (first number, second number, third number, fourth number, fifth number ) RETURN number AS total number;
BEGIN
total:=first + second + third + fourth + fifth – 1;
RETURN total;
DBMS_OUTPUT.PUT_LINE(total);
END functfour;
/
BEGIN
SELECT functionbyfour.functone(1,2) FROM DUAL;
END;
/​

You would need to create a package named FunctionByFour (CREATE OR REPLACE PACKAGE)
SQL> ed
Wrote file afiedt.buf
1 CREATE OR REPLACE PACKAGE functionbyfour AS
2 FUNCTION functone( first number, second number) RETURN NUMBER ;
3 FUNCTION functtwo( first number, second number, third number) RETURN NUMBER ;
4 FUNCTION functthree(first number, second number, third number, fourth number) RETURN NUMBER ;
5 FUNCTION functfour( first number, second number, third number, fourth number,fifth number) RETURN NUMBER ;
6* END functionbyfour;
7 /
Package created.
and then a corresponding package body (CREATE OR REPLACE PACKAGE BODY). You'll also need an END for the package body (right now, your code ends at the end of the fourth function)
SQL> ed
Wrote file afiedt.buf
1 CREATE OR REPLACE PACKAGE BODY functionbyfour AS
2 FUNCTION functone (first number, second number ) RETURN number AS total number;
3 BEGIN
4 total:=first + second - 1;
5 RETURN total;
6 DBMS_OUTPUT.PUT_LINE(total);
7 END functone;
8 FUNCTION functtwo (first number, second number, third number ) RETURN number AS total number;
9 BEGIN
10 total:=first + second + third - 1;
11 RETURN total;
12 DBMS_OUTPUT.PUT_LINE(total);
13 END functtwo;
14 FUNCTION functthree (first number, second number,third number, fourth number ) RETURN number AS total number;
15 BEGIN
16 total:=first + second + third + fourth - 1;
17 RETURN total;
18 DBMS_OUTPUT.PUT_LINE(total);
19 END functthree;
20 FUNCTION functfour (first number, second number, third number, fourth number, fifth number ) RETURN number AS total number;
21 BEGIN
22 total:=first + second + third + fourth + fifth - 1;
23 RETURN total;
24 DBMS_OUTPUT.PUT_LINE(total);
25 END functfour;
26* END functionbyfour;
SQL> /
Package body created.
Once you've done that, you can use the function
SQL> SELECT functionbyfour.functone(1,2) FROM DUAL;
FUNCTIONBYFOUR.FUNCTONE(1,2)
----------------------------
2
If you want to put the SELECT statement in a PL/SQL block, you'd need to declare a local variable and do a SELECT INTO to populate the local variable with the result of the function (you could also just assign the local variable the result of the function call without needing to use a SELECT).
SQL> ed
Wrote file afiedt.buf
1 DECLARE
2 l_result NUMBER;
3 BEGIN
4 -- First approach
5 l_result := functionByFour.functOne(1,2);
6 dbms_output.put_line( l_result );
7 -- Second approach
8 SELECT functionByFour.functOne(1,2)
9 INTO l_result
10 FROM dual;
11 dbms_output.put_line( l_result );
12* END;
13 /
2
2
PL/SQL procedure successfully completed.
Also, be aware that putting a DBMS_OUTPUT.PUT_LINE after your RETURN statement is pointless. That code can never be reached. If you want to print the result to the DBMS_OUTPUT buffer, that would need to come before the RETURN.

The line
CREATE OR REPLACE FUNCTION functionbyfour AS
should be
CREATE OR REPLACE PACKAGE functionbyfour AS
The line
CREATE OR REPLACE PACKAGE functionbyfour AS
should be
CREATE OR REPLACE PACKAGE BODY functionbyfour AS
The word second is a key word and you can't use it as a parameter name
You need an
END functionbyfour;
After your END functfour to end the package body
Your dbms_outputs will never be executed as they are after the return
You could do all this in one function
FUNCTION functall(FIRST NUMBER
,sec NUMBER DEFAULT 0
,third NUMBER DEFAULT 0
,fourth NUMBER DEFAULT 0
,fifth NUMBER DEFAULT 0)
RETURN NUMBER
AS
total NUMBER;
BEGIN
total := first + sec + third + fourth + fifth - 1;
dbms_output.PUT_LINE(total);
RETURN total;
END functall;
What a strange thing to want to do? :-)

You are confused about standalone programs and packages.
CREATE FUNCTION can only be used to create a standalone functiion. What you have there should be:
CREATE OR REPLACE PACKAGE functionbyfour AS
A package consists of two parts, a specification and a body. The spec is the public face of the API, the body is the implementation. What you have as the package (spec) is the package body. So change that second chunk of code to start
CREATE OR REPLACE PACKAGE BODY functionbyfour AS
and at least you'll have your program structured correctly.
The Oracle PL/SQL documentation is online, comnprehensive and free. I urge you to read it. Find out more.

Related

PL/SQL CREATE PROCEDURE - factorial

I don't know what's wrong with my code block to create a procedure to find out the factorial of a number. Thank you.
Question 1: write a stored procedure that gets an integer number n and calculates and displays its factorial.
SET SERVEROUTPUT ON;
CREATE OR REPLACE PROCEDURE factorial_number(
n NUMBER) AS
factorial NUMBER;
num Number;
BEGIN
FOR i IN REVERSE 1..n LOOP
num := i - 1;
factorial := factorial * num;
END LOOP;
DBMS_OUTPUT.PUT_LINE (factorial);
EXCEPTION
WHEN OTHERS
THEN DBMS_OUTPUT.PUT_LINE ('Error!');
END;
/
BEGIN
factorial_number(5);
END;
/
You're failing to initialize the local variable factorial. Uninitialized variables are initially null and multiplying null by any value produces null.
I don't see why you'd want your loop to go in reverse order. It doesn't matter since multiplication is communitive but it it unlikely to make your code easier to read/ debug/ follow.
You don't want to subtract 1 from the value you are multiplying by on each iteration of the loop. When i = 1, for example, you're multiplying factorial by 0 which means that (assuming you initialize factorial), you'd always end up with 0. You want to multiply by i so there is no need for the local variable num.
If I make those fixes
CREATE OR REPLACE PROCEDURE factorial_number(
n NUMBER)
AS
factorial NUMBER := 1; -- Initialize
BEGIN
FOR i IN 1..n LOOP -- Loop normally
dbms_output.put_line( 'Beginning iteration ' || i || ' of loop. ' ||
'Factorial = ' || factorial ||
' multiplying by ' || i );
factorial := factorial * i; -- Multiply by i not i-1
END LOOP;
DBMS_OUTPUT.PUT_LINE (factorial);
EXCEPTION
WHEN OTHERS
THEN DBMS_OUTPUT.PUT_LINE ('Error!');
END;
Then
BEGIN
factorial_number(5);
END;
will print out 120 (5*4*3*2*1).
I'm also adding an additional dbms_output line to print out the current state of the variables on each iteration of the loop. That's a relatively old-school method of debugging. In the modern world, you'd walk through the code with a debugger where you can see the values of your local variables but introductory classes may not teach debugger usage initially.
How about
SQL> CREATE OR REPLACE PROCEDURE factorial_number (n NUMBER)
2 AS
3 factorial NUMBER := 1;
4 BEGIN
5 FOR i IN 1 .. n
6 LOOP
7 factorial := factorial * i;
8 END LOOP;
9
10 DBMS_OUTPUT.PUT_LINE (factorial);
11 END;
12 /
Procedure created.
SQL>
SQL> BEGIN
2 factorial_number (5);
3 END;
4 /
120
PL/SQL procedure successfully completed.
SQL>

Is it possible to have constant function parameters in SQL?

Is it possible to have constant function parameters in SQL to make sure the values are not changed later?
Something like this doesn't work:
function my_func(
first_param constant varchar2
, second_param constant varchar2
) return varchar2
is
... -- Rest
That is not necessary as you cannot redefine an IN parameter. For example:
CREATE FUNCTION does_not_work(
a IN NUMBER,
b IN NUMBER
) RETURN NUMBER
IS
BEGIN
IF a < 2 THEN
a := 2;
END IF;
RETURN GREATEST( a, b );
END;
/
(Note: the IN keyword is optional and the default parameter direction; you would get the same error if you declared the signature without the IN keywords.)
Gives:
ORA-24344: success with compilation error
If you look at the errors:
SELECT * FROM USER_ERRORS;
Outputs:
NAME
TYPE
SEQUENCE
LINE
POSITION
TEXT
ATTRIBUTE
MESSAGE_NUMBER
DOES_NOT_WORK
FUNCTION
1
8
5
PLS-00363: expression 'A' cannot be used as an assignment target
ERROR
363
DOES_NOT_WORK
FUNCTION
2
8
5
PL/SQL: Statement ignored
ERROR
0
This tells you that you can't use an IN parameter as an assignment target.

Need help for dynamic calculation in oracle sql query

I Have a table tab_1 with below values.
ID Calculation value
1 10
2 10
3 1+2
4 5
5 3-2
6 5+1
Need help writing the query for following logic. I have a table where the records contain either calculation strings or values to be used in calculations. I need to parse the calculation like this:
ID 3 is the sum of ID 1 and 2.
ID 5 is the minus of ID 3 and 2.
ID 6 is the sum of ID 5 and 1.
Then I need to select the records for the referenced IDs and perform the calculations. My
expected output:
ID Calculation value
3 1+2 20
5 3-2 10
6 5+1 20
Thanks -- nani
"Need help writing the query for below logic."
This is not a problem which can be solved in pure SQL, because:
executing the calculation string requires dynamic SQL
you need recursion to look up records and evaluate the results
Here is a recursive function which produces the answers you expect. It has three private procs so that the main body of the function is simple to understand. In pseudo-code:
look up record
if record is value then return it and exit
else explode calculation
recurse 1, 3, 4 for each part of exploded calculation until 2
Apologies for the need to scroll:
create or replace function dyn_calc
(p_id in number)
return number
is
result number;
n1 number;
n2 number;
l_rec t23%rowtype;
l_val number;
type split_calc_r is record (
val1 number
, operator varchar2(1)
, val2 number
);
l_calc_rec split_calc_r;
function get_rec
(p_id in number)
return t23%rowtype
is
rv t23%rowtype;
begin
select *
into rv
from t23
where id = p_id;
return rv;
end get_rec;
procedure split_calc
(p_calc in varchar2
, p_n1 out number
, p_n2 out number
, p_operator out varchar2)
is
begin
p_n1 := regexp_substr(p_calc, '[0-9]+', 1, 1);
p_n2 := regexp_substr(p_calc, '[0-9]+', 1, 2);
p_operator := translate(p_calc, '-+*%01923456789','-+*%'); --regexp_substr(p_calc, '[\-\+\*\%]', 1, 1);
end split_calc;
function exec_calc
(p_n1 in number
, p_n2 in number
, p_operator in varchar2)
return number
is
rv number;
begin
execute immediate
'select :n1 ' || p_operator || ' :n2 from dual'
into rv
using p_n1, p_n2;
return rv;
end exec_calc;
begin
l_rec := get_rec(p_id);
if l_rec.value is not null then
result := l_rec.value;
else
split_calc(l_rec.calculation
, l_calc_rec.val1
, l_calc_rec.val2
, l_calc_rec.operator);
n1 := dyn_calc (l_calc_rec.val1);
n2 := dyn_calc (l_calc_rec.val2);
result := exec_calc(n1, n2, l_calc_rec.operator);
end if;
return result;
end;
/
Run like this:
SQL> select dyn_calc(6) from dual;
DYN_CALC(6)
-----------
20
SQL>
or, to get the output exactly as you require:
select id, calculation, dyn_calc(id) as value
from t23
where calculation is not null;
Notes
There is no exception handling. If the data is invalid the function will just blow up
the split_calc() proc uses translate() to extract the operator rather than regex. This is because regexp_substr(p_calc, '[\-\+\*\%]', 1, 1) mysteriously swallows the -. This appears to be an environment-related bug. Consequently extending this function to process 1+4+2 will be awkward.
Here is a LiveSQL demo.
In SQL:
select 'ID ' +ID+ ' is the ' + case when calculation like '%-%' then ' minus '
when calculation like '%+%' then ' sum ' END +' of
ID'+replace(replace(calculation,'+',' and '),'-',' and ')
from tab_1
where calculation is not null
In Oracle:
select 'ID ' ||ID|| ' is the ' || case when calculation like '%-%' then ' minus '
when calculation like '%+%' then ' sum ' END|| ' of
ID'||replace(replace(calculation,'+',' and '),'-',' and ')
from tab_1
where calculation is not null

Printing the position of the bigger letter in a list of characters

I am having some logic thinking trouble with this task.
So the task asks to return the position of the first bigger letter in a list of letters.
For example:
ABVD -> 3
BCDG -> 4
CFDE -> 2
This tasks suggests to use lenght, ascii, and named block, function
So this is what I could do so far:
declare
x varchar2(10) :='ABFD';
BEGIN
FOR i in 1..length(x) LOOP
dbms_output.put_line(ASCII(SUBSTR(x, i, 1)));
END LOOP;
END;
My thought was to turn the letters to numbers : 65, 66, 70, 68. The pattern is x + 1 and since the number 70 is not equal 66 + 1, so the program will return the position of that number, which is 3.
Unfortunately I don't know how turn this idea into code. Can you give me some hints/suggestions? Thanks!
In the problem statement you said "... use named block, function."
Your solution is an anonymous procedure. It is not named anywhere (which is why it is called "anonymous"). And it is not a function - it doesn't return anything.
I will let you study the documentation to understand the difference between function and procedure, and how to name a function or procedure. Below I will follow your lead and show how you can modify your code to make it into a workable anonymous procedure. (In the procedure I "print" the final value of ind; when you change this to a function, you should return that value, instead of printing it.)
In the code you posted, you are printing the letters in the input string, one by one. You are not even attempting to define or assign to an integer (the index of the first occurrence of the "highest" letter in the string). That should be done in the DECLARE block. Then we also need to store the highest letter found "so far" (for future comparisons).
The code might look like this:
declare
x varchar2(10) :='ABFD';
ind number := 1;
max_letter char(1) := substr(x, 1, 1);
BEGIN
FOR i in 2..length(x) LOOP
if substr(x, i, 1) > max_letter
then max_letter := substr(x, i, 1);
ind := i;
end if;
END LOOP;
dbms_output.put_line(ind);
END;
/
Note that letters can be compared to each other directly, there is no reason to convert them to numbers.
Pure SQL using model clause
with t(str) as
(select 'ABVD' from dual
union all select 'BCDG' from dual
union all select 'CFDE' from dual)
select str, instr(str, max_chr) ind
from t
model
partition by (rownum rn)
dimension by (1 dummy)
measures (str, chr(1) max_chr)
rules
iterate (4e3) until (substr(str[1], iteration_number + 2, 1) is null)
(max_chr[1] = greatest(max_chr[1], substr(str[1], iteration_number + 1, 1)));
STR IND
---- ----------
ABVD 3
BCDG 4
CFDE 2

How can we achieve Standard Normal CDF in Oracle SQL or PL/SQL?

How can i achieve below functionality using Oracle SQL or PL/SQL?
This stored procedure gives the same result as NORMDIST function in Calc.
The parameters that need to be passed are x, mean, standard deviation and cumulative.
Cumulative parameter gives a choice to get normal distribution value at x (0) or cumulative probability of value<=x (1).
create or replace FUNCTION NORMDIST(x_value number,mean_value number,stddev_value number, cumulative NUMBER DEFAULT 0)
RETURN NUMBER IS
x number;
t number;
z number;
ans number;
BEGIN
IF (stddev_value = 0) THEN
RETURN 1;
END IF;
x := (x_value-mean_value)/stddev_value;
IF cumulative = 1 THEN
z := abs(x)/SQRT(2);
t := 1/(1+0.5*z);
ans := t*exp(-z*z-1.26551223+t*(1.00002368+t*(0.37409196+t*(0.09678418+t*(-0.18628806+t*(0.27886807+t*(-1.13520398+t*(1.48851587+t*(-0.82215223+t*0.17087277)))))))))/2;
If (x <= 0)
Then RETURN ans;
Else return 1-ans;
End if;
ELSE
RETURN 1/(sqrt(2*3.14159265358979)*stddev_value)*Exp(-(Power(x_value-mean_value,2)/(2*Power(stddev_value,2)) ));
END IF;
END;
/
This is a quick solution, I have not tried to gain maximum precision or performance. Depending on your req, you might need to tweak number format, precision, calculation logic, etc.
create or replace function calc_sn_pdf(x in number) return number
is
pi CONSTANT NUMBER := 3.14159265358979;
begin
return 1/sqrt(2*pi) * exp(-x*x/2);
end;
/
The cdf must be approximated (as it is az integral function which has no simple mathematical formula), one possible approximation is implemented as follows. Many other approximations can be found on Wikipedia.
create or replace function calc_sn_cdf(x in number) return number
is
b0 CONSTANT NUMBER := 0.2316419;
b1 CONSTANT NUMBER := 0.319381530;
b2 CONSTANT NUMBER := -0.356563782;
b3 CONSTANT NUMBER := 1.781477937;
b4 CONSTANT NUMBER := -1.821255978;
b5 CONSTANT number := 1.330274429;
v_t number;
begin
--see 26.2.17 at http://people.math.sfu.ca/~cbm/aands/page_932.htm
--see https://en.wikipedia.org/wiki/Normal_distribution#Numerical_approximations_for_the_normal_CDF
--Zelen & Severo (1964) approximation
if x < 0 then
--this approximation works for x>0, but cdf is symmetric for x=0:
return 1 - calc_sn_cdf(-x);
else
v_t := 1 / (1 + b0*x);
return 1 - calc_sn_pdf(x)*(b1*v_t + b2*v_t*v_t + b3*v_t*v_t*v_t + b4*v_t*v_t*v_t*v_t + b5*v_t*v_t*v_t*v_t*v_t);
end if;
end;
/
Btw, if you need to run these functions a lot of time, it would be useful to turn on native pl/sql compilation.
--I wrote this function in PL/SQL and it works. I compared results with the NORMDIST
--Function in excel and the results match very closely. You will need to pass the
--following --parameters to the function.
-- 1. Value of X
-- 2. Value of Mean
-- 3. Value of Standard Deviation
--This function returns the same result when you pass cumulative=TRUE in excel.
create or replace FUNCTION NORMSDIST(x_value number,mean_value number,stddev_value number)
RETURN NUMBER IS
x number;
t number;
z number;
ans number;
BEGIN
IF (stddev_value = 0) THEN
RETURN 1;
END IF;
x := (x_value-mean_value)/stddev_value;
z := abs(x)/SQRT(2);
t := 1.0/(1.0+0.5*z);
ans := t*exp(-z*z-1.26551223+t*(1.00002368+t*(0.37409196+t*(0.09678418+t*(-0.18628806+t*(0.27886807+t*(-1.13520398+t*(1.48851587+t*(-0.82215223+t*0.17087277)))))))))/2.0;
If (x <= 0)
Then RETURN ans;
Else return 1-ans;
End if;
END NORMSDIST;