How to return rows from a declare/begin/end block in Oracle? - sql

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;

Related

PLS-00488: object "string" must be a type or subtype - but it is a type

I am trying to do build a sentence using a UDT, as follows:
CREATE OR REPLACE TYPE fv_group as object(
fv NUMBER,
group_number INTEGER
);
/
CREATE OR REPLACE TYPE fv_group_array IS VARRAY(100) OF fv_group;
CREATE OR REPLACE TYPE fv_grouping AS OBJECT (
fv_and_group fv_group_array,
MEMBER PROCEDURE insert_groupby(FV NUMBER),
MEMBER FUNCTION which_group(FV NUMBER) RETURN INTEGER
);
/
CREATE OR REPLACE TYPE BODY fv_grouping as
MEMBER PROCEDURE insert_groupby(FV NUMBER) IS
g fv_group;
BEGIN
IF fv < 15 THEN
g := fv_group(fv,1);
ELSE
g := fv_group(fv,2);
END IF;
fv_and_group.extend(1);
fv_and_group(fv_and_group.last) := g;
END;
MEMBER FUNCTION which_group(FV NUMBER) RETURN INTEGER IS
feature NUMBER;
BEGIN
FOR i IN 1..fv_and_group.count LOOP
feature := fv_and_group(i).fv;
IF fv_and_group(i).fv = fv THEN
RETURN fv_and_group(i).group_number;
END IF;
END LOOP;
RETURN 0;
END;
END;
/
The representation I need is:
DECLARE
obj fv_grouping;
BEGIN
SELECT :obj.which_group(gb.fv), count(*)
FROM (
SELECT :obj.insert_groupby(c.fv, 6, 3)
from cophir
) gb
GROUP BY :obj.which_group(gb.fv);
END;
/
The procedure insert_groupby inserts each value of the cophir table into a varray, which holds its values and the corresponding group.
After the varray is loaded with all values and their corresponding group, I want to group them. Is it possible, to do it in a query?
Thanks in advance!
grouping is a type. What is wrong?
GROUPING is an Oracle keyword. If you re-name your type to say FV_GROUPING you will solve the PLS-00488. Which will leave you free to address all the other syntax errors in your code:
The object type is not instantiated correctly in the query.
SELECT statements embedded in PL/SQL must select into a variable which matches the projection of the query.
Not sure what your code is trying to achieve, but this version of your code runs:
DECLARE
obj fv_grouping;
BEGIN
SELECT fv_grouping(cast(collect(fv_group(fv, 3)) as fv_group_array))
into obj
from cophir;
dbms_output.put_line(obj.which_group(2)) ;
END;
/
This uses COLLECT to gather the fv_group objects into an fv_group_array instance which can be used to instantiate fv_grouping and populate the obj variable.

PL/SQL equivalent of SELECT statement

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;
/

PL/SQL query with parameter

I am familiar with MSSQL and using a parameter within the query, but I am not sure how I would do this within PL/SQL.
DECLARE
LSITEID NUMBER := 100001;
BEGIN
SELECT * from invoicehead ih
JOIN sitemaster sm on sm.SITEIID = ih.SITEIID
JOIN invoiceline il on il.invoiceIID = ih.invoiceIID
WHERE
ih.StartDate BETWEEN '2015-12-01' AND '2016-03-07'
AND SITEIID IN ( LSITEID)
END;
Right now I am testing this within Pl/SQL. But essentially I would be passing in the query with the parameter from MSSQL Linked Server OPENQuery.
How I can run the above query in PL/SQL with the parameter?
There is plenty of other resource for finding an answer, e.g. here (Tutorialspoint) or specifically here (plsql-tutorial). But perhaps I have missed your point.
To not remain on merely citing links, your query could look like this:
DECLARE
LSITEID integer;
BEGIN
LSITEID := 100001;
-- dostuff
END;
Two things to note: First, in a declare part (as I have learnt it) you should avoid assigning values. Second, if you intend to pass in different parameters you could/should use a procedure.
In PL/SQL you just use the name of the argument. In the following example, the argument is P_VALUE, note select statement says where dummy = p_value.
DECLARE
FUNCTION dummycount (p_value IN DUAL.dummy%TYPE)
RETURN INTEGER
AS
l_ret INTEGER;
BEGIN
SELECT COUNT (*) c
INTO l_ret
FROM DUAL
WHERE dummy = p_value;
RETURN l_ret;
END dummycount;
BEGIN
DBMS_OUTPUT.put_line ('A: ' || dummycount (p_value => 'A'));
DBMS_OUTPUT.put_line ('X: ' || dummycount (p_value => 'X'));
END;
This results in the following output:
A: 0
X: 1

How to edit a cursor attribute for a return statement on plsql?

I want to return a cursor from a function, I have read that I can use:
return sys_refcursor
And then
open curs for select* from mytable;
return curs;
I tried curs.att := 'something' but I get an error
Also read I can do my own type:
TYPE type IS REF CURSOR RETURN mytable%ROWTYPE;
Then
CURSOR cur IS
SELECT* FROM mytable;
var cur%ROWTYPE;
BEGIN
OPEN cur;
FETCH cur INTO var;
var.att = 'something';
RETURN var;
This time I didn't get an error in the assign but in the return statement.
If I changed the var type to my type I couldn't fetch the value.
I wan't to edit the cursor, but not the table, how can I do this?
A cursor is a read-only structure. The only way to change the data that you would fetch from a cursor is to change the SQL statement that is used to open the cursor or to change the data in the underlying table(s).
While it is possible to return a cursor from one PL/SQL block to another, it is rarely the appropriate architecture. A SYS_REFCURSOR is generally appropriate when you want to return a result to a client application that knows how to use a cursor.
Do you really want to return a cursor, though? Or do you want to return a record type? The second code snippet you posted appears to be trying to return a record-- that's certainly possible but you would need to declare that the function returns a record rather than a cursor. That is, the RETURN statement in the declaration would need to be RETURN mytable%ROWTYPE rather than RETURN type. For example, if you want to return a record based on the EMP table
create or replace function get_emp( p_empno in emp.empno%type )
return emp%rowtype
is
l_rec emp%rowtype;
begin
select *
into l_rec
from emp
where empno = p_empno;
l_rec.sal := l_rec.sal + 100;
return l_rec;
end;

How to use an Oracle Associative Array in a SQL query

ODP.Net exposes the ability to pass Associative Arrays as params into an Oracle stored procedure from C#. Its a nice feature unless you are trying to use the data contained within that associative array in a sql query.
The reason for this is that it requires a context switch - SQL statements require SQL types and an associative array passed into PL/SQL like this is actually defined as a PL/SQL type. I believe any types defined within a PL/SQL package/procedure/function are PL/SQL types while a type created outside these objects is a SQL type (if you can provide more clarity on that, please do but its not the goal of this question).
So, the question is, what are the methods you would use to convert the PL/SQL associative array param into something that within the procedure can be used in a sql statement like this:
OPEN refCursor FOR
SELECT T.*
FROM SOME_TABLE T,
( SELECT COLUMN_VALUE V
FROM TABLE( associativeArray )
) T2
WHERE T.NAME = T2.V;
For the purposes of this example, the "associativeArray" is a simple table of varchar2(200) indexed by PLS_INTEGER. In C#, the associativeArry param is populated with a string[].
Feel free to discuss other ways of doing this besides using an associative array but know ahead of time those solutions will not be accepted. Still, I'm interested in seeing other options.
I would create a database type like this:
create type v2t as table of varchar2(30);
/
And then in the procedure:
FOR i IN 1..associativeArray.COUNT LOOP
databaseArray.extend(1);
databaseArray(i) := associativeArray(i);
END LOOP;
OPEN refCursor FOR
SELECT T.*
FROM SOME_TABLE T,
( SELECT COLUMN_VALUE V
FROM TABLE( databaseArray )
) T2
WHERE T.NAME = T2.V;
(where databaseArray is declared to be of type v2t.)
You cannot use associative arrays in the SQL scope - they are only usable in the PL/SQL scope.
One method is to map the associative array to a collection (which can be used in the SQL scope if the collection type has been defined in the SQL scope and not the PL/SQL scope).
SQL:
CREATE TYPE VARCHAR2_200_Array_Type AS TABLE OF VARCHAR2(200);
/
PL/SQL
DECLARE
TYPE associativeArrayType IS TABLE OF VARCHAR2(200) INDEX BY PLS_INTEGER;
i PLS_INTEGER;
associativeArray associativeArrayType;
array VARCHAR2_200_Array_Type;
cur SYS_REFCURSOR;
BEGIN
-- Sample data in the (sparse) associative array
associativeArray(-2) := 'Test 1';
associativeArray(0) := 'Test 2';
associativeArray(7) := 'Test 3';
-- Initialise the collection
array := VARCHAR2_200_Array_Type();
-- Loop through the associative array
i := associativeArray.FIRST;
WHILE i IS NOT NULL LOOP
array.EXTEND(1);
array(array.COUNT) := associativeArray(i);
i := associativeArray.NEXT(i);
END LOOP;
-- Use the collection in a query
OPEN cur FOR
SELECT *
FROM your_table
WHERE your_column MEMBER OF array;
END;
/