How can i execute function? - sql

I am trying to make a function that returns a row based upon the number i'm giving. I have created the function but can't seem to make it work.
This is my function:
create or replace function vitest(t_na test.na%type)
return sys_refcursor is t_test sys_refcursor;
begin
open t_test for
select * from test where na = t_na;
return t_test;
end;
I have tried using:
select vitest(1) from dual;
But it gives me error: ORA-00932.
I also tried using:
begin
vitest(1);
end;
But it says vitest is not a procedure...
How can i make it work?

Assuming your test table has col1,col2,col3 columns, you could call your function as follows:
DECLARE
l_cursor SYS_REFCURSOR;
l_col1 test.col1%TYPE;
l_col2 test.col2%TYPE;
l_col3 test.col3%TYPE;
BEGIN
l_cursor := vitest(1);
LOOP
FETCH l_cursor
INTO l_col1, l_col2, l_col3;
EXIT WHEN l_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(l_col1 || ' | ' || l_col2 || ' | ' || l_col3);
END LOOP;
CLOSE l_cursor;
END;
*Also, your select now would change
from
select * from test where na = t_na;
to
select col1,col2,col3 from test where na = t_na;

Related

How can I return a SELECT query with an oracle function?

I need to create a function that allows me to return the same result as a SELECT query and that contains pl/sql code.
I tried something really simple :
create or replace FUNCTION test
RETURN SYS_REFCURSOR
IS
l_rc SYS_REFCURSOR;
BEGIN
OPEN l_rc
FOR SELECT *
FROM my_table;
RETURN l_rc;
END;
But when I call my function with SELECT test from dual;, I get all result from my_table in a single cell instead of having each columns separated.
Is there a way of doing what I want ?
Ideally, I want a view but there seems to be no way of adding logical conditions with them.
the function has to be pipelined. For example :
TYPE MyType IS RECORD(ID NUMBER);
TYPE MyTableType IS TABLE OF MyType;
Function MyFunction(Arguments) return MyTableType pipelined is
Cursor Cur is select * from whetever;
R Cur%rowtype;
Begin
Open cur;
loop
fetch Cur into R;
exit when Cur%notfound;
pipe row(R);
End loop;
Close cur;
End MyFunction;
Then you can call it via :
select * from table(MyFunction(Arguments));
The simplest way is to leave the function as it is and only call it properly:
create table my_table (id, memo) as
select 1, 'some memo' from dual
/
create or replace function MyTableById (id int) return sys_refcursor is
rc sys_refcursor;
begin
open rc for
select * from my_table where id=MyTableById.id;
return rc;
end;
/
var rc refcursor
exec :rc := MyTableById (1);
print rc
ID MEMO
---------- ---------
1 some memo

How to make a select from a cursor that returns from a plsql function ORACLE

i have a function that return a cursor my package is like this
FUNCTION SEDIRUNTIME (sede varchar2) return SYS_REFCURSOR
this cursor return x number of row with only one value, for example :
ROW1 - 34
ROW2 - 55
ROW3 - 56 ecc. ecc.
now i have i select like this
.. AND field in (select DBK_ENIN_REPORT.*SEDIRUNTIME*(sede) from dual)
this will simulate a clause IN which we can know the values at run time.
for example, based on the location parameter, the cursor can give me 22 and 34 rather than just 56 or 78 98 09.
written so i do not work from error number 00932 incoherent data types.
solutions?
You cannot use a CURSOR like that.
But you can change the function to return a collection:
CREATE TYPE Numberlist IS TABLE OF NUMBER;
/
CREATE FUNCTION DBK_ENIN_REPORT.SEDIRUNTIME (
sede varchar2
) return NumberList
IS
out_numbers NumberList;
BEGIN
SELECT id
BULK COLLECT INTO out_numbers
FROM your_table;
RETURN out_numbers;
END;
/
Then you can do:
.. AND field MEMBER OF DBK_ENIN_REPORT.SEDIRUNTIME(sede)
or
.. AND field IN ( SELECT COLUMN_VALUE FROM TABLE( DBK_ENIN_REPORT.SEDIRUNTIME(sede) ) )
Well I would say you CAN do it but you need to twist little bit. You can see how i have done it as below. I take employee table as and example.
Create function:
CREATE OR REPLACE FUNCTION SEDIRUNTIME (sede VARCHAR2)
RETURN SYS_REFCURSOR
AS
cur SYS_REFCURSOR;
BEGIN
OPEN cur FOR SELECT employee_id FROM employee;
RETURN cur;
END;
/
Anonymous block which you can implement in your package as Procedure;
DECLARE
x SYS_REFCURSOR;
y NUMBER;
v VARCHAR2 (100);
v_sql VARCHAR2 (200);
TYPE var_emp IS TABLE OF employee%ROWTYPE
INDEX BY PLS_INTEGER;
v_emp var_emp;
BEGIN
x := SEDIRUNTIME ('sede');
LOOP
FETCH x INTO y;
v := v || ',' || y;
EXIT WHEN x%NOTFOUND;
END LOOP;
--Created the IN clause list
v := LTRIM (v, ',');
v_sql := 'Select * from employee where employee_id in (' || v || ')';
EXECUTE IMMEDIATE v_sql BULK COLLECT INTO v_emp;
FOR i IN 1 .. v_emp.COUNT
LOOP
DBMS_OUTPUT.put_line ( v_emp (i).employee_id || '--' || v_emp (i).first_name);
END LOOP;
END;
OUTPUT:
SQL> /
1--XXX
2--YYY
PL/SQL procedure successfully completed.
SQL>
PS: Ofcourse the solution provided by MTO is going to be much faster that this.

A procedure to Reverse a String in PL/SQL

I just started learning PL/SQL and I'm not sure how to create a procedure. The logic seems about right but I think there's some syntactical mistake in the first line. Here's my code:-
CREATE OR REPLACE PROCEDURE ReverseOf(input IN varchar2(50)) IS
DECLARE
reverse varchar2(50);
BEGIN
FOR i in reverse 1..length(input) LOOP
reverse := reverse||''||substr(input, i, 1);
END LOOP;
dbms_output.put_line(reverse);
END;
/
Two things - you shouldn't specify the datatype size in procedure's/function's parameter list and you do not need the DECLARE keyword. Try this:
CREATE OR REPLACE PROCEDURE ReverseOf(input IN varchar2) IS
rev varchar2(50):='';
BEGIN
FOR i in reverse 1..length(input) LOOP
rev := rev||substr(input, i, 1);
END LOOP;
dbms_output.put_line(rev);
END;
Try it without PL/SQL!
WITH
params AS
(SELECT 'supercalifragilisticexpialidocious' phrase FROM dual),
WordReverse (inpt, outpt) AS
(SELECT phrase inpt, CAST(NULL AS varchar2(4000)) outpt FROM params
UNION ALL
SELECT substr(inpt,2,LENGTH(inpt)-1), substr(inpt,1,1) || outpt
FROM wordReverse
WHERE LENGTH(inpt) > 0
)
SELECT phrase,outpt AS reversed FROM wordReverse, params
WHERE LENGTH(outpt) = LENGTH(phrase) ;
PHRASE REVERSED
---------------------------------- -----------------------------------
supercalifragilisticexpialidocious suoicodilaipxecitsiligarfilacrepus
Citation: http://rdbms-insight.com/wp/?p=94
Another solution reverse string minimizing loop count
DECLARE
v_str VARCHAR2(100) DEFAULT 'MYSTRING';
v_len NUMBER;
v_left VARCHAR2(100);
v_right VARCHAR2(100);
BEGIN
v_len := LENGTH(v_str)/2;
FOR rec IN 1..v_len
LOOP
v_left := substr(v_str,rec,1) || v_left;
IF rec * 2 <= LENGTH(v_str) THEN
v_right := v_right || substr(v_str,-rec,1);
END IF;
END LOOP;
v_str := v_right || v_left;
dbms_output.put_line(v_str);
END;
set serveroutput on
declare
str1 varchar2(30);
len number(3);
str2 varchar2(30);
i number(3);
begin
str1:='&str1';
len:=length(str1);
for i in reverse 1..len
loop
str2:=str2 || substr(str1,i,1);
end loop;
dbms_output.put_line('Reverse string is: '||str2);
end;
/
create or replace procedure ap_reverse_number(input_no VARCHAR2) as
output_no VARCHAR2(100);
begin
for i in 1 .. length(input_no) loop
output_no := output_no || '' ||
substr(input_no, (length(input_no) - i + 1), 1);
end loop;
dbms_output.put_line('Input no. is :' || input_no);
dbms_output.put_line('Output no. is:' || output_no);
end;
This should do the job correctly.
Try this one line statement to reverse the string in sql
with a as (select 'brahma' k from dual)
select listagg(substr(k,length(k)-level+1,1),'') within group (order by 1) b from a connect by level<length(k)+1
Try this one line query to reverse a string or a number.
select reverse('HelloWorld') from dual;
declare
name varchar2(20):='&name';
rname varchar2(20);
begin
for i in reverse 1..length(name)
loop
rname:=rname||substr(name,i,1);
end loop;
display(rname);
end;

Executing Dynamic Native SQL with Oracle Table type gives invalid identifier error

We have created Oracle Table type. And we have created an array of it. This is done as we dont know how many values can come which may be too much for a IN clause in sql query.
--Code Snippet -----
create or replace
TYPE "INPUTCODE"
as object
(pc varchar2(100) )
create or replace
TYPE "INPUTCODEARR"
IS TABLE OF inputcode;
create or replace
PROCEDURE "TEST_PROC" (testCodes IN inputcodeArr, timeHorizon IN NUMBER, p_recordset OUT SYS_REFCURSOR)
AS
var_sqlStmt VARCHAR2(4096);
BEGIN
var_sqlStmt := 'select t.a,t.b, t.c';
var_sqlStmt := var_sqlStmt || 'from test t';
if testCodes is not null then
var_sqlStmt := var_sqlStmt || ', table(testCodes) tc';
var_sqlStmt := var_sqlStmt || 'where tc.pc = t.name';
end if;
dbms_output.put_line('Final SQL Statement::' || var_sqlStmt);
open p_recordset for var_sqlStmt;
END TEST_PROC;
All the above ones are compiles and when you run the TEST_PROC procedure with few testCode values it will fail with error Invalid identifier for testCodes.
In the procedure, final sql statement which is printing in my console is correct and when you run this as a static sql statement inside procedure it runs without any error. But inside the dynamic sql it fails.
I tried executing using DYNAMIC_SQL package, but it results in same error.
Also, i tried giving it as a bind variable for 'table(testCodes)'. That also failed.
Please suggest.
You are using dynamic SQL, so you must tell Oracle which word is an identifier and which word is a variable.
Consider the following statement running directly in SQLPlus:
select t.a,t.b, t.c from test t, table(testCodes) tc
It will fail because no object is named testCodes in your DB. You have to tell the SQL engine that testCodes is in fact a variable. You have to do this because you have chosen to use dynamic SQL whereas variable binding is automatic in static SQL.
In most cases, you can bind "object" variables in the same way as standard variables. In PL/SQL there are several ways to do this, for instance with cursors you would use USING:
SQL> DECLARE
2 l_cur SYS_REFCURSOR;
3 l_tab inputcodeArr := inputcodeArr(INPUTCODE('A'), INPUTCODE('B'));
4 l_obj varchar2(100);
5 BEGIN
6 OPEN l_cur FOR 'SELECT pc FROM TABLE(:my_variable)' -- notice the ":"
7 USING l_tab; -- binding by position
8 LOOP
9 FETCH l_cur
10 INTO l_obj;
11 EXIT WHEN l_cur%NOTFOUND;
12 dbms_output.put_line(l_obj);
13 END LOOP;
14 CLOSE l_cur;
15 END;
16 /
A
B
PL/SQL procedure successfully completed
In your case however I wouldn't bother with dynamic SQL since you can open a cursor conditionally:
CREATE OR REPLACE PROCEDURE "TEST_PROC"(testCodes IN inputcodeArr,
timeHorizon IN NUMBER,
p_recordset OUT SYS_REFCURSOR) AS
BEGIN
IF testCodes IS NOT NULL THEN
OPEN p_recordset FOR
SELECT t.a, t.b, t.c FROM test t, TABLE(testCodes) tc
WHERE tc.pc = t.NAME;
ELSE
OPEN p_recordset FOR
SELECT t.a, t.b, t.c FROM test t;
END IF;
END TEST_PROC;
My advice would be to stick with static SQL as long as possible, as it's a lot easier to make mistakes with dynamic SQL.
Update following comment:
If your number of input is not constant and you have to use dynamic SQL because there are many combinations of filters, you can use the following strategy:
CREATE OR REPLACE PROCEDURE "TEST_PROC"(testCodes IN inputcodeArr,
timeHorizon IN NUMBER,
p_recordset OUT SYS_REFCURSOR) AS
l_sql LONG := 'SELECT t.a, t.b, t.c FROM test t WHERE';
BEGIN
-- filter #1
IF testCodes IS NOT NULL THEN
l_sql := l_sql || ' t.name IN (SELECT pc FROM TABLE(:filter1))';
ELSE
l_sql := l_sql || ' :filter1 IS NULL';
END IF;
-- filter #2
IF timeHorizon IS NOT NULL THEN
l_sql := l_sql || ' AND t.horizon = :filter2';
ELSE
l_sql := l_sql || ' AND :filter2 IS NULL';
END IF;
-- open cursor
OPEN p_recordset FOR l_sql USING testCodes, timeHorizon;
END TEST_PROC;
/
I'm making sure that the final SQL will always have the same number of variables in the same order, however each condition where the filter is NULL will be a tautology (NULL IS NULL).

How can I print a multi line result using PL/SQL?

PROCEDURE A(
...
BEGIN
stmt := 'select * from '||src;
execute immediate stmt;
dbms_output.put_line(??);
END A;
If you know the structure of the table named in "src" when writing the code then you can do this:
PROCEDURE A IS
...
l_cur sys_refcursor;
BEGIN
stmt := 'select * from '||src;
open l_cur for stmt;
loop
fetch l_cur into ??; -- record or list of variables that matches columns of "src"
exit when l_cur%notfound;
dbms_output.put_line(??);
end loop;
close l_cur;
END A;
If you will not know the structure until run time then you will need to use the DBMS_SQL package, which is very powerful but not simple.
I'm not sure wether this is working with your "execute immediate stmt" approach, but with static Sql, following is working for me:
for my_result in
(
select * from my_table tbl
where ...
order by tbl.my_id_col
) loop
dbms_output.put_line(my_result.field1 || ', ' || my_result.field2 || ...);
end loop;