ORACLE: Missing IN or OUT parameter at index:: 1 - sql

Can someone tell me what's wrong in my code. I need to create function that displays the number of digits given a number but I keep getting missing in and out parameter. Im am using Oracle SQL. Thank you
SET SERVEROUTPUT ON;
CREATE OR REPLACE FUNCTION Digit (n1 IN OUT INTEGER) RETURN INTEGER IS
Counter INTEGER := 0;
BEGIN
WHILE (n1 != 0 ) LOOP
n1 := n1 /10;
Counter := Counter + 1;
END LOOP;
RETURN Counter;
END;
Test block:
DECLARE
n1 INTEGER := 0;
BEGIN:
n1 := &n1;
DBMS_OUTPUT.PUT_LINE('The number of digit = ' ||Digit(Counter));
END;

The error is probably because of the stray : character after begin in your test block.
I would write it like this:
create or replace function digits
( p_num integer )
return integer
as
pragma udf;
i simple_integer := p_num;
l_digits simple_integer := 0;
begin
while i <> 0 loop
i := i / 10;
l_digits := l_digits + 1;
end loop;
return l_digits;
end digits;
I made the parameter in only, instead of in out. This means you can use it in SQL queries, and also in PL/SQL code without needing to pass in a variable whose value will get changed to 0 by the function.
pragma pdf tells the compiler to optimise the function for use in SQL.
I used simple_integer as in theory it's slightly more efficient for arithmetic operations, although I doubt any improvement is measurable in the real world (and I'm rather trusting the optimising compiler to cast my literal 10 as a simple_integer, as otherwise the overhead of type conversion will defeat any arithmetic efficiency).

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>

PLSQL Procedural Logic

How to write a PL/SQL function that:
Uses only numeric datatypes and functions-no VARCHAR2, CHAR, CLOB, XML, etc.
" In other words, character/string functions such as REVERSE, SUBSTR, etc are not to be used in your solution.
Accepts a PLS_INTEGER parameter
If the provided value is less than or equal to zero throws application error -20001 and provides a good error message
Returns a PLS_INTEGER value that has the digits from the input parameter in reverse order. If the input value ends in one or more zeroes those zeroes will not appear in the returned numeric value, since they would be leading zeroes.
Here is what I have written so far:
create or replace function test_reverse
(p_input in pls_integer)
return pls_integer
is
p_num pls_integer := 0;
p_in_num pls_integer := 0;
p_out_num pls_integer := 0;
begin
p_num := p_input;
loop
exit when p_num p_in_num := mod(p_num,10);
p_out_num := (p_out_num * 10) + p_in_num;
p_num := trunc(p_num / 10);
end loop;
return p_out_num;
end;
The problem is to turn 1234 into 4321. The string-y solution (admittedly using an undocumented built-in) is simplicity itself: to_number(reverse(to_char(1234))). Sticking to numeric datatypes is more cumbersome. My solution is very procedural: undoubtedly more elegant solutions exist.
Anyway, to turn 1234 into 4321 we need to generate 1 + 20 + 300 + 4000. My solution isolates each value in turn and multiplies by the appropriate power of ten. To isolate the values we use trunc() with a negative value. This rounds down the number to the left of the decimal point. Thus, trunc(1234,-3) produces 1000. To convert this into the required value we multiply by ten to the power of minus three. Thus 1000 * 10(-3) = 1.
The function iterates through the numbers. Having converted 1000 to 1 we calculate the remainder, 1234 - 1000 = 234. So now we need to isolate the 200 and convert it to 20; that is trunc(234, -2) and power(200, -1). So we can the offset fed into trunc() decrements by 1 and the exponent fed into power() increments by 2.
Here is a working function (very loosely based on the one you posted):
create or replace function test_reverse
(p_input in pls_integer)
return pls_integer
is
p_out_num pls_integer := 0;
offset pls_integer;
tgt pls_integer;
rmdr pls_integer;
exp pls_integer;
begin
rmdr := p_input;
offset := length(p_input)-1;
exp := -offset;
loop
tgt := trunc(rmdr, -offset);
p_out_num := p_out_num + (tgt * power(10, exp));
exp := exp + 2;
rmdr := rmdr - tgt;
exit when offset = 0;
offset := offset-1;
end loop;
return p_out_num;
end test_reverse;
/
Here is a LiveSQL demo (free Oracle Technet account required, alas). That doesn't feature the parameter validation but it is straightforward enough:
create or replace function test_reverse
(p_input in pls_integer)
return pls_integer
is
p_out_num pls_integer := 0;
offset pls_integer;
tgt pls_integer;
rmdr pls_integer;
exp pls_integer;
begin
if p_input <= 0 then
raise_application_error(-20001
'invalid input: ' || || ' must be greater than zero.'
);
end if;
rmdr := p_input;
....
I like the use of the modulo function as the op suggested:
CREATE OR REPLACE FUNCTION test_reverse (
p_input IN PLS_INTEGER
) RETURN PLS_INTEGER
IS
remain PLS_INTEGER := p_input;
retval PLS_INTEGER := 0;
BEGIN
IF p_input < 1 THEN
raise_application_error(-20001,'error: input must me greater than 0');
END IF;
LOOP
retval := retval * 10 + MOD(remain,10);
remain := trunc(remain / 10);
EXIT WHEN remain = 0;
END LOOP;
RETURN retval;
END;
/
This also avoids the use of the length() function which to my taste feels a bit "stringish", even if it operates on numbers.

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;

Generate unique random strings in plpgsql

I am trying to write a function to create unique random tokens of variable length. However, I am stumped by the plpgsql syntax. My intention is to create a function which
Takes a table and column as input
Generates a random string of a given length, with a given set of characters
Checks if the string is already in the colum
If so (and this is expected to be rare), simply generate a new random string.
Otherwise, return the random string
My current attempt looks like this:
CREATE FUNCTION random_token(_table TEXT, _column TEXT, _length INTEGER) RETURNS text AS $$
DECLARE
alphanum CONSTANT text := 'abcdefghijkmnopqrstuvwxyz23456789';
range_head CONSTANT integer := 25;
range_tail CONSTANT integer := 33;
random_string text;
BEGIN
REPEAT
SELECT substring(alphanum from trunc(random() * range_head + 1)::integer for 1) ||
array_to_string(array_agg(substring(alphanum from trunc(random() * range_tail + 1)::integer for 1)), '')
INTO random_string FROM generate_series(1, _length - 1);
UNTIL random_string NOT IN FORMAT('SELECT %I FROM %I WHERE %I = random_string;', _column, _table, _column)
END REPEAT;
RETURN random_string;
END
$$ LANGUAGE plpgsql;
However, this doesn't work, and gives me a not very helpful error:
DatabaseError: error 'ERROR: syntax error at or near "REPEAT"
I have tried a number of variations, but without knowing what the error in the syntax is I am stumped. Any idea how to fix this function?
There is no repeat statement in plpgsql. Use simple loop.
CREATE OR REPLACE FUNCTION random_token(_table TEXT, _column TEXT, _length INTEGER) RETURNS text AS $$
DECLARE
alphanum CONSTANT text := 'abcdefghijkmnopqrstuvwxyz23456789';
range_head CONSTANT integer := 25;
range_tail CONSTANT integer := 33;
random_string text;
ct int;
BEGIN
LOOP
SELECT substring(alphanum from trunc(random() * range_head + 1)::integer for 1) ||
array_to_string(array_agg(substring(alphanum from trunc(random() * range_tail + 1)::integer for 1)), '')
INTO random_string FROM generate_series(1, _length - 1);
EXECUTE FORMAT('SELECT count(*) FROM %I WHERE %I = %L', _table, _column, random_string) INTO ct;
EXIT WHEN ct = 0;
END LOOP;
RETURN random_string;
END
$$ LANGUAGE plpgsql;
Note, random_string should be a parameter to format().
Update. According to the accurate hint from Abelisto, this should be faster for a large table:
DECLARE
dup boolean;
...
EXECUTE FORMAT('SELECT EXISTS(SELECT 1 FROM %I WHERE %I = %L)', _table, _column, random_string) INTO dup;
EXIT WHEN NOT dup;
...
This is almost certainly not what you want. When you say, "checks if the string is already in the column" you're not referring to something that looks unique, you're referring to something that actually is UNIQUE.
Instead, I would point you over this answer I gave about UUIDs.

Incrementing a number in a loop in plpgsql

I couldn't find this immediately from the examples. I want to increment a variable in a loop, in a function.
For instance:
DECLARE
iterator float4;
BEGIN
iterator = 1;
while iterator < 999
.....
iterator ++;
END;
How would this be done?
I was looking at this document about flow control:
http://www.postgresql.org/docs/8.4/static/plpgsql-control-structures.html
And none of them seem to be relevant for me, unless these are absolutely the only ways to simulate incrementing a variable.
To increment a variable in plpgsql:
iterator := iterator + 1;
There is no ++ operator.
About the assignment operator in plpgsql:
The forgotten assignment operator "=" and the commonplace ":="
Correct syntax for loops in PL/pgSQL in the manual.
Your code fragment would work like this:
DECLARE
iterator float4 := 1; -- we can init at declaration time
BEGIN
WHILE iterator < 999
LOOP
iterator := iterator + 1;
-- do stuff
END LOOP;
END;
Simpler, faster alternative with a FOR loop:
FOR i in 1 .. 999 -- i is integer automatically, not float4
LOOP
-- do stuff
END LOOP;
The manual:
The variable name is automatically defined as type integer and exists only inside the loop (any existing definition of the variable name is ignored within the loop).
For a sscce
DO $$
DECLARE
counter INTEGER := 0 ;
BEGIN
WHILE counter <= 5 LOOP
counter := counter + 1 ;
RAISE NOTICE 'Counter: %', counter;
END LOOP ;
END; $$
if you want to avoid declare variable (more concise)
DO $$
BEGIN
FOR counter IN 1..5 LOOP
RAISE NOTICE 'Counter: %', counter;
END LOOP;
END; $$
credits