Incrementing a number in a loop in plpgsql - sql

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

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 assign global variable value with local variable value in PLSQL

I have attached code snippet. I want to change the value of Global variable from a function variable. Is it possible?
create or replace package pack_testing
as
function f1(n in number) return number ;
end;
create or replace package body pack_testing
as
n number:=1;
function f1(n in number) RETURN number
is
z number:=n;
begin
for i in 1..10 loop
z:=z+1;
if n<3 then dbms_output.put_line(i+'o');
else dbms_output.put_line(i);
end if;
return(i);
begin
n:=z; --this line giving error . I am assigning global variable value with local variable
end;
end loop;
end;
end;
If a subunit redeclares a global variable, you can reference the global variable by using the name of the unit that holds the global variable. In your case, just add the package name in front of the variable:
pack_testing.n:=z;
But it is best to avoid this situation entirely by using more descriptive names, not re-using names, or having a variable prefix policy. The rules around variable scope and precedence get trickier when SQL is involved, so I typically prefix every PL/SQL variable with G_ for global, C_ for constant, P_ for paramter, and V_ for (local) variable.
Do not use the same name n for two objects: package global variable and input paramter of function f1
As the matter of variable scope, your local input paramter of function f1 will have more prioritize than your n global variable.
And your n paramter is IN, not OUT or IN OUT, it cannot be assign value z, so that oracle raise error. If you have a global variable n, then change your IN parameter to n1 or sth else, for e.g FUNCTION f1( n1 IN NUMBER)
CREATE OR REPLACE PACKAGE pack_testing
AS
FUNCTION f1(n1 IN NUMBER) RETURN NUMBER;
END;
CREATE OR REPLACE PACKAGE BODY pack_testing
AS
n NUMBER := 1;
FUNCTION f1(n1 IN NUMBER) RETURN NUMBER
AS
z NUMBER := n1;
BEGIN
FOR i IN 1..10
LOOP
z := z + 1;
IF n < 3
THEN
dbms_output.put_line(i + 'o');
ELSE
dbms_output.put_line(i);
END IF;
RETURN i;
BEGIN
n := z;
END;
END LOOP;
END;
END;
And your test code seem never reach the line n := z; because it always end at previous line RETURN i;. So for the LOOP

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

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).

Find Min Or Max in custom aggregate function postgresql

I'm new to Postgresql (I'm programming in PL/pgSQL, there's no much difference with sql).
I wrote my custome aggregate function, which has to find the min value, or max in an array of numeric.
This is the function aggregate code
CREATE OR REPLACE FUNCTION searchMaxValue (numeric[]) RETURNS numeric AS $$
DECLARE
i numeric;
maxVal numeric;
BEGIN
maxVal = $1[1];
IF ARRAY_LENGHT($1,1) > 0 THEN --Checking whether the array is empty or not
<<confrontoMassimo>>
FOREACH i IN ARRAY $1 LOOP --Looping through the entire array, passed as parameter
IF maxVal <= $1[i] THEN
maxVal := $1[i];
END IF;
END LOOP confrontoMassimo;
ELSE
RAISE NOTICE 'Invalid parameter % passed to the aggregate function',$1;
--Raising exception if the parameter passed as argument points to null.
RAISE EXCEPTION 'Cannot find Max value. Parameter % is null', $1
USING HINT = 'You cannot pass a null array! Check the passed parameter';
END IF;
RETURN maxVal;
END;
$$ LANGUAGE plpgsql;
CREATE AGGREGATE searchMaxValueArray (numeric)
(
sfunc = array_append,
stype = numeric[],
finalfunc = searchMaxValue,
initCond = '{}'
);
The problem is, that it doesn't work as expected. What is the problem?
As mentioned above, there's a small typo in your function; ARRAY_LENGHT should be ARRAY_LENGTH.
Aside from that, the only issue I can see is here:
FOREACH i IN ARRAY $1 LOOP
IF maxVal <= $1[i] THEN
...
In a FOREACH loop, the target variable i isn't the array index, it's the array element itself. In other words, the loop should be:
FOREACH i IN ARRAY $1 LOOP
IF maxVal <= i THEN
maxVal := i;
END IF;
END LOOP
With those changes, it seems to work as expected: https://rextester.com/FTWB14034

ERROR: Reference the counter as the target of an assignment - PL/SQL

I'm relatively new to SQL.
I'm trying to print a simple pattern through this code
declare
n number(2):=5;
temp number(2):=n;
begin
for a in 1..5
a:=a+1;loop
for b in 1..temp loop
b:=b+1;
dbms_output.put_line(' ');
temp:=temp-1;
end loop;
for c in 1..2*a-1 loop
c:=c+1;
dbms_output.put_line('*');
end loop;
end loop;
end;
/
I keep getting this error:
PLS-00103: Encountered the symbol "A" when expecting one of the following:
* & - + / at loop mod remainder rem <an exponent (**)> ||
multiset
I understand Oracle doesn't allow to reference the counter as the target of an assignment which is why I keep getting error at line 6 but I'm unable to make it work even by declaring another global variable and assigning increment statement in it but it doesn't work either.
Please help.
Thanks!
Modifying the previous answers to actually give you Pascal's triangle, which you mentioned you were attempting in a comment:
set serveroutput on format wrapped
declare
n number(2):=5;
begin
for a in 1..n loop
for b in 1..n-a loop
dbms_output.put(' ');
end loop;
for c in 1..2*a-1 loop
dbms_output.put('*');
end loop;
dbms_output.new_line;
end loop;
end;
/
*
***
*****
*******
*********
PL/SQL procedure successfully completed.
Both your dbms_output.put_line calls needed to be just dbms_output.put, as that was printing each * on a line on its own. But you do need a line break after each time around the a loop, so I've added a dbms_output.newline at the end of that. You were also decrementing temp inside the b loop, which meant it was zero instead of (n-1) for the second time around the a loop; but you don't really need a separate temp variable at all as that is always the same as (n-a)+1 and the +1 just puts an extra space on every line. (I also made the a loop 1..n as I assume you want to change the value of n later in one place only). With n := 8:
*
***
*****
*******
*********
***********
*************
***************
Crucially though you also have to set serveroutput on format wrapped, otherwise the leading spaces you're generating in the b loop are discarded.
You can also do this in plain SQL, though you need to supply the 5 twice, or use a bind or substitution variable:
select lpad(' ', 5 - level, ' ') || rpad('*', (level * 2) - 1, '*') as pascal
from dual
connect by level <= 5
PASCAL
------------------------------
*
***
*****
*******
*********
Your b and c loops are just doing a manual lpad really.
When I took your code in my editor I first noticed, you tried to increase a before starting the loop, and Oracle gives first error at that point. And also it does not allow you to increase counter variable in for loop, (I don't know why) I checked on the internet and found that you can not set increment step for Oracle for loops also you can not set a value for counter variable in for loop.
The code below works fine for me :
declare
n number(2):=5;
temp number(2):=n;
begin
for a in 1..5
loop --a:=a+1;
for b in 1..temp loop
--b:=b+1;
dbms_output.put_line(' ');
temp:=temp-1;
end loop;
for c in 1..2*a-1 loop
--c:=c+1;
dbms_output.put_line('*');
end loop;
end loop;
end;
/
As StephaneM says, loop variables are incremented by the loop itself: you don't need to do a := a + 1, and most of all you CAN'T assign them ! Here is a corrected version:
declare
n number(2):=5;
temp number(2):=n;
begin
for a in 1..5
loop
for b in 1..temp loop
dbms_output.put_line(' ');
temp:=temp-1;
end loop;
for c in 1..2*a-1 loop
dbms_output.put_line('*');
end loop;
end loop;
end;
/