I'll start by saying what I think I have understood.
A explicit cursor is used because we need to reuse the query later.
If an non-explicit cursor is used (i.e. for cs in (select .........)), the request is reexecuted each time the cursor is used.. Therefore an explicit cursor is used for efficienty.
To factor the code, we can use a "pipelined table function" or a "view" to create a cursor.
I would to know like why should I use one solution over another.
Here is the pro and con that I know about these solutions:
neiter pro nor con
I can extract a part of the view or pipelined table function function with a select statement.
con
The record type, and table type used by the "pipelined table" must be declared. It takes time
pro:
We can use all the possibilities of pl/sql statement inside a pipelined table such as a loop
Is all what I have said true?
Is there other things that I should know?
With both cursor types, the database executes the statement within it when it opens. Provided the cursor remains open, you can fetch the results from it later without re-running it. So both are equally efficient in that respect.
An explicit cursor is one where you control its full lifecycle: open, fetch, and close. With an implicit cursor, PL/SQL handles this for you.
You use an explicit cursor when you want full control over the fetch process. The main use case for this bulk collection with a limit.
An explicit cursor can also be handy if you want to use the same query in many places in your application. Declare it at the package level and you can reference it anywhere else you like:
create or replace package pkg as
cursor common_cursor is
select ...
end;
This gives a single definition for the query, which can make your code more maintainable. The problem with this is you're on the hook for opening, fetching, and closing it wherever you use it. In most cases, this results in much more code for minimal benefit.
Which brings us to views. Instead of declaring the common cursor, you could place the common query in a view:
create or replace view common_query as
select ...;
You can then use this in any other SQL statement just like a regular table. So you can join to it, etc. You can't do this with an explicit cursor directly. You have to wrap it in a (pipelined) table function:
create or replace function pipetf
return ...
pipelined
as
retvals ...;
begin
open pkg.common_cursor;
loop
fetch pkg.common_cursor
bulk collect into retvals limit 100;
exit when retvals.count = 0;
for i in 1 .. retvals.count loop
pipe row ( retvals ( i ) ) ;
end loop;
end loop;
close pkg.common_cursor ;
return;
end pipetf;
/
This allows you to use the cursor within another SQL statement like a view:
select * from pipetf;
At this point, a pipelined table function seems a lot more faff than a view. So why bother?
Well it allows you to do things views can't (easily):
Generate new rows or manipulate the result set procedurally
Create parameterized queries
In general you can't pass a variable to a query like this a view (there are ways, but they come with gotchas):
select c2 from ...
where c1 = :var
group by c2;
Whereas you can in an explicit cursor:
cursor common_cursor ( var int ) is
select c2 from ...
where c1 = var
group by c2;
So you could use this in a PTF to create a reusable, parameterized query:
create or replace function pipetf ( var int )
return ...
pipelined
as
retvals ...;
begin
open pkg.common_cursor ( var );
loop
fetch pkg.common_cursor
bulk collect into retvals limit 100;
exit when retvals.count = 0;
for i in 1 .. retvals.count loop
pipe row ( retvals ( i ) ) ;
end loop;
end loop;
close pkg.common_cursor ;
return;
end pipetf;
/
So if you need to use PL/SQL to create new rows, manipulate a queries results, or want reusable parameterized queries, pipelined table functions were the way to go.
Why were?
Oracle Database 18c added polymorphic table functions, which covers many of the row generation/result manipulation examples. And from 19.6 you can create SQL macros, which you can use to emulate parameterized views. These features cover most (all?) the use cases for pipelined table functions (and more).
If you just need a reusable query with no extra processing, I'd stick with a view.
Related
CREATE OR REPLACE PROCEDURE RDBSTAGE.ATCHMNT_ERR_FILEID AUTHID CURRENT_USER
IS
CURSOR cv_atchtab IS
SELECT * FROM ATTACHMENT_ERROR;
I_ATCHMNT_ERR cv_atchtab%ROWTYPE;
V_FILE_ID VARCHAR2(40);
BEGIN
OPEN cv_atchtab;
LOOP
FETCH cv_atchtab BULK COLLECT INTO I_ATCHMNT_ERR;
EXIT WHEN cv_atchtab%NOTFOUND;
FOR i IN 1..I_ATCHMNT_ERR.COUNT
LOOP
SELECT FILE_ID BULK COLLECT
INTO V_FILE_ID
FROM ATTACHMENT_CLAIM t1
WHERE t1.CLAIM_TCN_ID=I_ATCHMNT_ERR(i).CLAIM_TCN_ID;
UPDATE ATTACHMENT_ERROR
SET FILE_ID = V_FILE_ID
WHERE t1.CLAIM_TCN_ID=I_ATCHMNT_ERR.CLAIM_TCN_ID;
END LOOP;
END LOOP;
CLOSE cv_atchtab;
END;
END ATCHMNT_ERR_FILEID;
/
SHOW ERRORS
Procedure ATCHMNT_ERR_FILEID compiled
Errors: check compiler log Errors for PROCEDURE
RDBSTAGE.ATCHMNT_ERR_FILEID:
LINE/COL ERROR
11/40 PLS-00497: cannot mix between single row and multi-row (BULK) in INTO list
14/5 PL/SQL: Statement ignored
14/31 PLS-00302: component 'COUNT' must be declared
PLS-00497: cannot mix between single row and multi-row (BULK) in INTO list
BULK COLLECT INTO is the syntax for populating a PL/SQL collection from a query. But your code is populating a scalar single row variable.
14/31 PLS-00302: component 'COUNT' must be declared
I_ATCHMNT_ERR.COUNT is invalid because count() only applies to collections I_ATCHMNT_ERR is scalar.
To fix this you need to define and use collection types. Something like this:
CREATE OR REPLACE PROCEDURE ATCHMNT_ERR_FILEID
IS
CURSOR cv_atchtab IS
SELECT * FROM ATTACHMENT_ERROR;
type ATCHMNT_ERR_nt is table of cv_atchtab%ROWTYPE;
I_ATCHMNT_ERR ATCHMNT_ERR_nt;
V_FILE_ID VARCHAR2(40);
BEGIN
OPEN cv_atchtab;
LOOP
FETCH cv_atchtab BULK COLLECT INTO I_ATCHMNT_ERR; -- collection type
EXIT WHEN I_ATCHMNT_ERR.COUNT = 0; -- changed this
FOR i IN 1..I_ATCHMNT_ERR.COUNT
LOOP
SELECT FILE_ID
INTO V_FILE_ID -- scalar type
FROM ATTACHMENT_CLAIM t1
WHERE t1.CLAIM_TCN_ID = I_ATCHMNT_ERR(i).CLAIM_TCN_ID;
UPDATE ATTACHMENT_ERROR t2 -- changed this
SET FILE_ID = V_FILE_ID
WHERE t2.CLAIM_TCN_ID = I_ATCHMNT_ERR(i).CLAIM_TCN_ID; -- changed this
END LOOP;
END LOOP;
CLOSE cv_atchtab;
END ATCHMNT_ERR_FILEID;
Here is a db<>fiddle demo of the above working against my guess of the data model.
The Oracle documentation is comprehensive, online and free. The PL/SQL Guide has a whole chapter on Collections and Records which I suggest you read. Find it here.
As an aside, nested loops with single row statements like this are usually a red flag in PL/SQL. They are pretty inefficient and slow. SQL is a set-based language, and we should always try to solve problems using SQL whenever possible, and ideally in one set-based statement. If this code is intended for production (rather than being a homework assignment) you should definitely consider re-writing it in a more performative fashion.
I am trying to execute my below procedure but kept getting error (ORA-00900: invalid SQL statement)
CREATE OR REPLACE PROCEDURE RESETUSERSESSION (run IN VARCHAR2)
IS
cursor usersessiondetail_cur IS
SELECT usd.CLIENTID,usd.OPERID,usd.REGISTER,usd.MACHINE_ID,usd.SESSIONNUMBER
FROM cashiering_dev.CSH_USER usr, cashiering_dev.CSH_USERSESSIONDETAIL usd
WHERE usr.clientid = usd.clientid
AND usr.operid = usd.operid
AND usr.register = usd.register
AND usr.machine_id = usd.machine_id
AND usr.sessionnumber = usd.sessionnumber
AND usr.Machine_ID = 'basrytest'
AND usd.LOGOFFDATETIME IS NULL;
BEGIN
OPEN usersessiondetail_cur;
FOR vItems in usersessiondetail_cur
LOOP
EXECUTE IMMEDIATE 'UPDATE csh_UserSessionDetail
SET ClientID =vItems.CLIENTID
WHERE ClientID =vItems.CLIENTID
AND OperID =vItems.OPERID
AND Register =vItems.REGISTER
AND Machine_ID =vItems.MACHINE_ID
AND SessionNumber =vItems.SESSIONNUMBER';
END LOOP;
CLOSE usersessiondetail_cur;
END;
Your SQL is invalid because the cursor projection names are not in scope when the dynamic SQL string is executed. You need to use placeholders like this:
FOR vItems in usersessiondetail_cur
LOOP
EXECUTE IMMEDIATE 'UPDATE csh_UserSessionDetail
SET ClientID = :p1
WHERE ClientID = :p2
AND OperID = :p3
AND Register = :p4
AND Machine_ID = :p5
AND SessionNumber = :p6'
using vItems.CLIENTID
, vItems.CLIENTID
, vItems.OPERID
, Items.REGISTER
, vItems.MACHINE_ID
, vItems.SESSIONNUMBER;
END LOOP;
Your dynamic code is not an anonymous PL/SQL block or a CALL statement so parameters are passed by position not name, which means you must pass vItems.CLIENTID twice. Find out more.
Other observations
First and foremost, there is absolutely no need to implement dynamic execution for this SQL.
The OPEN and CLOSE cursor statements are not used with a FOR cursor loop.
You do not need an explicit cursor declaration for this query.
The row-by-agonising row UPDATE with a cursor loop is bad practice and needlessly inefficient compared to set-based UPDATE statement.
Your procedure doesn't use the run parameter ...
... but the cursor does have a hardcoded string for MACHINE_ID.
Lastly, the UPDATE statement doesn't actually change the state of the table, because it sets CLIENT_ID = CLIENT_ID, so the whole procedure is pointless.
Apart from that, everything is fine.
I assume you're writing this as a test for understanding how to use dynamic SQL rather than as an implementation of business logic. But even if it is a test it is better to write a proper piece of code which does something. Especially when you're sharing the code with others on StackOverflow. Posting code with so many issues is distracting because potential respondents don't know which to tackle.
A much simpler approach with just FOR loop. We dont require to Open Close cursor in this case as this is internally handled by Oracle. Also I do not understand the need of updating Client ID again. If we are selecting the Client ID in where Clause there is no significance of Updating. Anyhow Enjoy :)
CREATE OR REPLACE
PROCEDURE RESETUSERSESSION(
run IN VARCHAR2)
AS
BEGIN
FOR vItems IN
(SELECT usd.CLIENTID,
usd.OPERID,
usd.REGISTER,
usd.MACHINE_ID,
usd.SESSIONNUMBER
FROM cashiering_dev.CSH_USER usr,
cashiering_dev.CSH_USERSESSIONDETAIL usd
WHERE usr.clientid = usd.clientid
AND usr.operid = usd.operid
AND usr.register = usd.register
AND usr.machine_id = usd.machine_id
AND usr.sessionnumber = usd.sessionnumber
AND usr.machine_id = 'basrytest'
AND usd.LOGOFFDATETIME IS NULL
)
LOOP
UPDATE csh_UserSessionDetail
SET ClientID =vItems.CLIENTID
WHERE ClientID =vItems.CLIENTID
AND OperID =vItems.OPERID
AND Register =vItems.REGISTER
AND Machine_ID =vItems.MACHINE_ID
AND SessionNumber =vItems.SESSIONNUMBER;
END LOOP;
END;
/
I am having a table Course which have column DepId and Course and some other value. I need to search this table for a some set of (DepId, Course). This set will be decided at runtime.
I want to write a single query in a procedure to fetch all the record pertaining to above set.
For example consider the table has data like
DepId Course ...
------------------
1 A
2 B
3 C
4 D
5 E
6 F
Now only I want to search for below records:
DepId Course ...
------------------
1 A
4 D
What would be most efficient way to write above query?
I was thinking of creating an Array of record and passing it in the 'IN' operator. But I was not able to get any example for this. Can someone guide me on this?
Thanks
Leveraging Oracle Collections to Build Array-typed Solutions
The answer to your question is YES, dimensioned variables such as ARRAYS and COLLECTIONS are viable data types in solving problems where there are multiple values in either or both the input and output values.
Additional good news is that the discussion for a simple example (such as the one in the OP) is pretty much the same as for a complex one. Solutions built with arrays are nicely scalable and dynamic if designed with a little advanced planning.
Some Up Front Design Decisions
There are actual collection types called ARRAYS and ASSOCIATIVE ARRAYS. I chose to use NESTED TABLE TYPES because of their accessibility to direct SQL queries. In some ways, they exhibit "array-like" behavior. There are other trade-offs which can be researched through Oracle references.
The query applied to search the COURSE TABLE would apply a JOIN condition instead of an IN-LIST approach.
The use of a STORED PROCEDURE typed object improves database response. Queries within the procedure call can leverage and reuse already compiled code plus their cached execution plans.
Choosing the Right Collection or Array Type
There are a lot of choices of collection types in Oracle for storing variables into memory. Each has an advantage and some sort of limitation. AskTom from Oracle has a good example and break-down of what a developer can expect by choosing one variable collection type over another.
Using NESTED TABLE Types for Managing Multiple Valued Variables
For this solution, I chose to work with NESTED TABLES because of their ability to be accessed directly through SQL commands. After trying several different approaches, I noticed that the plain-SQL accessibility leads to more clarity in the resulting code.
The down-side is that you will notice that there is a little overhead here and there with respect to declaring an instance of a nested table type, initializing each instance, and managing its size with the addition of new values.
In any case, if you anticipate a unknown number of input variables or values (our output), an array-typed data type (collection) of any sort is a more flexible structure for your code. It is likely to require less maintenance in the end.
The Example: A Stored Procedure Search Query
Custom TYPE Definitions
CREATE OR REPLACE TYPE "COURSE_REC_TYPE" IS OBJECT (DEPID NUMBER(10,0), COURSE VARCHAR2(10));
CREATE OR REPLACE TYPE "COURSE_TBL_TYPE" IS TABLE of course_rec_type;
PROCEDURE Source Code
create or replace PROCEDURE ZZ_PROC_COURSE_SEARCH IS
my_input course_tbl_type:= course_tbl_type();
my_output course_tbl_type:= course_tbl_type();
cur_loop_counter pls_integer;
c_output_template constant varchar2(100):=
'DEPID: <<DEPID>>, COURSE: <<COURSE>>';
v_output VARCHAR2(200);
CURSOR find_course_cur IS
SELECT crs.depid, crs.course
FROM zz_course crs,
(SELECT depid, course
FROM TABLE (CAST (my_input AS course_tbl_type))
) search_values
WHERE crs.depid = search_values.depid
AND crs.course = search_values.course;
BEGIN
my_input.extend(2);
my_input(1):= course_rec_type(1, 'A');
my_input(2):= course_rec_type(4, 'D');
cur_loop_counter:= 0;
for i in find_course_cur
loop
cur_loop_counter:= cur_loop_counter + 1;
my_output.extend;
my_output(cur_loop_counter):= course_rec_type(i.depid, i.course);
end loop;
for j in my_output.first .. my_output.last
loop
v_output:= replace(c_output_template, '<<DEPID>>', to_char(my_output(j).depid));
v_output:= replace(v_output, '<<COURSE>>', my_output(j).course);
dbms_output.put_line(v_output);
end loop;
end ZZ_PROC_COURSE_SEARCH;
Procedure OUTPUT:
DEPID: 1, COURSE: A
DEPID: 4, COURSE: D
Statement processed.
0.03 seconds
MY COMMENTS: I wasn't particularly satisfied with the way the input variables were stored. There was a clumsy kind of problem with "loading" values into the nested table structure... If you can consider using a single search key instead of a composite pair (i.e., depid and course), the problem condenses to a simpler form.
Revised Cursor Using a Single Search Value
This is the proposed modification to the table design of the OP. Add a single unique key id column (RecId) to represent each unique combination of DepId and Course.
Note that the RecId column represents a SURROGATE KEY which should have no internal meaning aside from its property as a uniquely assigned value.
Custom TYPE Definitions
CREATE OR REPLACE TYPE "NUM_TBL_TYPE" IS TABLE of INTEGER;
Remove Array Variable
This will be passed directly through an input parameter from the procedure call.
-- REMOVE
my_input course_tbl_type:= course_tbl_type();
Loading and Presenting INPUT Parameter Array (Nested Table)
The following can be removed from the main procedure and presented as part of the call to the procedure.
BEGIN
my_input.extend(2);
my_input(1):= course_rec_type(1, 'A');
my_input(2):= course_rec_type(4, 'D');
Becomes:
create or replace PROCEDURE ZZ_PROC_COURSE_SEARCH (p_search_ids IN num_tbl_type) IS...
and
my_external_input.extend(2);
my_external_input:= num_tbl_type(1, 4);
Changing the Internal Cursor Definition
The cursor looks about the same. You can just as easily use an IN-LIST now that there is only one search parameter.
CURSOR find_course_cur IS
SELECT crs.depid, crs.course
FROM zz_course_new crs,
(SELECT column_value as recid
FROM TABLE (CAST (p_search_ids AS num_tbl_type))
) search_values
WHERE crs.recid = search_values.recid;
The Actual SEARCH Call and Output
The searching portion of this operation is now isolated and dynamic. It does not need to be changed. All the Changes happen in the calling PL/SQL block where the search ID values are a lot easier to read and change.
DECLARE
my_input_external num_tbl_type:= num_tbl_type();
BEGIN
my_input_external.extend(3);
my_input_external:= num_tbl_type(1,3,22);
ZZ_PROC_COURSE_SEARCH (p_search_ids => my_input_external);
END;
-- The OUTPUT (Currently set to DBMS_OUT)
DEPID: 1, COURSE: A
DEPID: 4, COURSE: D
DEPID: 7, COURSE: G
Statement processed.
0.01 seconds
This is something I havee used in the past in a situation similar to yours. Hopefully it helps.
The main benefit of this method would be that if you only passed it a single paramter it would still return all records for that single parameter. This way a single stored procedure with 5 input parameters could be used to search for all combinations of inputs.
Just call the stored procedure passing in the set and should return all values mathcing the criteria
usp_custom_search '1','A'
usp_custom_search '4','D'
usp_custom_search '4',NULL
usp_custom_search NULL,'A'
etc
Stored Procedure:
CREATE OR REPLACE PROCEDURE custom_search (
dep_id IN VARCHAR2,
course_id IN VARCHAR2,
result_set OUT SYS_REFCURSOR)
BEGIN
query_str VARCHAR2(1000);
query_str := 'SELECT';
query_str := query_str || ' DepId, Course';
query_str := query_str || ' FROM Course';
query_str := query_str || ' WHERE 1=1';
IF (dep_id is not null) then query_str := query_str || ' AND DepId = ''' || dep_id || ''''; END IF;
IF (course_id is not null) then query_str := query_str || ' AND Course = ''' || course_id || ''''; END IF;
open result_set for query_str;
END custom_search;
/
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.
I have a hefty SQL statement with unions where code keeps getting re-used. I was hoping to find out if there is a way to re-use a single bind variable without repeating the variable to for "USING" multiple times.
The code below returns "not all variables bound" until I change the "USING" line to "USING VAR1,VAR2,VAR1;"
I was hoping to avoid that as I'm referring to :1 in both instances - any ideas?
declare
var1 number :=1;
var2 number :=2;
begin
execute immediate '
select * from user_objects
where
rownum = :1
OR rownum = :2
OR rownum = :1 '
using var1,var2;
end;
/
EDIT: For additional info, I am using dynamic SQL as I also generate a bundle of where conditions.
I'm not great with SQL arrays (I am using a cursor in my code but I think that will overcomplicate the issue) but the pseudocode is:
v_where varchar2(100) :='';
FOR i in ('CAT','HAT','MAT') LOOP
v_where := v_where || ' OR OBJECT_NAME LIKE ''%' || i.string ||'%''
END;
v_where := ltrim(v_where, ' OR');
And then modifying the SQL above to something like :
execute immediate '
select * from user_objects
where
rownum = :1
OR rownum = :2
OR rownum = :1 AND ('||V_WHERE||')'
using var1,var2;
There are some options you might consider, although they may require changes, either to how you execute your SQL statement or to your SQL statement itself.
Use DBMS_SQL instead of EXECUTE IMMEDIATE -- DBMS_SQL (see http://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_sql.htm) is harder to use than EXECUTE IMMEDIATE, but gives you more control over the process -- including the ability (through DBMS_SQL.BIND_VARIABLE and DBMS_SQL.BIND_ARRAY) to bind by name instead of by position.
Use EXECUTE IMMEDIATE with a WITH clause -- You might be able restructure your query to use WITH clause that gathers your bind variables in subquery at the beginning, and then joins to the subquery (instead of referencing the bind variables directly) whenever it needs them. It might look something like this
with your_parameters as
(select :1 as p1, :2 as p2 from dual)
select *
from your_table, your_parameters
where your_table.some_column1 = your_parameters.p1
and your_table.some_column2 <= your_parameters.p1
and your_table.some_column3 = your_parameters.p2
This could affect the performance of your query, but it might be an acceptable compromise.
Don't use dynamic SQL -- Of course, if you don't need dynamic SQL, you don't need to use EXECUTE IMMEDIATE, so the "bind only by position" limitiation does not apply. Are you sure you really need to use dynamic SQL?
EDIT: If you're using dynamic SQL because you have a variable number of OR conditions like you posted in your edit, you might be able to avoid using dynamic SQL by doing one of the following:
If the OR criteria come from a table (or query) -- Join to that table (or query) instead of using a list of OR criteria. For example, if CAT, HAT, and MAT are listed in a column named YOUR_CRITERIA in a table named YOUR_CRITERIA_TABLE you might add YOUR_CRITERIA_TABLE to the FROM clause and replace the OBJECT_NAME LIKE '%CAT% OR OBJECT_NAME LIKE '%MAT% OR OBJECT_NAME LIKE '%HAT% OR OBJECT_NAME LIKE '%MAT% in the WHERE clause with something like OBJECT_NAME LIKE '%' || YOUR_CRITERIA_TABLE.YOUR_CRITERIA || '%'.
Otherwise, you might put the criteria in a global temporary table -- If your criteria don't come from a table (or query), you could (once, at design time, not at run time) create a global temporary table to hold them, and then at run time, insert the criteria into the global temporary table and then join to it as described in item 1.
Or, you might put the criteria in an nested table -- This is like item 2, except uses a nested table (one created using CREATE TYPE...IS TABLE OF) instead of a global temporary table. You could create or own nested table type, or use a built-in one like SYS.ODCIVARCHAR2LIST. In PL/SQL, you would populate an variable of this type, and then use it like a "real" table like in item 1.
An example of item 3 might look something like:
DECLARE
tblCriteria SYS.ODCIVARCHAR2LIST;
BEGIN
tblCriteria := SYS.ODCIVARCHAR2LIST();
-- In "real" code you might populate the nested table in a loop.
-- This example populates it explicitly so that it will compile. For the
-- purpose of the example, we could have populated the nested table in
-- a single statement:
-- tblCriteria := SYS.ODCIVARCHAR2LIST('CAT', 'HAT', 'MAT');
tblCriteria.EXTEND(1);
tblCriteria(tblCriteria.LAST) := 'CAT';
tblCriteria.EXTEND(1);
tblCriteria(tblCriteria.LAST) := 'HAT';
tblCriteria.EXTEND(1);
tblCriteria(tblCriteria.LAST) := 'MAT';
FOR rec IN
(
SELECT
USER_OBJECTS.*
FROM
USER_OBJECTS,
TABLE(tblCriteria) YOUR_NESTED_TABLE
WHERE
USER_OBJECTS.OBJECT_NAME LIKE '%' || YOUR_NESTED_TABLE.COLUMN_VALUE || '%'
)
LOOP
-- Do something. For example, print out the object name.
DBMS_OUTPUT.PUT_LINE(rec.OBJECT_NAME);
END LOOP;
END;
No, unfortunately, the bind variables for EXECUTE IMMEDIATE must be provided in the same order they appear in the statement, and the bind variable names are ignored. So you'll just have to have :1, :2 and :3 in your statement.