Closed. This question is off-topic. It is not currently accepting answers.
Want to improve this question? Update the question so it's on-topic for Stack Overflow.
Closed 10 years ago.
Improve this question
I want to use single cursor to fetch single record or all records using where condition, e.g. : student is table and sid is an attribute.
I have two cursors,
DECLARE S1 CURSOR FOR SELECT * FROM Student;
and
Declare S2 Cursor for select * from Student where sid=11
My query is on how to combine these two conditions to use only one cursor. I need this as I have two functions m_viewStudent and m_viewallStudents, for this I would like to use only one cursor to display the requested details from table.
so how can I achieve this ?
Try this:
select *
from student
where sid = 11
or not exists (select 1 from student where sid = 11)
Here is a sqlfiddle demo
UPDATE
If you want to use the same cursor for different functions then you can do it like this:
create package p is
procedure one_sid(in_sid number);
procedure all_sid;
end p;
/
create package body p is
cursor c(p_sid number) is
select *
from student
where sid = p_sid or p_sid is null;
procedure one_sid(in_sid number) is
begin
open c(in_sid);
close c;
end;
procedure all_sid is
begin
open c(null);
close c;
end;
end p;
/
You can change the cursor to
SELECT * FROM students WHERE sid like '%' || variable_from_function || '%';
This will ensure that when function m_viewStudent calls this, only the sid (value sent to this function variable_from_function) record is picked. When m_viewallStudents calls this, all records get picked as variable_from_function would be null.
Assuming your procedure variable is $var, try this:
Declare S2 Cursor for select * from Student
where sid=$var
or $var is null
And pass in a null when you want them all, or make the default value of the parameter null and pass in no parameter
The best way to do this is with a Cursor Variable, more commonly called a Ref Cursor. This is basically a pointer to a result set. The advantage of a Ref Cursor is that we can vary the select statement, like this:
create or replace package student_utils is
-- a hard-types ref cursor
type stud_cur is ref cursor return students%rowtype;
function get_students
( p_sid in students.sid%type := null )
return stud_cur;
end;
Note that I have made a couple of presumptions about how you are intending to use the code. Using a package allows us to define a hard-typed ref cursor, which means that it can only be used for queries which match the projection of the STUDENTS table. (If you don't have an actual STUDENTS table you can use of of the views or define a Pl/SQL Record instead.)
create or replace package body student_utils is
function get_students
( p_sid in students.sid%type := null )
return stud_cur
is
return_value stud_cur;
begin
if p_sid is null
then
open return_value for
select * from m_viewallStudents;
else
open return_value for
select * from m_viewStudent
where sid = p_sid;
end if;
return return_value;
end;
end;
These queries are hard-coded but we can also open Ref Cursors with dynamic SQL, which is a powerful technique.
Read the documentation to find out more.
Related
Is it possible to select the parameters for calling a procedure from the select statement?
EXECUTE PROCEDURE_NAME(para1,para2,para3,para4);
commit;
Is it possible to select para1,para2,para3,para4 from a select query?
EXECUTE PROCEDURE_NAME((SELECT PARA1,PARA2,PARA3,PARA4 FROM TABLEA))
COMMIT;
I do not have access to modify the procedure.
As a slight variation on what #vc74 suggested, you could just replace your EXECUTE command (which, assuming this is SQL*Plus or SQL Developer anyway, is just a wrapper for an anonymous block anyway) with an explicit anonymous block:
begin
for r in (SELECT PARA1,PARA2,PARA3,PARA4 FROM TABLEA) loop
PROCEDURE_NAME(r.PARA1,r.PARA2,r.PARA3,r.PARA4);
end loop;
end;
/
(I've left the bits from your original call uppercase and the new bits lower case mostly to distinguish them.)
Using a loop just means you don't need to declare local variables and select into those. It would also allow you to process multiple rows from the table, though I see form a comment you only expect one row. However, the flip side of that is it won't complain if there are no rows, or if there is more than one row, as the variable approach would do.
You could also use a record type to avoid declaring all the parameters separately:
declare
l_row tablea%rowtype;
begin
SELECT * into l_row FROM TABLEA;
PROCEDURE_NAME(l_row.PARA1,l_row.PARA2,l_row.PARA3,l_row.PARA4);
end;
/
This now does expect exactly one row to be found in the table.
You can call the functions in sql. So if you are able to create a function in your schema then you can do the following:
create a function function_name in your schema that calls the procedure procedure_name and returns some dummy result
use this function in sql query: select function_name(para1,para2,para3,para4) from tablea
example of function:
create or replace function function_name(
p1 varchar2,
p2 varchra2,
p3 varchar2,
p4 varchar2
) return number
is
begin
procedure_name(p1,p2,p3,p4); -- here you execute the procedure
return null;
end;
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
I'm looking for a way to return one case-insensitive varchar2 from a table given as a parameter.
The database is configured to have as the first column the one I'll look into.
This is what I got to:
create or replace function generic_return(nam varchar2, table_n varchar2)
return varchar2
as
case_insensitive varchar2(300);
case_ins varchar2(300);
colu varchar2(30);
t_nam varchar2(30):=upper(table_n);
cursor point is execute inmediate select colu from t_nam;
cursor col is select column_name from cols where table_name=t_nam;
non_existent_table exception;
begin
case_ins:=upper(rtrim(ltrim(nam)));
open col;
fetch col into colu;
close col;
select column_name from cols where table_name=upper(table_n);
if colu is null then
raise non_existent_table;
end if;
open point;
loop
fetch point into case_insensitive;
exit when point%notfound;
if upper(case_insensitive)=case_ins then
return case_insensitive;
end if;
end loop;
close point;
return null;
end;
/
The function receives what to look for, and the name of the table to look into, then the first variables are to compare them, t_nam is to use the uppercased version of the second parameter... and from that there's the huge mess: I get the column name from the table in col, from cols, and then I try to make a cursor that goes around checking if what the tuple, going by the same modifications, is the first parameter, or not, if it happens to be, then it should return the unmodified version of the one in the table. Otherwise, it should return "null", which I'll treat differently on each procedure that uses it.
By null I mean nothingness, not the string.
Yes, you are right. That's a pretty huge mess. :-)
For starters, you cannot declare an explicit cursor as an execute immediate statement.
Next, if you want the first column in the table, you need to specify
WHERE column_id = 1
and then you can just grab it via a SELECT-INTO, no need for an explicit cursor.
Then you could try something like:
my_cur SYS_REFCURSOR;
BEGIN
...
OPEN my_cur FOR 'SELECT ' || first_col || ' FROM ' || t_name;
LOOP
FETCH my_cur INTO case_insensitive;
EXIT WHEN my_cur%NOTFOUND;
... logic for match ....
END LOOP;
CLOSE my_cur;
I want to create a procedure in PL/SQL that has 5 steps. Step 1 and 2 execute first and return an ID. In step 3, we have a SELECT statement that has a condition with that returned ID. I want then to take all of the results of that SELECT statement and use them in a JOIN in another SELECT statement and use THOSE results in a 3rd SELECT statement again using JOIN. From what I've seen, I can't use CURSOR in JOIN statements. Some of my co-workers have suggested that I save the results in a CURSOR and then use a loop to iterate through each row and use that data for the next SELECT. However since I'm going to do 2 selects this will create a huge fork of inside loops and that's exactly what I'm trying to avoid.
Another suggestion was to use Temprary Tables to store the data. However this procedure could be executed at the same time by many users and the table's data would conflict with each other. Right now I'm looking at LOCAL Temporary tables that supposedly filter the data according the the session but I'm not really sure I want to create dummy tables for my procedures since I want to avoid leaving trash in the schema (this procedure is for a custom part of the application). Is there a standard way of doing this? Any ideas?
Sample:
DECLARE
USERID INT := 1000000;
TEXT1 VARCHAR(100);
TEXT_INDEX INT;
CURSOR NODES IS SELECT * FROM NODE_TABLE WHERE DESCRIPTION LIKE TEXT || '%';
CURSOR USERS IS SELECT * FROM USERGROUPS JOIN NODES ON NODES.ID = USERGROUPS.ID;
BEGIN
SELECT TEXT INTO TEXT1 FROM TABLE_1 WHERE ID = USERID;
TEXT_INDEX = INSTR(TEXT, '-');
TEXT = SUBSTR(TEXT, 0, TEXT_INDEX);
OPEN NODES;
OPEN USERS;
END;
NOTE: This does NOT work. Oracle doesn't support joins between cursors.
NOTE2: This CAN be done in a single query but for the sake of argument (and in my real use case) I want to break those steps down in a procedure. The sample code is a depiction of what I'm trying to achieve IF joins between cursors worked. But they don't and I'm looking for an alternative.
I ended up using a function (although a procedure could be used as well) along with tables. Things I've learned and one should pay attention to:
PL/SQL functions can only return types that have been declared in the schema in advance and are clear. You can't create a function that returns something like MY_TABLE%ROWTYPE, even though it seems the type information is available it is not acceptable. You have to instead create a custom type of MY_TABLE%ROWTYPE is you want to return it.
Oracle treats tables of declared types differently from tables of %ROWTYPE. This confused the hell out of me at first but from what I've gathered this is how it works.
DECLARE TYPE MY_CUSTOM_TABLE IS TABLE OF MY_TABLE%ROWTYPE;
Declares a collection of types of MY_TABLE row. In order to add to this we must use BULK COLLECT INTO from an SQL statement that queries MY_TABLE. The resulting collection CANNOT be used in JOIN statements is not queryable and CANNOT be returned by a function.
DECLARE
CREATE TYPE MY_CUSTOM_TYPE AS OBJECT (COL_A NUMBER, COL_B NUMBER);
CREATE TYPE MY_CUSTOM_TABLE AS TABLE OF MY_CUSTOM_TYPE;
my_custom_tab MY_CUSTOM_TABLE;
This create my_custom_tab which is a table (not a collection) and if populated can be queried at using TABLE(my_custmo_tab) in the FROM statement. As a table which is declared in advance in the schema this CAN be returned from a function. However it CANNOT be populated using BULK COLLECT INTO since it is not a collection. We must instead use the normal SELECT INTO statement. However, if we want to populate it with data from an existing table that has 2 number columns we cannot simply do SELECT * INTO my_custom_tab FROM DOUBLE_NUMBER_TABLE since my_custom_tab hasn't been initialized and doesn't contain enough rows to receive the data. And if we don't know how many rows a query returns we can't initialize it. The trick into populating the table is to use the CAST command and cast our select result set as a MY_CUSTOM_TABLE and THEN add it.
SELECT CAST(MULTISET(SELECT COL_A, COL_B FROM DOUBLE_NUMBER_TABLE) AS MY_CUSTOM_TABLE) INTO my_custom_tab FROM DUAL
Now we can easily use my_custom_tab in queries etc through the use of the TABLE() function.
SELECT * FROM TABLE(my_custom_tab)
is valid.
You can do such decomposition in many ways, but all of them have a significant performance penalty in comaration with single SQL statement.
Maintainability improvement are also questionable and depends on specific situation.
To review all possibilities please look through documentation.
Below is some possible variants based on simple logic:
calculate Oracle user name prefix based on given Id;
get all users whose name starts with this prefix;
find all tables owned by users from step 2;
count a total number of found tables.
1. pipelined
Prepare types to be used by functions:
create or replace type TUserRow as object (
username varchar2(30),
user_id number,
created date
)
/
create or replace type TTableRow as object (
owner varchar2(30),
table_name varchar2(30),
status varchar2(8),
logging varchar2(3)
-- some other useful fields here
)
/
create or replace type TUserList as table of TUserRow
/
create or replace type TTableList as table of TTableRow
/
Simple function to find prefix by user id:
create or replace function GetUserPrefix(piUserId in number) return varchar2
is
vUserPrefix varchar2(30);
begin
select substr(username,1,3) into vUserPrefix
from all_users
where user_id = piUserId;
return vUserPrefix;
end;
/
Function searching for users:
create or replace function GetUsersPipe(
piNameStart in varchar2
)
return TUserList pipelined
as
vUserList TUserList;
begin
for cUsers in (
select *
from
all_users
where
username like piNameStart||'%'
)
loop
pipe row( TUserRow(cUsers.username, cUsers.user_id, cUsers.created) ) ;
end loop;
return;
end;
Function searching for tables:
create or replace function GetUserTablesPipe(
piUserNameStart in varchar2
)
return TTableList pipelined
as
vTableList TTableList;
begin
for cTables in (
select *
from
all_tables tab_list,
table(GetUsersPipe(piUserNameStart)) user_list
where
tab_list.owner = user_list.username
)
loop
pipe row ( TTableRow(cTables.owner, cTables.table_name, cTables.status, cTables.logging) );
end loop;
return;
end;
Usage in code:
declare
vUserId number := 5;
vTableCount number;
begin
select count(1) into vTableCount
from table(GetUserTablesPipe(GetUserPrefix(vUserId)));
dbms_output.put_line('Users with name started with "'||GetUserPrefix(vUserId)||'" owns '||vTableCount||' tables');
end;
2. Simple table functions
This solution use same types as a variant with pipelined functions above.
Function searching for users:
create or replace function GetUsers(piNameStart in varchar2) return TUserList
as
vUserList TUserList;
begin
select TUserRow(username, user_id, created)
bulk collect into vUserList
from
all_users
where
username like piNameStart||'%'
;
return vUserList;
end;
/
Function searching for tables:
create or replace function GetUserTables(piUserNameStart in varchar2) return TTableList
as
vTableList TTableList;
begin
select TTableRow(owner, table_name, status, logging)
bulk collect into vTableList
from
all_tables tab_list,
table(GetUsers(piUserNameStart)) user_list
where
tab_list.owner = user_list.username
;
return vTableList;
end;
/
Usage in code:
declare
vUserId number := 5;
vTableCount number;
begin
select count(1) into vTableCount
from table(GetUserTables(GetUserPrefix(vUserId)));
dbms_output.put_line('Users with name started with "'||GetUserPrefix(vUserId)||'" owns '||vTableCount||' tables');
end;
3. cursor - xml - cursor
It's is a specific case, which may be implemented without user-defined types but have a big performance penalty, involves unneeded type conversion and have a low maintainability.
Function searching for users:
create or replace function GetUsersRef(
piNameStart in varchar2
)
return sys_refcursor
as
cUserList sys_refcursor;
begin
open cUserList for
select * from all_users
where username like piNameStart||'%'
;
return cUserList;
end;
Function searching for tables:
create or replace function GetUserTablesRef(
piUserNameStart in varchar2
)
return sys_refcursor
as
cTableList sys_refcursor;
begin
open cTableList for
select
tab_list.*
from
(
XMLTable('/ROWSET/ROW'
passing xmltype(GetUsersRef(piUserNameStart))
columns
username varchar2(30) path '/ROW/USERNAME'
)
) user_list,
all_tables tab_list
where
tab_list.owner = user_list.username
;
return cTableList;
end;
Usage in code:
declare
vUserId number := 5;
vTableCount number;
begin
select count(1) into vTableCount
from
XMLTable('/ROWSET/ROW'
passing xmltype(GetUserTablesRef(GetUserPrefix(vUserId)))
columns
table_name varchar2(30) path '/ROW/TABLE_NAME'
)
;
dbms_output.put_line('Users with name started with "'||GetUserPrefix(vUserId)||'" owns '||vTableCount||' tables');
end;
Of course, all variants may be mixed, but SQL looks better at least for simple cases:
declare
vUserId number := 5;
vUserPrefix varchar2(100);
vTableCount number;
begin
-- Construct prefix from Id
select max(substr(user_list.username,1,3))
into vUserPrefix
from
all_users user_list
where
user_list.user_id = vUserId
;
-- Count number of tables owned by users with name started with vUserPrefix string
select
count(1) into vTableCount
from
all_users user_list,
all_tables table_list
where
user_list.username like vUserPrefix||'%'
and
table_list.owner = user_list.username
;
dbms_output.put_line('Users with name started with "'||vUserPrefix||'" owns '||vTableCount||' tables');
end;
P.S. All code only for demonstration purposes: no optimizations and so on.
I'm trying to return a result set from a 11g Oracle table, after I've received the set back I need to set all the fetched rows as updated.
The below script returns only one row at a time, and I can't really get the update to work as I like. Some basic explanation would be highly appriciated.
Maybe it's also a bad idea using the rowtype?
CREATE OR REPLACE PROCEDURE OWNER_EXT.X_GETEXTERNALACCOUNTREF(result out X_externalaccountref%rowtype)
IS
cur SYS_REFCURSOR;
BEGIN
open cur for select * from X_externalaccountref WHERE externalstatus IS NULL;
LOOP
FETCH cur INTO result;
update X_externalaccountref set externalstatus = 1, externalchangedate = SYSDATE() where X_id = result.X_id;
EXIT WHEN cur%NOTFOUND;
END LOOP;
close cur;
END;
/
Why you only get one record returned:
You loop over the recordset, fetching one record at a time, and each time you assign this record to the output variable. So only your last record will be stored in the output variable.
What you want to do is return a record set. For this, you can use pl/sql tables. To easily fetch a set into a table you could use bulk collect into (a link - but google will find you a lot more). You can use the rowtype as the type for your table, or a type you make up yourself. You could use a package variable to declare a type, or define a type in the database schema. If you use rowtype, that is not bad at all. If your table should change, then rowtype will reflect this aswell, saving you the hassle of trawling through your code to add or remove columns should you have manually declared each column in a own type.
Ex:
function hand_out_money
return pck_emp.t_tab_emp
is
tab_emp pck_emp.t_tab_emp;
begin
select *
bulk collect into tab_emp
from emp
where deptno = 20;
update emp
set comm = comm + 10
where deptno = 20;
return tab_emp;
end;
To be able to use this, have t_tab_emp declared in a package_spec. This way, you can reference the returned type from where you call this code. Just put type t_tab_emp is table of emp%rowtype; in there - like i would do in package pck_emp.
Your calling code could then be:
declare
tab_result pck.t_tab_emp;
begin
tab_result := hand_out_money;
end;
To do your update:
If you fetch all your record into a plsql table, you could thereafter do a single update statement:
update X_externalaccountref
set externalstatus = 1, externalchangedate = SYSDATE
where externalstatus IS NULL;
Also: you could just use a function if you only return this one variable, it would make sense for a getter.
Also: i generally don't like function or procedures named 'getxxx' who then do DML. More proper would be to call your procedure 'activate_external_accounts' for example, which would then return the recordset. Mind, that if you do a bulk select before the update, the externalstatus and externalchangedate won't be updated! So you don't get a resultset returned, but a pre-update set.
update X_externalaccountref
set externalstatus = 1
, externalchangedate = SYSDATE
where externalstatus is null;
Why not?
I can't seem to get variables to work in an Oracle PL/SQL where clause. I come from a Microsoft SQL Server background and there it was easy. For example, what would be all steps needed to do something similar to the following?
declare #var int set #var = 1
select * from SomeTable where SomeField = #var
This doesn't seem like it should be hard in PL/SQL, but evidently it is. :-/ I hear I need to use cursors and the like in PL/SQL?
Any help would be greatly appreciated. Thanks.
What do you want to do with the data that the SELECT returns? If you just want to see it you don't need PL/SQL at all, just do this in SQL Plus:
variable var number
exec :var := 1
select * from SomeTable where SomeField = :var;
Or in a tool like SQL Developer or Toad, just do this:
select * from SomeTable where SomeField = :var;
and it will prompt you to enter the value for :var.
The following code declares a variable var to use in the WHERE clause, and a variable result to put the result in then executes it inside a PL/SQL block.
DECLARE
var INT := 1;
result INT;
BEGIN
SELECT 123
INTO result
FROM DUAL
WHERE var = 1;
DBMS_OUTPUT.put_line (var);
DBMS_OUTPUT.put_line (result);
END;
The DBMS_OUTPUT.PUT_LINE calls make it produce this DBMS output:
1
123
declare
type t_rec is record
(
col1 number,
col2 myTable.col2%type
);
v_rec t_rec;
type t_tab is table of v_rec%type index by binary_integer;
v_tab t_tab;
begin
select col1, col2
bulk collect into v_tab
from myTable
where col3 = 'BLAH';
-- do something great with v_tab...
end;
Also know that if you try to select into (or bulk collect into) a variable and no rows are returned, you'll get a no_data_found exception, so you may want to handle that situation.
See more here on pl/sql collections. Above uses an associative array, but there are nested tables and varrays as well. Again, see the link.
Hope that helps.
I use it like this
select * from sec_mainmenu where serno = '&MENU_ID';
When you run it, pl/sql will prompt for MENU_ID value.