I am trying to create nested BEGIN..END blocks within the body of BigQuery stored procedure. The code is as follows:
CREATE OR REPLACE PROCEDURE dataset.proc(IN p_var1 INT64, OUT out_param STRING)
BEGIN
DECLARE p_abc INT64;
DECLARE p_bcd INT64;
BEGIN
DECLARE p_abc INT64 DEFAULT 0; //Error Here : re-declaration cannot occur.
WHILE (p_abc <= p_bcd) DO
BEGIN
SET p_abc = p_abc + 1;
END;
END WHILE;
END;
END;
The above stored procedure doesn't compile because of the redeclaration. Unlike in traditional databases, like Netezza or Teradata, I can easily perform such type of variable scoping.
Is there some way to do this on BigQuery or not possible at all?
The documentation says:
It is an error to declare a variable with the same name as a variable declared earlier in the current block or in a containing block.
So I would say it is impossible to create a variable with the same name in the case you described.
Related
In PL/SQL I have learned that there are two ways to define procedures like below.
Method 1
DECLARE
a number;
PROCEDURE print(mynum number) IS
BEGIN
dbms_output.put_line(mynum);
END;
Method 2
CREATE print(mynum number) IS
BEGIN
dbms_output.print_line(mynum);
END;
But only in method 2, the procedure will store in DBMS as we can call it again and again. What is the difference between two different methods of creating a PLSQL procedure and how we can create and save the store procedure in DBMS which consists of declared variables?
Although you posted invalid syntax, OK - I think I understand the question.
If you want the procedure to be stored, you have to use what you called "Method 2", e.g.
create or replace procedure print (mynum number) is
a number; --> locally declared variable
begin
a := mynum * mynum;
dbms_output.put_line(mynum);
dbms_output.put_line(a);
end;
/
The first piece of code ("Method 1") represents an anonymous PL/SQL block whose contents is "lost" at the end of your session (i.e. when you disconnect).
In Firebird, DECLARE statements can be listed at the beginning of an EXECUTE BLOCK statement:
EXECUTE BLOCK [(<inparams>)]
[RETURNS (<outparams>)]
AS
[<declarations>]
BEGIN
[<PSQL statements>]
END
Within a block, no further DECLARE statements are possible, i.e. all variables are globally scoped, for the entire block. Is there any workaround to declare local variables for the scope of a nested block only, in Firebird? Nesting EXECUTE BLOCK calls isn't possible:
EXECUTE BLOCK AS
DECLARE i INTEGER;
BEGIN
EXECUTE BLOCK AS -- This doesn't compile
DECLARE j INTEGER;
BEGIN
END
END
You can't nest execute block statements. The design of this feature did not consider this option. In Firebird 3, you can define 'sub-procedures' or 'sub-functions', but those don't have access to variables or input columns from the outer-level (so they only provide an isolated scope, and you'd need to explicitly pass in values using parameters, and out using returning values), and you can't nest more than one level.
For example:
execute block
returns (firstval integer, secondval integer)
as
declare function x(param1 integer) returns integer
as
declare var1 integer;
begin
var1 = param1 * 2;
return param1 + var1;
end
begin
firstval = 1;
while (firstval < 10) do
begin
secondval = x(firstval);
suspend;
firstval = firstval + 1;
end
end
https://dbfiddle.uk/?rdbms=firebird_3.0&fiddle=677b33e416bd3f6a6a34e060d9afce9e
I am not aware of other options to declare variables with a limited scope in Firebird PSQL.
What would be the PL/SQL equivalent of this SQL query:
SELECT * FROM table(OWNER.PACKAGE.get_exam('123456789'));
This is the Function that I am trying to call:
FUNCTION get_exam(id IN VARCHAR2)
RETURN ab_assign_v1
IS
CURSOR c_exams(cid VARCHAR2) IS
SELECT t_api_exam_v1(
sei.person_id, --unique id
l.description --loc description
)
FROM my_view sei
JOIN loc l
ON sei.loc_code = l.loc_code
v_collection ab_assign_v1;
BEGIN
OPEN c_exams(id);
FETCH c_exams BULK COLLECT INTO v_collection;
CLOSE c_exams;
RETURN v_collection;
EXCEPTION
WHEN OTHERS THEN
error_a1.raise_error(SQLCODE, SQLERRM);
END get_exam;
Hope this helps.
DECLARE
lv <COLLECTION_NAME>;
BEGIN
lv:= OWNER.PACKAGE.get_exam('123456789');
dbms_output.put_line(lv.COUNT);
END;
/
Assuming that you want to return the result of a function :
select owner.package.get_exam('123456789') from table
Your function returns a nested table type. You simply need to declare a variable of that type, and assign to it as you would if it were a scalar:
declare
l_coll your_collection_type;
begin
l_coll := OWNER.PACKAGE.get_exam('123456789');
end;
/
In this example your_collection_type is a placeholder for whatever object your function actually returns.
" I am getting this error: PLS-00201: identifier 'ab_assign_v1' must be declared "
ab_assign_v1 is the type used by your function. From the code posted in your revised question it seems that type is in the same schema which owns the package with the function. However your original pseudo-code prefixes the call with the schema name. So, putting two and two together, you need to revise the variable declaration to include the schema too. (You may need to grant EXECUTE on it too, if you haven't done this already).
declare
l_coll OWNER.ab_assign_v1;
begin
l_coll := OWNER.PACKAGE.get_exam('123456789');
end;
/
my procedure looks like this:
create or replace procedure odcitaj_surovinu_zo_skladu
(
v_id_suroviny IN surovina.id_suroviny%TYPE,
odcitaj IN OUT number
)
as
begin
...//some code here
odcitaj:=odcitaj-22;
...//some code here
end;
Procedure compiled w/o errors. I'm trying to execute it as:
execute odcitaj_surovinu_zo_skladu(1,200);
But it gives error, that '200' can't be used as target of assigment.
So how to execute it? Does ODCITAJ even need to be IN OUT? Cause i know that, if it was just IN , then it would act as constant and i won't be able to assign it anything
As Dmitry Bychenko said, you have to use a variable as the target of an OUT or IN OUT parameter, you can't provide a constant. Your parameter does need to be IN OUT since you're modifying it in the procedure. You can either use an anonymous block:
declare
l_odcitaj number;
begin
l_odcitaj := 200;
odcitaj_surovinu_zo_skladu(1, l_odcitaj);
-- do something with the updated value of l_odcitaj
end;
/
If you want to use the SQL*Plus/SQL Developer execute shorthand wrapper for an anonymous block you can declare a bind variable instead:
variable l_odcitaj number;
exec :l_odcitaj := 200;
exec odcitaj_surovinu_zo_skladu(1, :l_odcitaj);
Notice that the variable name has a colon in front when it is set and when the procedure is called, because it is a bind variable.
If you want you can then use that updated bind variable in other calls, or print it's post-procedure value:
print l_odcitaj
If the updated value - from odcitaj:=odcitaj-22; - doesn't need to be returned and is only used inside the procedure, you could declare the argument as IN and have a local variable which you set from the argument and then manipulate and use in the procedure.
create or replace procedure odcitaj_surovinu_zo_skladu
(
v_id_suroviny IN surovina.id_suroviny%TYPE,
v_odcitaj IN number
)
as
l_odcitaj number;
begin
l_odcitaj := v_odcitaj;
...//some code here
l_odcitaj:=l_odcitaj-22;
...//some code here
end;
/
You could then call the procedure with constant values. It just depends whether the caller needs to know the modified value.
I want to return rows from a select statement within a declare/begin/end block. I can do this in T-SQL but I would like to know how to do it in PL/SQL.
The code looks a bit like the following:
declare
blah number := 42;
begin
select *
from x
where x.value = blah;
end;
An anonymous PL/SQL block, like the one you've shown, can't "return" anything. It can interact with the caller by means of bind variables, however.
So the method I would use in this case would be to declare a cursor reference, open it in the PL/SQL block for the desired query, and let the calling application fetch rows from it. In SQLPlus this would look like:
variable rc refcursor
declare
blah number := 42;
begin
open :rc for
select *
from x
where x.value = blah;
end;
/
print x
If you recast your PL/SQL as a stored function then it could return values. In this case what you might want to do is create a collection type, fetch all the rows into a variable of that type, and return it:
CREATE TYPE number_table AS TABLE OF NUMBER;
CREATE FUNCTION get_blah_from_x (blah INTEGER)
RETURN number_table
IS
values number_table;
BEGIN
SELECT id
BULK COLLECT INTO values
FROM x
WHERE x.value = blah;
RETURN values;
END;
/
Well, this depends heavily on your data access library.
You can return any SQL-compatible type as a parameter. This includes complex SQL types and collection types.
But most libraries are simply not capable of handling Oracle's object types.
Either way, my examples will use these object types:
create type SomeType as object(Field1 VarChar(50));
create type SomeTypeList as table of SomeType;
When your access library can handle object types, you could simply return a list of PL/SQL objects:
begin
:list := SomeTypeList(SomeType('a'),SomeType('b'),SomeType('c'));
end;
If not, you could hack around it by forcing this list into a select and return its result as a cursor:
declare
list SomeTypeList;
begin
list := SomeTypeList(SomeType('a'),SomeType('b'),SomeType('c'));
open :yourCursor for
SELECT A
FROM table(list);
end;