FOR loop Netezza issue - loops

I'm working with stored procedures in netezza.
I want to loop over a range of values.
The upper bound on the loop is passed as a variable into the sproc by the user.
i.e. EXECUTE SPROC(12);
so problem is that Netezza (aginity workbench) won't accept this input variable as the upper bound on the loop.
i.e.
DECLARE
x alias as $1.
begin
for i in 1..x loop
...do stufff...
end loop;
end;
I know that this can be solved using loop and exit style loop but It's eating me up as to why i can't do the above given that the documentation suggests that it's possible to do so.
Anyone know why this doesn't work or how to make it work?
Thanks.
Clancy.

Please find below working example -
CREATE OR REPLACE PROCEDURE generateTime(integer)
LANGUAGE NZPLSQL RETURNS varchar(255) AS
BEGIN_PROC
DECLARE
p_abc integer;
p_bcd integer;
p_var1 ALIAS FOR $1;
BEGIN
p_bcd := ISNULL(p_var1, 10);
raise notice 'p_bcd=%',p_bcd;
FOR p_abc in 0..(p_bcd)
LOOP
raise notice 'Hello World %', p_abc;
END LOOP;
END;
END_PROC;
Hope this will help.

Related

Assign Element To Index of Array with Attribute in PostgreSSQL

Can I assign a value from cursor to index of array with attribute?
I have an oracle code like this :
cursor cursor1 is select name, value from table;
FOR loop1 IN cursor1 LOOP
array1.EXTEND;
array1(loop1).cur_name := cursor1.name;
array1(loop1).cur_value := cursor1.value;
END LOOP;
i tried to convert to postgresql like this, but it's getting error
CREATE FUNCTION function_name () RETURNS something AS $$
DECLARE
cursor1 cursor for select name, value from table;
array1 text[];
BEGIN
-- Do something
...
FOR loop1 IN cursor1 LOOP
array1[loop].cur_name := cursor1.name; --error here
array1[loop1].cur_value := cursor1.value; -- error here
END LOOP;
-- Do something
...
RETURN;
END;
is there any method to create an array with attibute name?
The Oracle function is returning a collection (An Associative Array if I remember correctly, but its been awhile). Postgres does NOT have collections, the closest data type is an array. However since your collection contains multiple columns, you need to create a UDT (user defined type}, then your function returns an array of that type. (Note I assumed the data types in the table. Correct as deeded.)
create type name_val as (name text, value integer);
create or replace function function_name ()
returns name_val[]
language plpgsql
as $$
declare
cursor1 cursor for
select name, value
from test
limit 10;
rec record;
array1 name_val[];
l_name_val name_val;
begin
-- do something
for rec in cursor1
loop
l_name_val.name = rec.name;
l_name_val.value = rec.value;
array1 = array1 || l_name_val;
end loop;
-- do something
return array1;
end;
$$;
There are a couple other option which avoid the cursor and looping altogether. Assuming you actually need any Array returned you can reduce the above function to a single sql statement:
create or replace function function_name3()
returns name_val[]
language sql
as $$
select array_agg((name, value)::name_val)
from test
limit 10;
$$;
Demo Here
UPDATE:
I noticed that subsequent to my answer you update the question from for loop1 in 1 .. 10 ... to for rec in cursor1 ... thus removing the resulting row limitation. You accomplish the same by just removing the Limit 10 clause.

How to set a value into a variable when cursor encounters not found in postgresql?

I have a cursor like this defined in stored procedure written in plpgsql
cur1 cursor(usid numeric) is select gid, mid from x where userid = usid;
I am currently able to exit when cursor encounters a 'not found' case as following
open cur1(1);
loop
fetch cur1 into gid, mid;
exit when not found;
raise notice '% %',gid, mid;
end loop;
close cur1;
But apart from exiting at line exit when not found, I also want to store a value 1 to variable f, which I've declared in declare section of my function.
I tried following two approaches, but both of them are throwing errors near "when" :
Approach 1:
set f=1 when not found
Approach 2:
select 1 into f when not found
I can not think about any other way to do this. Is it possible to do assignment like this?

can we use more than one function inside one PL/SQL Block

DECLARE
avg_sal number(8,2);
c number(8);
total number(8,2);
at number(8);
a number(8);
rt number(8);
r number(8);
y number(8);
yr number(8);
c number(8);
ch number(8);
FUNCTION cmd_int(amt number,rate number,intrest number)
RETURN number
IS
cint number(8,2);
BEGIN
END;
FUNCTION smp_int(amt number,rate number,intrest number)
RETURN number
IS
sint number(8,2);
BEGIN
sint:=(amt*rate*intrest)/100;
return total/c;
END;
BEGIN
dbms_output.put_line('Enter amount :'||:a);
at:=:a;
dbms_output.put_line('Enter rate :'||:r);
rt:=r;
dbms_output.put_line('Enter year :'||:y);
yr:=y;
dbms_output.put_line('1 Compound Intrest');
dbms_output.put_line('2 Simple Intrest');
dbms_output.put_line('Enter your choice :'||:c);
ch:=c;
CASE
WHEN ch:=1 THEN
ci:=cmd_int(at,rt,yr);
dbms_output.put_line('Compound Intrest :'||ci);
WHEN ch:=2 THEN
si:=smp_int(at,rt,yr);
dbms_output.put_line('Simple Intrest :'||si);
END;
The above is the code which I want to execute there are two functions cmd_int and smp_int so can I able to execute more than one function inside one PL/SQL Block? Thank you!
Basically i want to execute 1st part when my choice is 1 and second part when my choice is 2.
Yes, you can define and call as many functions and procedures as you like (and subblocks - up to a limit anyway). The functionality of PL/SQL would be rather curtailed if you couldn't.
But the rest of the code has to be valid. You're missing an end case:
...
CASE
WHEN ch:=1 THEN
ci:=cmd_int(at,rt,yr);
dbms_output.put_line('Compound Intrest :'||ci);
WHEN ch:=2 THEN
si:=smp_int(at,rt,yr);
dbms_output.put_line('Simple Intrest :'||si);
END CASE;
END;
And AT is a reserved word in PL/SQL so you cannot have a variable with that name, you will need to change it.
You also seem to be confusing PL/SQL local variables (e.g. r) with bind variables (:r), and you're trying to have interaction between the block and the user, which PL/SQL is not designed for. Once the PL/SQL block is valid your client will either complain that not all variables are bound, or will prompt for them all before executing the block, and you will only see the 'prompt' strings from dbms_output after the block has executed.
Your application or client needs to gather all the values outside the PL/SQL block; Michael Schaefers has shown a common method for SQL Developer or SQL*Plus. You can still combine that with a PL/SQL block if that is part of your assignment.
To technically answer the question of the headline: yes, you can use more than one function inside a PL/SQL block, as the following example code shows:
set serveroutput on;
DECLARE
FUNCTION cmd_int(amt number,rate number,intrest number)
RETURN number
IS
cint number(8,2);
BEGIN
return 4;
END;
FUNCTION smp_int(amt number,rate number,intrest number)
RETURN number
IS
sint number(8,2);
BEGIN
return 5;
END;
BEGIN
dbms_output.put_line('Result 1: ' || cmd_int(1,2,3));
dbms_output.put_line('Result 2: ' || smp_int(1,2,3));
END;
/
Executing this block yields to
Result 1: 4
Result 2: 5
Now to your problem:
I suggest you create two separate functions cmd_int and smp_int via CREATE OR REPLACE FUNCTION ... that do, what you want. Since these two are used in the same logical context you can also create a package CREATE PACKAGE INTEREST and define both functions within this package.
Then, to ask for user input and to actally use the functions I suggest you stick to an sqlplus script using the ACCEPT command or to handle everything in your client application (should you have one).
See Oracle Documenation on CREATE FUNCTION, CREATE PACKAGE and sqlplus ACCEPT
The basic Idea of an sqlplus script would be
SET SERVEROUTPUT ON;
ACCEPT a NUMBER PROMPT 'Enter amount: ';
ACCEPT r NUMBER PROMPT 'Enter rate: ';
ACCEPT y NUMBER PROMPT 'Enter year: ';
ACCEPT c NUMBER PROMPT '1 Compount Interest, 2 Simple Interest: ';
SELECT CASE WHEN &&c = 1 THEN cmd_int(a,r,y) ELSE smp_int(a,r,y) END AS Interest FROM DUAL;
More infos about using sqlplus can be found here
Yes. You can define more functions and procedures within a pl/sql block.

How to use dbms_output to display an array of values assigned to a variable?

I have something like this, but got an error says ORA-06533: Subscript beyond count. I want to see all the values from the "select distinct" statement in the output tab. Anyone can help? thanks!
DECLARE
TYPE v_chks_array IS VARRAY (10) OF VARCHAR2 (50);
arrSRCs v_chks_array;
BEGIN
arrSRCs := v_chks_array ();
arrSRCs.EXTEND (10);
SELECT /*+parallel (a,4)*/
DISTINCT a.src_table
BULK COLLECT INTO arrSRCs
FROM hcr_dm.hcr_dm_fact a;
DBMS_OUTPUT.put_line (arrSRCs (10));
END;
Collections are not needed here:
begin
for results in
(
select /*+ parallel (a,4) */ distinct a.src_table
from hcr_dm.hcr_dm_fact a;
) loop
dbms_output.put_line(results.src_table);
end loop;
end;
/
Instead of VARRAY, you may try a TABLE type collection. You'll have more flexibility with number of records to hold while doing BULK COLLECT.
Create or replace type v_chks_array IS TABLE OF VARCHAR2 (500);
More information can be found at http://docs.oracle.com/cd/B19306_01/appdev.102/b14261/collections.htm

Error for Associative Array : Missing IN OUT Parameter

I'm learning about Collections and trying out Associative Arrays in Oracle 11g. I'm using SQL Developer to write and test my code below and I am getting the error which I can't troubleshoot :
Error Report
Missing IN OUT Parameter at index ::1
Code I have written is as follows:
---SIMPLE collections EXAMPLE
DECLARE
TYPE prospect_towns IS TABLE OF VARCHAR2 (25)
INDEX BY PLS_INTEGER;
a_big_towns prospect_towns; -- associative array
i PLS_INTEGER := 1; -- index for the array
v_counter NUMBER;
v_town VARCHAR2(25);
BEGIN
a_big_towns(1):='Birmingham';
a_big_towns(2):='London':
a_big_towns(3):='Manchester';
-- v_counter := 1;
FOR i IN 1..a_big_towns.COUNT
LOOP <<big towns>>
--v_town := a_big_towns(i);
DBMS_OUTPUT.PUT_LINE('Inside Loop, town is '||a_big_towns(i));
i= a_big_towns.next:
END LOOP<<big towns>>
END;
/
Any ideas what's wrong ?
The second of these lines:
a_big_towns(1):='Birmingham';
a_big_towns(2):='London':
a_big_towns(3):='Manchester';
... has a colon at the end, instead of a semicolon. That's causing the following a_big_towns to be interpreted as a bind variable name by the parser. So it should be:
a_big_towns(2):='London';
Once you get past that, this line isn't needed, and would need := instead of = if it was, and also has a colon instead of a semicolon at the end:
i= a_big_towns.next:
... so remove that completely.
I'm not sure the labels are really adding anything here, but if you do have a label it doesn't need to be repeated at the end, and the name can't have a space in it, so make it:
<<big_towns>>
FOR i IN 1..a_big_towns.COUNT LOOP
And this needs a semicolon at the dned:
END LOOP;
This SQL Fiddle compiles.

Resources