Cursor giving NO_DATA_FOUND if column with empty value included - oracle

I have a cursor defined as
CURSOR all_movie_nominations IS SELECT a.title, a.award_year,n.people_id FROM award a JOIN
nominee n ON
a.award_id = n.award_id
WHERE n.movie_id = (SELECT movie_id FROM movie WHERE movie_name = movieName);
The cursor is being opened and closed right.
Using the cursor in following code as
FETCH all_movie_nominations INTO award_title, award_year,nominee_people_id;
WHILE all_movie_nominations%FOUND LOOP
SELECT given_name, family_name INTO first_name, last_name FROM people WHERE people_id=nominee_people_id;
IF first_name = '' THEN
DBMS_OUTPUT.PUT_LINE('The Movie was nominated for '||award_title||' for the year '||award_year);
ELSE
DBMS_OUTPUT.PUT_LINE(first_name||' '||last_name||' was nominated for '||award_title||' for the year '||award_year);
END IF;
FETCH all_movie_nominations INTO award_title, award_year, nominee_people_id;
END LOOP;
Now the attribute n.people_id in cursor definition. This column contains empty values for some rows.
On executing the above code, I get the correct output but a 'NO_DATA_FOUND` Exception at the end of the output.
If I exclude the n.people_id from the cursor, I do not get any exception.
Any suggestions on what is possibly going wrong?

Unless this is student code about learning to use cursors, you would never code this way in real life. SQL statements inside a loop construct are a red flag. Because you are hammering the database this way with numerous (number of awards) tiny select statements retrieving a single person. Which means the PL/SQL engine has to switch to the SQL engine numerous times, leading to relatively expensive context switches. If you use joins, or in your case outer joins, you can minimize the number of context switches to one.
An example of an alternative (untested of course):
for r in
( select a.title
, a.award_year
, p.given_name
, p.family_name
from award
inner join nominee n on a.award_id = n.award_id
left outer join people p on n.people_id = p.people_id
where n.movie_id = (select movie_id from movie where movie_name = movieName)
)
loop
dbms_output.put_line
( case
when r.given_name is null then
'The Movie'
else
r.given_name || ' ' || r.family_name
end ||
' was nominated for ' || r.title || ' for the year ' || r.award_year
);
end loop;

I am not sure what could be happen, I think that the not_found exception is thrown by the inner select sentence:
IF nominee_people_id is not null THEN
SELECT given_name, family_name INTO first_name, last_name FROM people WHERE
people_id=nominee_people_id;
END IF;
I would try to put a condition that checks nominee_people_id is not null for executing the senetence.
I hope it was useful for you
Redgards

Hi you can opt for exception block for that if you want to ignore IF--END IF BLOCK;
BEGIN
SELECT given_name, family_name INTO first_name, last_name FROM people WHERE people_id=nominee_people_id;
EXCEPTION WHEN NO_DATA_FOUND THEN
dbms_output.put_line('no data found for '||nominee_people_id);
END;

Related

Replacing DBMS_OUPUT in a PL/SQL test LOOP to return a collection

So I have this assignment where I have to create a stored procedure to search for movies in an Oracle DataBase.
The search string follow the following logic :
It looks for the year of the movie in parenthesis
Ex. (1992)
It looks for a range of year in brackets
Ex. [1992,2000]
It looks for a word contained in the title, country, realisator, genre, actors or scenarists.
Any of the above can be combined multiple times.
Ex. : The Lord Of the Ring Ian McKellen Christopher Lee [1992,2000]
The logic used to solve this problem was to make a giant query to group all the required data then use a cursor to loop through the result set with a cursor to check if every word of the search string is valid.
I managed to make a procedure that works as expected, but the only way I found to return the results was to use DBMS_OUTPUT. Now the problem is that when I plug this is Hibernate the DBMS_OUTPUT is not sent over to the client. I've read some way to force the output by setting DBMS_OUTPUT.enable, but I feel that this is not the proper way to do it.
So here are my questions :
Is my logic flawed? Is there a simpler way to archive this with a single select or something?
Is there a way to dynamically push data inside a cursor and return it?
Am I really supposed to trick the DBMS_OUTPUT so it is sent over to hibernate?
Here's my code :
CREATE OR REPLACE PROCEDURE p_SearchFilm(searchString IN VARCHAR2) IS
IsValid BOOLEAN;
y1 INTEGER;
y2 INTEGER;
subStrArray apex_application_global.vc_arr2;
term VARCHAR(100);
CURSOR films IS
Select FilmId, Titre, real.Prenom||' '||real.nom as Realisateur, anneeSortie, ListPays, ListGenres,
ListScenaristes, ListActeurs, langueOrigine
from Film
natural left join
(select FilmId, listagg(p.Nom, ',') within group (Order By p.nom) ListPays from Film
natural join Film_vs_pays
natural join Pays p
Group by FilmId)
natural left join
(select FilmId, listagg(g.Nom, ',') within group (Order By g.nom) ListGenres from Film
natural join Film_vs_Genre
natural join Genre g
Group by FilmId)
natural left join
(select FilmId, listagg(p.Prenom||' '||p.Nom, ',') within group (Order By p.nom) ListScenaristes from Film
natural join Scenariste s
join Personne p on s.personneId = p.personneId
Group by FilmId)
natural left join
(select FilmId, listagg(p.Prenom||' '||p.Nom, ',') within group (Order By p.nom) ListActeurs from Film
natural join Personnage perso
join Personne p on perso.personneId = p.personneId
Group by FilmId)
left join Personne real on real.personneId = realisateurId;
BEGIN
<<FILM_LOOP>>
FOR film IN films LOOP
subStrArray := apex_util.string_to_table(searchString, ' ');
FOR i in 1..subStrArray.count LOOP
IsValid:= FALSE;
term:= subStrArray(i);
IF REGEXP_LIKE(term, '\(\d{4}\)') THEN
IF film.anneeSortie = TO_NUMBER(regexp_substr(term, '\d{4}')) THEN
IsValid:= TRUE;
END IF;
ELSIF REGEXP_LIKE(term, '\[\d{4},\d{4}\]') THEN
y1:= regexp_substr(term, '\d{4}', 1, 1);
y2:= regexp_substr(term, '\d{4}', 1, 2);
IF film.anneeSortie BETWEEN y1 AND y2 THEN
IsValid:= TRUE;
END IF;
ELSE
IF UPPER(film.Titre||film.Realisateur||film.ListActeurs||film.ListScenaristes||film.ListGenres||film.ListPays||film.langueOrigine)
LIKE '%'||UPPER(term)||'%' THEN
IsValid:= TRUE;
END IF;
END IF;
IF NOT IsValid THEN
CONTINUE FILM_LOOP;
END IF;
END LOOP;
DBMS_OUTPUT.put_line(film.FilmId||'|'||film.Titre);
END LOOP;
END;
A small disclaimer here :
I saw some similar questions that addressed this issue, but the ones that used cursors were returning a complete select, not hand picked rows.
The question about DBMS_OUTPUT and Hibernate stated that it should be avoided.
The questions using piped rows seamed to work only with functions (Changing the procedure for a function called by the procedure could be a valid work around, I'd like to know if something else is possible before tho).
The use of the DBMS_OUTPUT package is pretty much bounded to developer executions of anonymous blocks, and hence not suited for your intended communication with the Hibernate framework.
If you already have a stored procedure to apply your filter and determine your positive results, a solution could be populate a temporary table with those positives, and then return an open cursor which will only have the data from that temporary table, something like:
create global temporary table movie_results( movie varchar2(200) ) on commit preserve rows;
Of course your temp table can have more columns, but let me leave it like this, just to illustrate my solution.
CREATE OR REPLACE PROCEDURE p_SearchFilm(searchString IN VARCHAR2, movies out SYS_REFCURSOR) IS
IsValid BOOLEAN;
y1 INTEGER;
y2 INTEGER;
subStrArray apex_application_global.vc_arr2;
term VARCHAR(100);
CURSOR films IS
Select FilmId, Titre, real.Prenom||' '||real.nom as Realisateur, anneeSortie, ListPays, ListGenres,
ListScenaristes, ListActeurs, langueOrigine
from Film
natural left join
(select FilmId, listagg(p.Nom, ',') within group (Order By p.nom) ListPays from Film
natural join Film_vs_pays
natural join Pays p
Group by FilmId)
natural left join
(select FilmId, listagg(g.Nom, ',') within group (Order By g.nom) ListGenres from Film
natural join Film_vs_Genre
natural join Genre g
Group by FilmId)
natural left join
(select FilmId, listagg(p.Prenom||' '||p.Nom, ',') within group (Order By p.nom) ListScenaristes from Film
natural join Scenariste s
join Personne p on s.personneId = p.personneId
Group by FilmId)
natural left join
(select FilmId, listagg(p.Prenom||' '||p.Nom, ',') within group (Order By p.nom) ListActeurs from Film
natural join Personnage perso
join Personne p on perso.personneId = p.personneId
Group by FilmId)
left join Personne real on real.personneId = realisateurId;
BEGIN
<<FILM_LOOP>>
FOR film IN films LOOP
subStrArray := apex_util.string_to_table(searchString, ' ');
FOR i in 1..subStrArray.count LOOP
IsValid:= FALSE;
term:= subStrArray(i);
IF REGEXP_LIKE(term, '\(\d{4}\)') THEN
IF film.anneeSortie = TO_NUMBER(regexp_substr(term, '\d{4}')) THEN
IsValid:= TRUE;
END IF;
ELSIF REGEXP_LIKE(term, '\[\d{4},\d{4}\]') THEN
y1:= regexp_substr(term, '\d{4}', 1, 1);
y2:= regexp_substr(term, '\d{4}', 1, 2);
IF film.anneeSortie BETWEEN y1 AND y2 THEN
IsValid:= TRUE;
END IF;
ELSE
IF UPPER(film.Titre||film.Realisateur||film.ListActeurs||film.ListScenaristes||film.ListGenres||film.ListPays||film.langueOrigine)
LIKE '%'||UPPER(term)||'%' THEN
IsValid:= TRUE;
END IF;
END IF;
IF NOT IsValid THEN
CONTINUE FILM_LOOP;
END IF;
END LOOP;
--DBMS_OUTPUT.put_line(film.FilmId||'|'||film.Titre);
insert into movie_results( movie )
values film.FilmId||'|'||film.Titre;
commit;
END LOOP;
open movies for
select *
from movie_results;
END;
Now your parameter 'movies' has all the positive results derived from your procedure, and all you'll have to do is read the cursor in Hibernate.
Note that, once you close your connection, the temporary table looses all the data and will be ready to use again when another session begins (always remember to close your Cursors/Connections).
The first part of this can probably be accomplished with just a query. You can define your search terms like this: How to declare variable and use it in the same SQL script? (Oracle SQL)
I'll leave the basic query writing to you (e.g. joining appropriate data), but your script will in effect look something like this:
DEFINE var_year1 = 1992;
DEFINE var_year2 = 1994;
DEFINE var_country = null;
DEFINE var_title = 'Lord Of The Rings';
DEFINE var_realisator = null;
DEFINE var_genre = null;
DEFINE var_actors = 'Ian';
DEFINE var_scenarists = 'Lee';
SELECT film_id, title, year
FROM ...
WHERE year BETWEEN &var_year1 AND &var_year2
AND UPPER(title) LIKE UPPER('%&var_title%')
AND UPPER(country) LIKE UPPER('%&var_country%')
AND UPPER(realisator) LIKE UPPER('%&var_realisator%')
AND UPPER(genre) LIKE UPPER('%&var_genre%')
AND UPPER(actors) LIKE UPPER('%&var_actors%')
AND UPPER(scenarists) LIKE UPPER('%&var_scenarists%');
/
The reason I only select film_id, title, and year is because the title and year will be distinct values to the film_id. There are a couple of ways to skin this cat, but it all revolves around using a LIKE statement with variables that you have defined. If you do not want to explicitly define the values in the script, you can use the ACCEPT command. Or, you can interface it with something else that applies those values into the variables.
With this approach, it was kind of a shot in the dark on how you really have your data laid out. But keep in mind good relational database management practices, and you should be able to turn this into a useable query.
Additional Method:
You can also use a series of subqueries to find the same data above using the like statements, then INNER JOIN them together to figure out which ones are in common. Example:
WITH sub_title AS (SELECT film_id FROM Film WHERE UPPER(title) LIKE UPPER('%&var_title%')),
...
sub_actor AS (SELECT film_id FROM (...) WHERE UPPER(actor) LIKE UPPER('%&var_actor%'))
SELECT film_id
FROM sub_title t
INNER JOIN sub_actor a ON a.film_id = t.film_id;
/
If you had searched for 'star war' in that, you would have returned for example
1 Star Wars A New Hope
2 Star Wars The Empire Strikes Back
3 Star Wars Return of the Jedi
Then in the second subquery if you had searched 'Donald Glover' you would have gotten
2 Star Wars The Empire Strikes Back
3 Star Wars Return of the Jedi
Then, the INNER JOIN would have given you ID numbers 2 and 3 because those were the only ones where all of the criteria were met. Repeat the process for your other search terms. I hope this helps.

Iterate through cursor and storing the output of the cursor in another table

I am trying to iterate through a cursor which stores the value of the table. I use a FOR Loop to iterate and IF one of the conditions is met, I store the output in another table. I am not sure of the approach I am following and also getting error(ORA-00933: SQL command not ended properly). Stats_Queries is my reference table where I iterate my cursor through. STATS_RESULT_CARD is my output table where I have to store the results. Please help.
DECLARE
CURSOR c1 IS
select Stats_Queries.OBJECTTYPE, Stats_Queries.CATEGORY, Stats_Queries.QUERY
from Stats_Queries;
r1 c1%ROWTYPE;
BEGIN
FOR r1 IN c1 LOOP
If (r1.OBJECTTYPE = 'CARD') THEN
INSERT INTO STATS_RESULTS_CARD (NODETYPENAME, NODEDEFNAME , CARDTYPENAME, PROVISIONSTATUSNAME, STATDATE, CARDCOUNT)
select nt.name, nd.name, ct.name, ps.name, sysdate, count(c.cardid)
from cardtype ct, card c, node n, nodetype nt, nodedef nd, provisionstatus ps
where ct.name in ('SRA AMP', 'XLA AMP', 'SAM', 'ESAM')
and ct.cardtypeid = c.card2cardtype
and c.card2node = n.nodeid
and n.node2nodetype = nt.nodetypeid
and n.node2nodedef = nd.nodedefid
and c.card2provisionstatus = ps.provisionstatusid
group by nt.name, nd.name, ct.name, ps.name
END If;
END LOOP;
END;
As an aside from the answer that Finbarr has provided (which is perfectly correct; add in the missing semi-colon and your procedure should work), why do you need to loop through the cursor at all? That's the slow way of doing it.
You could just do a single insert statement instead, such as:
insert into stats_results_card (nodetypename,
nodedefname,
cardtypename,
provisionstatusname,
statdate,
cardcount)
select x.nt_name,
x.nd_name,
x.ct_name,
x.ps_name,
x.statdate,
x.cnt_cardid
from (select nt.name nt_name,
nd.name nd_name,
ct.name ct_name,
ps.name ps_name,
sysdate statdate,
count (c.cardid) cnt_cardid
from cardtype ct,
card c,
node n,
nodetype nt,
nodedef nd,
provisionstatus ps
where ct.name in ('SRA AMP',
'XLA AMP',
'SAM',
'ESAM')
and ct.cardtypeid = c.card2cardtype
and c.card2node = n.nodeid
and n.node2nodetype = nt.nodetypeid
and n.node2nodedef = nd.nodedefid
and c.card2provisionstatus = ps.provisionstatusid
group by nt.name,
nd.name,
ct.name,
ps.name) x
cross join (select stats_queries.objecttype,
stats_queries.category,
stats_queries.query
from stats_queries
where objecttype = 'CARD');
N.B. This assumes that there really isn't any link between the original cursor and the select statement that was inside the loop; we do a cross join to replicate the rows the required number of times.
If there was an actual join between the two queries, you would put that in place of the cross join.
ORA-00933: SQL command not ended properly
Probably occurring because you missed a semicolon after
group by nt.name, nd.name, ct.name, ps.name

oracle apex shuttle saving deselections

I'm using Apex 4.2.4 and Oracle 11g. I have a mailing list application where I'm maintaining multiple mailing lists. A given recipient may belong to one or more lists. I'm using an Apex Shuttle to maintain the lists.
The source of all mailing recipients is in the table: mail_recipient. There are four important fields in mail_recipient:
prim_key
first_name
last_name
email_address
There is another table that stores the selected recipients for a given mailing list: mail_recipient_category: The important fields in mail_recipient_category are:
prim_key
recipient_fkey (this stores the prim_key from the mail_recipient table)
category
merge_check
There are two displayed items on the page. The category drop down list (P31_email_list) and the shuttle (P31_email_list_assignments)
The LOV for P31_email_list_assignments is:
Select last_name || ', ' || first_name || ' -- ' || email_address, prim_key from mail_recipient
order by 1;
The PL/SQL function body for the shuttle source is:
declare
emp_list apex_application_global.vc_arr2;
i number := 1;
begin
for r in (Select mr.last_name || ', ' || mr.first_name || ' -- ' || mr.email_address, mr.prim_key
From mail_recipient mr left outer join
mail_recipient_category mrc
On mr.prim_key = mrc.recipient_fkey
Where mrc.category = :P31_EMAIL_LIST)
loop
emp_list(i) := r.prim_key;
i := i + 1;
end loop;
return APEX_UTIL.TABLE_TO_STRING(emp_list, ':');
end;
There is also a single page process to update the database table mail_recipient_category. The process is executed on submit after computations and validations.
Begin
MERGE INTO MAIL_RECIPIENT_CATEGORY ss
USING (
Select
shuttle.column_value shuttle_st
, db.recipient_fkey db_st
from
table(string_to_coll(:P31_email_list_assignments)) shuttle
left outer join mail_recipient_category db
on shuttle.column_value = db.recipient_fkey
and db.category = :P31_email_list) t
on (ss.recipient_fkey = t.db_st
and ss.category = :P31_email_list
)
when matched
then
update
set
ss.merge_check = ss.merge_check
delete
where
t.shuttle_st is null and ss.category = :P31_email_list
-- t.shuttle_st is null
when not matched
then
insert
(recipient_fkey, category)
values
(t.shuttle_st, :P31_email_list);
end;
The shuttle works fine to load from left to right and save items. The problem I'm having is deselecting items from the right side to the left side of the shuttle. After moving a a mail recipient from the right to the left side, when I press the submit button, the items don't leave the right side of the shuttle and the process doesn't delete the row from the mail_recipient_category table.
Thanks for your help with this.
After working with a very experienced colleague, we determined that the page process was at fault. Specifically, the Delete clause of the Merge statement doesn't seem to work for this shuttle. We ended up adding a separate Delete statement after the the Merge statement in the page process.
I also dropped the mail_recipient_category table and am now using the more meaningful name mail_recipient_list.
The shuttle field name is (p31_email_list_assignments).
The working shuttle LOV Definition code is:
Select last_name || ', ' || first_name || ' -- ' || email_address, prim_key
From mail_recipient
order by 1;
The working shuttle Source code is:
declare
mail_list apex_application_global.vc_arr2;
i number := 1;
begin
for r in (Select mr.last_name || ', ' || mr.first_name || ' -- ' || mr.email_address, mr.prim_key
From mail_recipient mr left join
mail_recipient_list mrl
On mr.prim_key = mrl.recipient_fkey
Where mrl.list = :P31_EMAIL_LIST)
loop
mail_list(i) := r.prim_key;
i := i + 1;
end loop;
return APEX_UTIL.TABLE_TO_STRING(mail_list, ':');
end;
The working After Submit page process code is:
Begin
Merge Into MAIL_RECIPIENT_LIST mrl
Using (
Select
shuttle.column_value shuttle_cv
, db.recipient_fkey db_rfk
From
table(string_to_coll(:P31_email_list_assignments)) shuttle
left outer join mail_recipient_list db
on shuttle.column_value = db.recipient_fkey
and db.list = :P31_email_list) t
On (mrl.recipient_fkey = t.db_rfk
And mrl.list = :p31_email_list
)
When Matched
Then
Update
Set
mrl.merge_check = mrl.merge_check
-- Delete
-- Where
-- t.shuttle_cv is null
When Not Matched
Then
Insert
(recipient_fkey, list)
Values
(t.shuttle_cv, :P31_email_list);
/* The commented-out delete clause of the Merge statement never worked with this shuttle.
The following delete statement will every time this page process is called
*/
Delete from MAIL_RECIPIENT_LIST
Where instr(':' || :P31_email_list_assignments || ':',':' || recipient_fkey || ':') = 0
And list = :P31_email_list;
end;
I hope someone finds this useful.

Oracle cursor with variables help needed

I am trying to do a cursor which does something like below, struggling with different approaches with no results. Seems, I won't be able to do it by myself, and decided to ask you for help.
Below code shows what I want to achieve rather than ready approach. Please help.
I dont know it it matters but note, that I need to update CUSTOMERS in loop. I also need to select some data from another table referencing customer in this loop, then insert something to third table and update customer table.
DECLARE
CURSOR MY_CURSOR
IS
SELECT CUSTOMERID FROM CUSTOMERS WHERE ACTIVE = 1 ;
MY_RECORD MY_CURSOR%ROWTYPE;
BEGIN
FOR MY_RECORD IN MY_CURSOR
LOOP
DECLARE TEMPORARY_TABLE TABLE (A DATE, B NUMBER, C VARCHAR)
INSERT INTO #TEMPORARY_TABLE(A,B,C) (SELECT CREATEDDATE, ID, NAME FROM ACCOUNT WHERE CUSTOMER = MY_RECORD.CUSTOMERID)
INSERT INTO SOME_EVENT_TABLE(ID, NAME, DATE, ACCOUNT_ID) VALUE (some_seq.NEXTVAL, #TEMPORARY_TABLE[C], #TEMPORARY_TABLE[A], #TEMPORARY_TABLE[B])
UPDATE CUSTOMERS SET LAST_ACCOUNT_CHECK_NAME=#TEMPORARY_TABLE(C), LAST_INSERTED_EVENT_ID = some_seq.CURRVAL WHERE ID = MY_RECORD.CUSTOMERID
END LOOP;
COMMIT;
END;
First, you can't declare a temporary table in Oracle like you do in SQL Server. However, you really don't need it here anyway.
Something like this should work:
FOR MY_RECORD IN MY_CURSOR LOOP
FOR R IN (SELECT CREATEDDATE, ID, NAME
FROM ACCOUNT WHERE CUSTOMER = MY_RECORD.CUSTOMERID) LOOP
INSERT INTO some_event_table(ID, NAME, DATE, ACCOUNT_ID)
VALUES (some_seq.NEXTVAL, R.NAME, R.CREATEDATE, R.ID);
UPDATE customers
SET last_account_check_name = R.name
, last_inserted_event_id = some_seq.CURRVAL
WHERE id = MY_RECORD.CUSTOMER_ID;
END LOOP;
END LOOP;
COMMIT;
Row by row actions in SQL are terribly inefficient. You will get vastly better performance if you do this in a set-based way.
INSERT INTO some_event_table(ID, NAME, DATE, ACCOUNT_ID)
SELECT some_seq.NEXTVAL, a.name, a.createdate, a.id
FROM ACCOUNT a
INNER JOIN customers c ON c.customerid = a.customerid
WHERE c.active = 1;
UPDATE customers
SET last_account_check_name =
( SELECT a.name FROM account a WHERE a.customerid = c.customerid ),
last_inserted_event_id = some_seq.CURRVAL
WHERE c.active = 1;
There may be concurrency issues with that (what happens if customers is updated between the two statements?), but that might be good enough for your needs.

Binding variables in dynamic PL/SQL

I have a dynamic PL/SQL that will construct the SELECT statement based on what the searching criteria input from the users,likes:
l_sql := 'SELECT * INTO FROM TABLEA WHERE 1=1 ';
IF in_param1 IS NOT NULL THEN
l_sql := l_sql || 'AND column1 = in_param1 ';
END IF;
IF in_param2 IS NOT NULL THEN
l_sql := l_sql || 'AND column2 = in_param2 ';
END IF;
...................................
IF in_paramXX IS NOT NULL THEN
l_sql := l_sql || 'AND columnXX = in_paramXX ';
END IF;
To reduce the hard parse overhead , I consider to use the binding variables. However , it is difficult to manage when supplying the actual values to the binding variables as there are so many binding variables and combination of the generated SELECT statement . I cannot use the method of DBMS_SESSION.set_context() introduced at http://www.dba-oracle.com/plsql/t_plsql_dynamic_binds.htm because my account has no right to use this package. Besides , I want the generated SQL only contains the conditions on the fields that the user did not leave empty. So I cannot change the dynamic SQL to something likes
SELECT * INTO FROM TABLEA WHERE 1=1
and ( in_param1 is NULL or column1 = in_param1)
and ( in_param2 is NULL or column2 = in_param2)
...............................................
and ( in_paramXX is NULL or columnXX = in_paramXX)
So , I want to try to use the DBMS_SQL method .Can anyone give an example about how to use DBMS_SQL to call dynamic SQL with binding variables? Especially , how can I get the result executed from DBMS_SQL.execute() to the SYS_REFCURSOR , something like :
open refcursor for select .... from
The oracle version that I use is 10g and it seems that the oracle 10g does not have DBMS_Sql.To_Refcursor()
In your Oracle version you can apply some tricks to your query in order to do this. The idea is to use a query in the following form:
select *
from
(select
:possibleParam1 as param1
-- do the same for every possible param in your query
:possibleParamN as paramN
from dual
where rownum > 0) params
inner join
-- join your tables here
on
-- concatenate your filters here
where
-- fixed conditions
then execute it with:
open c for query using param1, ..., paramN;
It works by using DUAL to generate a fake row with every single param, then inner joining this fake row to your real query (without any filters) using only the filters you want to apply. This way, you have a fixed list of bind variables in the SELECT list of the params subquery, but can control which filters are applied by modifying the join condition between params and your real query.
So, if you have something like, say:
create table people (
first_name varchar2(20)
last_name varchar2(20)
);
you can construct the following query if you just want to filter on first name
select *
from
(select
:first_name as first_name,
:last_name as last_name
from dual
where rownum > 0) params
inner join
people
on
people.first_name = params.first_name;
and this if you want to filter on both first_name and last_name
select *
from
(select
:first_name as first_name,
:last_name as last_name
from dual
where rownum > 0) params
inner join
people
on
people.first_name = params.first_name and
people.last_name = params.last_name;
and in every case you would execute with
open c for query using filterFirstName, filterLastName;
It is important for performance to use the where rownum > 0 with DUAL as it forces Oracle to "materialize" the subquery. This usually makes DUAL stop interfering with the rest of the query. Anyway, you should check the execution plans to be sure Oracle is not doing anything wrong.
In 10g a DBMS_SQL cursor can't be changed into a Ref Cursor. Going through a result set through DBMS_SQL is tortuous since, as well as looping through the rows, you also have to loop through the columns in a row.
I want the generated SQL only contains
the conditions on the fields that the
user did not leave empty
Is that purely for performance reasons ? If so, I suggest you work out what the practical execution plans are and use separate queries for them.
For example, say I'm searching on people and the parameters are first_name, last_name. gender, date_of_birth. The table has indexes on (last_name,first_name) and (date_of_birth), so I only want to allow a query if it specifies either last_name or date_of_birth.
IF :p_firstname IS NOT NULL and :p_lastname IS NOT NULL THEN
OPEN cur FOR
'SELECT * FROM PEOPLE WHERE last_name=:a AND first_name=:b AND
(date_of_birth = :c or :c is NULL) AND (gender = :d or :d IS NULL)' USING ....
ELSIF :p_lastname IS NOT NULL THEN
OPEN cur FOR
'SELECT * FROM PEOPLE WHERE last_name=:a AND
(date_of_birth = :c or :c is NULL) AND (gender = :d or :d IS NULL)' USING ....
ELSIF :p_dateofbirth IS NOT NULL THEN
OPEN cur FOR
'SELECT * FROM PEOPLE WHERE date_of_birth=:a AND
(first_name=:b OR :b IS NULL) AND (gender = :d or :d IS NULL)' USING ....
ELSE
RAISE_APPLICATION_ERROR(-20001,'Last Name or Date of Birth MUST be supplied);
END IF;

Resources