Typically variables are declared in the DECLARE section, and are available inside BEGIN block. I find this style stiff and tedious.
Is it possible to declare variables inside the BEGIN block just when they are needed? It is stupid to declare new global variable, if it's needed just to store some temporary value for further calculations, queries and assertions.
Try this - I added comments in the code for you to understand the visibility domain of each variable. Also you can remove the comment from the last DBMS_OUTPUT to see that var2 is no longer available in the outer code.
set serveroutput on;
<<tag>>
DECLARE
var1 INT := 1; -- global variable
BEGIN
DECLARE
var1 INT := 2; -- local variable
var2 INT := 0;
BEGIN
DBMS_OUTPUT.PUT_LINE(var1); -- will display 2 (value of local var1);
DBMS_OUTPUT.PUT_LINE(tag.var1); -- will display 1 (value of global var1);
DBMS_OUTPUT.PUT_LINE(var2); -- will display 0 (value of local var2);
END;
DBMS_OUTPUT.PUT_LINE(var1); -- will display 1 (value of global var1);
-- DBMS_OUTPUT.PUT_LINE(var2); -- will crash since var2 is no longer in memory;
END;
All languages have own features. This is only instrument.
May be you should divide your code into small chunks(procedures, functions)? If your code placed in 20-30 rows, you will not have problems with declaring variables. From smaller chuncks you can build bigger and reusable code.
Regards
Related
I have the following code which works just fine in building a 2D array indexed by VARCHAR2. But I have a question about how I would instantiate the top-level array with an "empty" second level array. (See the comments in the code.)
The only way I've found to instantiate the initial array (inside the Add() procedure) is to declare a dummy variable (which is itself an empty array), and assign it to the top-level array:
DECLARE
TYPE pt_array IS TABLE OF number INDEX BY VARCHAR2(25);
TYPE prj_array IS TABLE OF pt_array INDEX BY VARCHAR2(25);
pt_array_ pt_array;
pj_array_ prj_array;
PROCEDURE Add (
act_ IN VARCHAR2,
pt_ IN VARCHAR2,
val_ IN NUMBER )
IS
dummy_empty_array pt_array; -- initiate a dummy variable
BEGIN
IF not pj_array_.EXISTS(act_) THEN
pj_array_(act_) := dummy_empty_array; -- can I initiate here without having to declare a variable?
END IF;
IF not pj_array_(act_).EXISTS(pt_) THEN
pj_array_(act_)(pt_) := val_;
END IF;
END Add;
BEGIN
Add ('A', '123', 1);
Add ('A', '456', 1);
Add ('B', '456', 1);
END;
Most languages offer the ability to assign an anonymous array when required (i.e. without having to define an intermittent variable). For example, in Javascript you'd just do x = [];. But I haven't found a way to do this in PL/SQL.
I've tried various different syntaxes, one obvious example being the following (which errors at compile time with "no function with name 'PT_ARRAY' exists"):
pj_array_(act_) := pt_array();
Also, the concept of using keyword EXTEND doesn't apply if you're declaring you array/tables with an INDEX BY clause.
So my question is whether it's possible to write this code (i.e. to initiate the empty array) without the dummy variable?
I need to test a stored procedure that has several subprocedures in the is block. I have copied everything to a test window, however, putting these subprocedures in the declare block does not seem to work. When I attempt to call them from the begin block, I get an error saying that they have not been declared. I haven't changed anything other than moving the contents of is to declare. What can I do?
You can declare procedure, however:
1) You declare it without 'create or replace' keywords
2) If you declare any variables in the same block, all procedures and functions must be declared after the last variable declaration.
So, for example, this:
declare
some_text varchar2(10) := 'SOME_TEXT';
procedure print_4 is
x number := 4;
begin
dbms_output.put_line(x);
end print_4;
begin
print_4;
end;
is a valid pl/sql block, while this:
declare
procedure print_4 is
x number := 4;
begin
dbms_output.put_line(x);
end print_4;
some_text varchar2(10) := 'SOME_TEXT';
begin
print_4;
end;
is not.
If you post your code we might find some other errors.
So I know I can initialize variables in PL/SQL using either of the following:
The DEFAULT keyword
The := assignment operator
For example:
counter binary_integer DEFAULT 15;
counter binary_integer := 15;
Are these two methods exactly equivalent to the PL/SQL engine, or are there any slight differences?
Yes they are equivalent.
From Oracle documentation
You can use the keyword DEFAULT instead of the assignment operator to
initialize variables. You can also use DEFAULT to initialize
subprogram parameters, cursor parameters, and fields in a user-defined
record.
Use DEFAULT for variables that have a typical value. Use the
assignment operator for variables (such as counters and accumulators)
that have no typical value.
Example 2-8 Assigning Default Values to Variables with DEFAULT Keyword
SQL> DECLARE
2 blood_type CHAR DEFAULT 'O'; -- Same as blood_type CHAR := 'O';
3
4 hours_worked INTEGER DEFAULT 40; -- Typical value
5 employee_count INTEGER := 0; -- No typical value
6
7 BEGIN
8 NULL;
9 END;
10 /
PL/SQL procedure successfully completed.
SQL>
According to the documentation you can use either:
To specify the initial value, use either the assignment operator (:=) or the keyword DEFAULT, followed by an expression
which means they are equivalent.
it's probably only a misunderstanding of the different types of variables that exist in Oracle SQL and PL/SQL, but how can I use the return value of a PL/SQL function as input for another PL/SQL function inside a SQL script without having to manually set it as value of a DEFINE variable?
Here is the code (being run inside a SQL script inside Oracle SQL Developer):
-- some INSERTS/UPDATES/SELECTS ...
DEFINE in = 'somevalue';
VAR return1 NUMBER;
EXECUTE :return1 := someschema.somepackage.somefunction(in);
PRINT return1;
-- reasonable return value gets printed out
VAR return2 NUMBER;
EXECUTE :return2 := someschema.somefunction(return1);
-- ^
-- this does not work ----------------------+
-- (neither does ":return1")
DEFINE in2 = <manually enter value of "return1">
EXECUTE :return2 := someschema.somefunction(in2);
-- ^
-- this works ------------------------------+
-- some INSERTS/UPDATES/SELECTS ...
Thank's in advance.
DEFINE and EXECUTE would work as expected in SQL*Plus. To execute your entire code in SQL Developer or as a script from a client, I would suggest you:
Use an anonymous block.
DECLARE a variable for the OUT parameter of procedure, and another variable to store the return value of the function.
In the BEGIN-END block, call the procedure.
And use the same varible to store the return value of the function.
For example,
DECLARE
o_var ;
f_var ;
BEGIN
-- call the procedure
package.procedure(o_var);
--call the function
SELECT package.function(o_var) INTO f_var FROM DUAL;
-- do something
END;
/
Below solution works for me.
DECLARE
parameter_var ;
assignment_var ;
BEGIN
--call the function and assign it to assignment_var
assignment_var:=function_xyz(parameter_var);
-- use assignment_var for further computation
END;
/
Hope it helps others.
I have a routine written in T-SQL for SQL Server. We are migrating to Oracle so I am trying to port it to PL/SQL. Here is the T-SQL routine (simplified); note the use of the table-valued variable which, in Oracle, will become a "nested table" type PL/SQL variable. The main thrust of my question is on the best ways of working with such "collection" objects within PL/SQL. Several operations in the ported code (second code sample, below) are quite awkward, where they seemed a lot easier in the SQL Server original:
DECLARE #MyValueCollection TABLE( value VARCHAR(4000) );
DECLARE #valueForThisRow VARCHAR(4000);
DECLARE #dataItem1Val INT, #dataItem2Val INT, #dataItem3Val INT, #dataItem4Val INT;
DECLARE theCursor CURSOR FAST_FORWARD FOR
SELECT DataItem1, DataItem2, DataItem3, DataItem4 FROM DataTable;
OPEN theCursor;
FETCH NEXT FROM theCursor INTO #dataItem1Val, #dataItem2Val, #dataItem3Val, #dataItem4Val;
WHILE ##FETCH_STATUS = 0
BEGIN
-- About 50 lines of logic that evaluates #dataItem1Val, #dataItem2Val, #dataItem3Val, #dataItem4Val and constructs #valueForThisRow
SET #valueForThisRow = 'whatever';
-- !!! This is the row that seems to have no natural Oracle equivalent
INSERT INTO #MyValueCollection VALUES(#valueForThisRow);
FETCH NEXT FROM theCursor INTO #dataItem1Val, #dataItem2Val, #dataItem3Val, #dataItem4Val;
END;
CLOSE theCursor;
DEALLOCATE theCursor;
-- !!! output all the results; this also seems harder than it needs to be in Oracle
SELECT * FROM #MyValueCollection;
I have been able to port pretty much everything, but in two places (see comments in the code), the logic is a lot more complex than the old SQL Server way, and I wonder if there might be, in Oracle, some more graceful way that is eluding me:
set serveroutput on; -- needed for DBMS_OUTPUT; see below
DECLARE
TYPE StringList IS TABLE OF VARCHAR2(4000);
myValueCollection StringList;
dummyTempCollection StringList; -- needed for my kludge; see below
valueForThisRow VARCHAR2(4000);
BEGIN
-- build all the sql statements
FOR c IN (
SELECT DataItem1, DataItem2, DataItem3, DataItem4 FROM DataTable;
)
LOOP
-- About 50 lines of logic that evaluates c.DataItem1, c.DataItem2, c.DataItem3, c.DataItem4 and constructs valueForThisRow
valueForThisRow := 'whatever';
-- This seems way harder than it should be; I would rather not need an extra dummy collection
SELECT valueForThisRow BULK COLLECT INTO dummyTempCollection FROM dual; -- overwrites content of dummy temp
myValueCollection := myValueCollection MULTISET UNION dummyTempCollection; -- merges into main collection
END LOOP;
-- output all the results... again, there's no shorter/easier/more-compact/single-line equivalent?
IF myValueCollection.COUNT > 0
THEN
FOR indx IN myValueCollection.FIRST .. myValueCollection.LAST
LOOP
DBMS_OUTPUT.PUT_LINE(myValueCollection(indx));
END LOOP;
END IF;
END;
/
Thanks in advance for any help!
Personally, I'd take the "50 lines of logic", move it into a function that you call in your SQL statement, and then do a simple BULK COLLECT to load the data into your local collection.
Assuming that you really want to load data element-by-element into the collection, you can simplify the code that loads the collection
DECLARE
TYPE StringList IS TABLE OF VARCHAR2(4000);
myValueCollection StringList := StringList();
valueForThisRow VARCHAR2(4000);
BEGIN
-- build all the sql statements
FOR c IN (
SELECT DataItem1, DataItem2, DataItem3, DataItem4 FROM DataTable;
)
LOOP
-- About 50 lines of logic that evaluates c.DataItem1, c.DataItem2, c.DataItem3, c.DataItem4 and constructs valueForThisRow
valueForThisRow := 'whatever';
myValueCollection.extend();
myValueCollection( myValueCollection.count ) := valueForThisRow;
END LOOP;
-- output all the results... again, there's no shorter/easier/more-compact/single-line equivalent?
IF myValueCollection.COUNT > 0
THEN
FOR indx IN myValueCollection.FIRST .. myValueCollection.LAST
LOOP
DBMS_OUTPUT.PUT_LINE(myValueCollection(indx));
END LOOP;
END IF;
END;
/
If you declare the collection as an associative array, you could avoid calling extend to increase the size of the collection. If you know the number of elements that you are going to load into the collection, you could pass that to a single extend call outside the loop. Potentially, you can also eliminate the valueForThisRow local variable and just operate on elements in the collection.
As for the code that processes the collection, what is it that you are really trying to do? It would be highly unusual for production code to write to dbms_output and expect that anyone will see the output during normal processing. That will influence the way that you would write that code. Assuming that your intention is really to just call dbms_output, knowing that will generally send the data into the ether
FOR indx IN 1 .. myValueCollection.count
LOOP
dbms_output.put_line( myValueCollection(indx) );
END LOOP;
This works when you have a dense collection (all indexes between 1 and the count of the collection exist and have values). If you might have a sparse collection, you would want to use FIRST, NEXT, and LAST in a loop but that's a bit more code.