REF CURSOR get a column from a procedure - sql

I have a procedure I am running from SQL developer. It pumps out about 50 columns. Currently I am working on a bug which is updating one of these columns. It is possible to just show column X from the result?
I am running it as
VARIABLE cursorout REFCURSOR;
EXEC MY_PROC('-1', '-1', '-1', 225835, :cursorout);
PRINT cursorout;
Ideally I want to print out the 20th column so would like to do something like
PRINT cursorout[20];
Thanks

It is possible to just show column X from the result?
Not without additional coding, no.
As #OldProgrammer said in the comment to your question you can use dbms_sql package to describe columns and pick one you like.
But, if, as you said, you know column names, the probably easiest way to display contents of that column would be using XML functions, xmlsequence() and extract() in particular.
Unfortunately we cannot pass SQL*PLUS bind variable as a parameter to the xmlsequence() function, so you might consider to wrap your procedure in a function, which returns refcursor:
Test table:
create table t1(col, col2) as
select level
, level
from dual
connect by level <= 5;
SQL> select * from t1;
COL COL2
---------- ----------
1 1
2 2
3 3
4 4
5 5
Here is a simple procedure, which opens a refcursor for us:
create or replace procedure p1(
p_cursor out sys_refcursor
) is
begin
open p_cursor for
select * from t1;
end;
/
Procedure created
Here is the function-wrapper for the p1 procedure, which simply executes the procedure and returns refcursor:
create or replace function p1_wrapper
return sys_refcursor is
l_res sys_refcursor;
begin
p1(l_res);
return l_res;
end;
/
Function created
The query. Extract path is ROW/COL2/text(), where COL2 is the name of a column we want to print.
select t.extract('ROW/COL2/text()').getstringval() as res
from table(xmlsequence(p1_wrapper)) t ;
Result:
RES
--------
1
2
3
4
5
5 rows selected.

In my opinion,you can define a cursor in procedure MY_PROC,and put which column is updated in the cursor(for example 20) and then return then cursor.Or you just create a table to record every execute result of your procedure.

Related

Define, initialise and use variables in SQL developer and SSIS ODBC connection

I am working on a script to be later used in my SSIS ETL, the source DB is oracle and I am using SQL Developer 20.0.2.75 .
I spent so much time declaring 100 variables but it doesn't see to work in SQL developer.
Define & Initialise:
Declare
V1 number;
V2 number;
.
.
.
V100 number;
Begin
Select UDF(params1,param2) into V1 from dual;
Select UDF(params3,param4) into V2 from dual;
...
End;
I was hoping I'd be able to use these variables in my script like :
select columns from table where Col1=:V1 and Col2=:V2
When used "Run Statement" prompts for values, "Run Script" doesn't see to like into Variable statements.
I even tried :
select columns from table where Col1=&&V1 and Col2=&&V2
Now my query doesn't work !
After below responses, I changed my script to :
Variable V1 Number;
Variable V2 Number;
exec select MyFunction(p1,p2) into :V1 from Dual;
/
Select columns from table where col1=:V1 and col2=:V2
It still prompts for value
This is how I defined my function
Create Function MyFunction(m IN Varchar, s IN Number)
Return Number
IS c Number;
select code into c from table where col1=m and col2=s;
Return(c);
End;
Is there anything wrong with the function?
You define variables as per you would in SQL Plus or SQLcl and then run it as a script
Text below
variable x1 number
begin
select 123 into :x1 from dual;
end;
/
print x1
Similar example in SQL Plus (and will work in SQL Dev as well)
SQL> set serverout on
SQL> variable x1 number
SQL> begin
2 select 5 into :x1 from dual;
3 end;
4 /
PL/SQL procedure successfully completed.
SQL> print x1
X1
----------
5
SQL>
SQL> select rownum from dual
2 connect by level <= :x1;
ROWNUM
----------
1
2
3
4
5
SQL>
SQL> begin
2 dbms_output.put_line('X1 is '||:x1);
3 end;
4 /
X1 is 5
PL/SQL procedure successfully completed.
I spent so much time declaring 100 variables
To me, it looks like a wrong approach. OK, declare a few variables, but 100 of them?! Why wouldn't you switch to something easier to maintain. What? A table, for example.
create table params
(var varchar2(20),
value varchar2(20)
);
Pre-populate it with all variables you use (and then just update their values), or just insert rows:
insert into params (var, value) values ('v1', UDF(params1, param2));
insert into params (var, value) values ('v2', UDF(params3, param4));
...
Fetch values through a function:
create or replace function f_params (par_var in varchar2)
return varchar2
is
retval varchar2(20);
begin
select value
into retval
from params
where var = par_var;
return retval;
end;
Use it (in your query) as:
select columns
from table
where Col1 = f_params('v1')
and Col2 = f_params('v2')
If many users use it, consider creating one "master" params table (which contains all the variables) and a global temporary table (which would be populated and used by each of those users).

Stored procedure variable error in PLSQL when declaring variables

Using Oracle 11g when creating the following stored procedure
create or replace PROCEDURE sp_EqualVote(AREA IN NVARCHAR2, DATEOFVOTE IN DATE)
IS
DECLARE test nvarchar(255);
BEGIN
SELECT
AREA,
DATEOFVOTE,
CASE
WHEN (REMAINVOTES = LEAVEVOTES) THEN REMAINVOTES
END AS EqualVote
INTO test
FROM VOTING
WHERE REMAINVOTES = LEAVEVOTES;
END;
END;
I encounter the following error, I'm not quite sure where to go
PLS-00103: Encountered the symbol "DECLARE" when expecting one of the following: begin function pragma procedure subtype type <an identifier> <a double-quoted delimited-identifier> current cursor delete exists prior external language The symbol "begin" was substituted for "DECLARE" to continue.
I'm a university student and not really that familiar with PLSQL. The idea is the stored procedure should display if an an area has equal votes, given the area and date in the procedure then display an equalvotes labeled column with a value of 50
Quite a few mistakes.
you don't need DECLARE within the named PL/SQL procedure
parameters names should differ from column names, so you'd rather use - for example - p_area in nvarchar2, p_dateofvote in date
if you select 3 columns, you have to put them INTO 3 variables - you've declared only one, so either declare two more, or remove AREA and DATEOFOTE from SELECT
what are those parameters used for? Usually, as a part of the WHERE clause - which is not the case in your code
pay attention to number of rows returned by the SELECT statement. If you're selecting into a scalar variable, make sure that it returns only one row
what will you do with TEST variable, once you get its value? Currently, nothing
you've got an END that is a surplus.
Therefore, consider something like this which should at least compile (depending on table description):
SQL> create table voting (area nvarchar2(10),
2 dateofvote date,
3 remainvotes nvarchar2(10),
4 leavevotes nvarchar2(10));
Table created.
SQL> create or replace procedure
2 sp_equalvote(p_area in nvarchar2, p_dateofvote in date)
3 is
4 test nvarchar2(255);
5 begin
6 select
7 case when remainvotes = leavevotes then remainvotes end
8 into test
9 from voting
10 where remainvotes = leavevotes
11 and area = p_area
12 and dateofvote = p_dateofvote;
13 end;
14 /
Procedure created.
SQL>
[EDIT]
After reading the comment, perhaps you'd rather use a function.
Some sample values:
SQL> insert into voting values (1, date '2019-02-20', 100, 15);
1 row created.
SQL> insert into voting values (1, date '2019-03-10', 300, 300);
1 row created.
Function:
SQL> create or replace function
2 sp_equalvote(p_area in nvarchar2, p_dateofvote in date)
3 return nvarchar2
4 is
5 test nvarchar2(255);
6 begin
7 select
8 case when remainvotes = leavevotes then 'draw'
9 else 'not equal'
10 end
11 into test
12 from voting
13 where area = p_area
14 and dateofvote = p_dateofvote;
15
16 return test;
17 end;
18 /
Function created.
SQL>
Testing:
SQL> select * From voting;
AREA DATEOFVOTE REMAINVOTE LEAVEVOTES
---------- ---------- ---------- ----------
1 20.02.2019 100 15
1 10.03.2019 300 300
SQL> select sp_equalvote(1, date '2019-02-20') res from dual;
RES
--------------------
not equal
SQL> select sp_equalvote(1, date '2019-03-10') res from dual;
RES
--------------------
draw
SQL>
DECLARE is not allowed in the body of a PL/SQL procedure. The IS or AS serves the purpose of delimiting where the variable declaration section starts - so your procedure should be
create or replace PROCEDURE sp_EqualVote(AREA IN NVARCHAR2, DATEOFVOTE IN DATE)
IS
test nvarchar(255);
BEGIN
SELECT
AREA,
DATEOFVOTE,
CASE
WHEN (REMAINVOTES = LEAVEVOTES) THEN REMAINVOTES
END AS EqualVote
INTO test
FROM VOTING
WHERE REMAINVOTES = LEAVEVOTES;
END;
You also had an extra END, which I removed.
Best of luck.

Get number of inserted rows by just plain sql

is there a way to get the number of inserted rows inside of the same transaction?
I see that PL/SQL command:
SQL%ROWCOUNT
does the job, however I don't want to create a procedure just for that!
I tried to simply call
insert into T ...
select SQL%ROWCOUNT;
but it gives me "invalid character".
If I remember well mysql actually had a way to obtain this information, does oracle really not provide any means for that?
I don't want to create a procedure just for that
No need to create any procedure, you could simply use an anonymous PL/SQL block.
For example,
SQL> SET serveroutput ON
SQL> DECLARE
2 var_cnt NUMBER;
3 BEGIN
4 var_cnt :=0;
5 FOR i IN(SELECT empno FROM emp)
6 LOOP
7 INSERT INTO emp(empno) VALUES(i.empno);
8 var_cnt := var_cnt + SQL%ROWCOUNT;
9 END loop;
10 DBMS_OUTPUT.PUT_LINE(TO_CHAR(var_cnt)||' rows inserted');
11 END;
12 /
14 rows inserted
PL/SQL procedure successfully completed.
SQL>
Update If you cannot use PL/SQL, and just plain SQL, then you cannot use SQL%ROWCOUNT.
The only option that comes to my mind is to have a timestamp column in your table, and query the count based on the timestamp to know the number of rows inserted.
Try following,
DBMS_OUTPUT.put_line(TO_CHAR(SQL%ROWCOUNT)||' rows inserted');

Use of dynamic AND in WHERE Clause

DECLARE
dynaCol varchar2(200);
varCol varchar2(100);
BEGIN
dbms_output.put_line(dynaCol);
select name into varCol from GSSP_ETL.DEMO_TABLE_CHECK where ID='1'|| (SELECT q'$ and optional='N'$' NAME FROM DUAL) ;
dbms_output.put_line(varCol);
END;
Though I have a row for id=1 and optional=N I still get no data found error.
SELECT q'$ and optional='N'$' NAME FROM DUAL gives me and optional='N', so there is nothing wrong to use like this.
Please help if anyone know the reason or better way to append the and in where clause.
Your original query is looking for the row with ID = "1 and optional = 'N'" which causes the problem.
You should use dynamic SQL if you want to have variable WHERE clause:
SQL> create table t (id, name, optional)
2 as
3 select 1, 'XXX','N' from dual
4 /
SQL> select * from t;
ID NAM O
---------- --- -
1 XXX N
SQL> set serveroutput on
SQL> declare
2 dynaCol varchar2(200) := q'[ and optional='N']';
3 varCol varchar2(100);
4 p_id int := 1;
5 begin
6
7 execute immediate
8 ' select name from t where id = :x '||dynaCol
9 into varCol using p_id;
10 dbms_output.put_line(varCol);
11 end;
12 /
XXX
Sorry i couldn't comment due to my low reputation..
No,its not possible to do with out immediate i think so,the best way is what Dmitry did.
Its possible to send whole select statement dynamically but a part with out execute immediate..
...You got close.
Since you're using PL/SQL, why not consider creating a persistent object such as a PL/SQL Stored Procedure instead? There will be less code on the JBOSS side and the database schema layout will be further separated from the code you intended on accessing the database.
The Demonstration Table
This is an interpretation from pieces of your original post. This is the table I used to test out my recommendations.
CREATE TABLE "DEMO_TABLE_CHECK"
( "ID" NUMBER(10,0) NOT NULL ENABLE,
"NAME" VARCHAR2(40) NOT NULL ENABLE,
"OPTIONAL" VARCHAR2(5) NOT NULL ENABLE,
CONSTRAINT "DEMO_TABLE_CHECK_PK" PRIMARY KEY ("ID") ENABLE
)
/
My test data:
The Procedure Source Code
CREATE or REPLACE PROCEDURE proc_example(p_id IN number,
p_opt IN varchar2 default null)
IS
Result varchar2(100);
BEGIN
SELECT name
INTO Result
FROM GSSP_ETL.DEMO_TABLE_CHECK
WHERE id = p_id
AND optional = nvl(p_opt, optional);
dbms_output.put_line(Result);
END proc_example;
Notice that this example solution actually does away with the dynamic SQL calls proposed in previous solutions. The realm of PL/SQL procedural code is likely to accommodate for pushing around and concatenating strings of SQL commands.
An Example Call and the Output
begin
proc_example(p_id=> 1, p_opt=>'N');
end;
-- output:
ALPHA
begin
proc_example(p_id=> 1, p_opt=> null);
proc_example(p_id=> 1);
end;
-- output:
ALPHA
ALPHA
All this PL/SQL code, which was originally in an a block passed from the part of your program that accessed the database will now reside on the database.
Discussion of the PL/SQL Procedure Design
The optional part of the query, represented by p_opt, has a default designation. This means if there is no value for that parameter, then the procedure will ignore it and assume it is equal to the defined default value. No errors will be thrown.
AND optional = nvl(p_opt, optional)
This line is a replacement for the add-on SQL string. the input parameter p_opt, whether it was supplied (such as = 'N') or skipped ( implied to = null ), the SQL script includes or excludes the effect of this operator based on the "switching" parameter supplied.
Closing Comments:
If you want to see a better differentiation of results based on that last, dynamic SQL command, you might want to try scenarios where the first criteria is actually ambiguous, such as multiple instances if the ID column. (If ID = 1 was true for more than one record...) But of that set of results, have the second criteria identify something unique when in combination with the first.

Arrays in Oracle SQL

Here's a simplified pseudo-code version of what I'd like to be able to do in PL-SQL (Oracle):
DECLARE
mylist as ARRAY
BEGIN
mylist (1) := '1'
mylist (2) := '3'
...
SELECT *
FROM aTable
WHERE aKey IN mylist;
END;
The SELECT should return the matching records for mylist(1), mylist(2) etc. It should be similar to ORing all the values, but of course we don't know in advance how many values we get.
How can I achieve this? I know that PL/SQL has some collection datatypes, but I can't seem to get them to work properly in SQL statements.
Thanks for any ideas.
This is easy to do with the TABLE() function. The one catch is that the array variable must use a type declared in SQL. This is because SELECT uses the SQL engine, so PL/SQL declarations are out of scope.
SQL> create or replace type numbers_nt as table of number
2 /
Type created.
SQL>
SQL> declare
2 l_array numbers_nt;
3 begin
4 l_array := numbers_nt (7521,7566,7654);
5 for r in ( select ename
6 from emp
7 where empno in ( select *
8 from table (l_array)
9 )
10 )
11 loop
12 dbms_output.put_line ( 'employee name = '||r.ename);
13 end loop;
14 end;
15 /
employee name = PADFIELD
employee name = ROBERTSON
employee name = BILLINGTON
PL/SQL procedure successfully completed.
SQL>
A couple of suggestions:
1.) There's a CAST SQL keyword that you can do that might do the job... it makes your collection be treated as if it were a table.
2.) Pipelined functions. Basically a function returns data that looks like a table.
This link summarises the options and has a number of code listings that explain them.
http://www.databasejournal.com/features/oracle/article.php/3352091/CASTing-About-For-a-Solution-Using-CAST-and-Table-Functions-in-PLSQL.htm