oracle apex shuttle saving deselections - oracle

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.

Related

PL SQL FUNCTION BODY RETURN SQL QUERY GETS ERROR: ORA-20001: Query must begin with SELECT or WITH

I get an ORA-20001: Query must begin with SELECT or WITH when I verify the code below. Does that mean I can't use FOR record (sql query) and a loop? Is it ok to load APEX card info the way I did in the loop? Any help is appreciated.
My goal is to display all media for a project and make the icon turn green for MEDIA that is referenced in a specific STEP_MEDIA row. I have tried outer joins, but I get duplicates. If there is a better way please let me know.
Oracle Cloud
Application Express 21.1.7
Database Version: 19c
return q'~
DECLARE
n_count number;
BEGIN
FOR record in (SELECT *
FROM MEDIA
where KHN_PROJECT_ID = :P300_PROJECT_ID
ORDER BY FILE_NAME)
LOOP
CARD_TITLE := record.FILE_NAME;
CARD_SUBTITLE := record.FILE_MIMETYPE;
CARD_LINK := apex_util.prepare_url( '#' );
CASE WHEN record.FILE_MIMETYPE LIKE 'video%'
THEN
CARD_TEXT := '<video> ...</video>';
ELSE
CARD_TEXT := '<img> ...</img>';
END CASE;
CARD_ICON := 'fa-check';
SELECT COUNT(STEP_MEDIA_ID) INTO n_count
FROM STEP_MEDIA
WHERE MEDIA_ID = record.MEDIA_ID AND STEP_ID = :P300_STEP_ID_LOADED;
IF ncount = 0
THEN
CARD_COLOR := 'u-success';
ELSE
CARD_COLOR := 'u-normal';
END IF;
CARD_SUBTEXT := '<button class="t-Button t-Button--noLabel t-Button--icon add-favorite" id="fav_'||record.MEDIA_ID||'" type="button">Select</span></button>';
END LOOP;
END;
~';
Here is the code I wrote using an outer join. It produces duplicate rows.
select m.FILE_NAME AS CARD_TITLE,
m.FILE_MIMETYPE AS CARD_SUBTITLE,
m.MEDIA_ID,
apex_util.prepare_url( '#' ) CARD_LINK,
CASE WHEN m.FILE_MIMETYPE LIKE 'video%'
THEN
'<video >...</video>'
ELSE
'<img >...</img>'
END
CARD_TEXT,
'fa-check' CARD_ICON,
CASE WHEN sm.step_media_id is NULL
THEN
'u-success'
ELSE
'u-normal'
END
CARD_COLOR,
from MEDIA m
LEFT OUTER JOIN STEP_MEDIA sm
ON m.MEDIA_ID = sm.MEDIA_ID
where m.KHN_PROJECT_ID = :P300_PROJECT_ID OR sm.STEP_ID = :P300_STEP_ID_LOADED
ORDER BY m.FILE_NAME
This is Apex, right?
Errors with codes from -20000 to -20999 are user-defined. In this case, Apex developers created it (so it's not one of built-in Oracle errors).
What does it say? Looks like you want to "dynamically" create a Classic report with the Cards layout. You're supposed to return a valid query, and it begins with a select or the with keyword (if you're using a with factoring clause, i.e. a CTE (common table expression)).
There's no evidence that code you posted returns what I said.
From my point of view, you shouldn't be doing it in a loop anyway. Everything can be done with a single query (in your case, that would be a join of two tables) that uses CASE expressions to set certain CARD_... values.
That query would then be Classic report's source.
This query uses sql which is compatible with Oracle Apex and displays all media and shows which media is used in a step. The subquery was the solution.
select DISTINCT m.FILE_NAME AS CARD_TITLE,
m.FILE_MIMETYPE AS CARD_SUBTITLE,
m.TAGS AS TAGS,
m.MEDIA_ID,
apex_util.prepare_url( '#' ) CARD_LINK,
CASE WHEN m.FILE_MIMETYPE LIKE 'video%'
THEN
'<video controls width = "100%" height= "100%" class="object-fit">... </video>'
ELSE
'<img >...</img>'
END
CARD_TEXT,
'fa-check' CARD_ICON,
CASE WHEN sm.step_media_id is NULL
THEN
'u-normal'
ELSE
'u-success'
END
CARD_COLOR,
'<button class="t-Button t-Button--noLabel t-Button--icon add-favorite" id="fav_'||m.MEDIA_ID||'" type="button">Select</span></button>' CARD_SUBTEXT
from MEDIA m
LEFT OUTER JOIN (select * FROM STEP_MEDIA WHERE STEP_ID = :P300_STEP_ID_LOADED) sm
ON m.MEDIA_ID = sm.MEDIA_ID
where m.KHN_PROJECT_ID = :P300_PROJECT_ID
ORDER BY m.FILE_NAME

Oracle APEX select values into classic report rows

I'm working on an Oracle database-driven web app using apex.There is a P2_ROWS field which contains a list of values selected from the data table, defined in the source sql query. There is a button on the report region, which allows users to sort the list of values in a certain order. A dynamic action is assigned to the button. When the button is clicked, a PL/SQL is executed with a order by query that should change the order in which the rows are displayed.
The source sql is defined as:
select
"PRODUCT_ID",
"PRODUCT_NAME",
"PRODUCT_DESCRIPTION",
"PRICE",
"PRODUCT_LOCATION",
dbms_lob.getlength("THUMBNAIL") as "THUMBNAIL"
from "PRODUCTS"
where
(
instr(upper("PRODUCT_NAME"), upper(nvl(:P2_REPORT_SEARCH, "PRODUCT_NAME"))) > 0
)
The PL/SQL assigned to the button is:
SELECT PRODUCTS.PRODUCT_ID, PRODUCTS.PRODUCT_NAME, PRODUCTS.PRODUCT_DESCRIPTION, PRODUCTS.PRICE, PRODUCTS.PRODUCT_LOCATION, dbms_lob.getlength("THUMBNAIL") as "THUMBNAIL"
INTO :P2_ROWS
FROM PRODUCTS
INNER JOIN TEMP_DISTANCES ON TEMP_DISTANCES.PRODUCT_ID = PRODUCTS.PRODUCT_ID
ORDER BY DISTANCE ASC;
When the button is clicked, the app returns error " ORA-00947: not enough values ORA-06550".
How would you solve this? Please feel free to comment and feel free to suggest any improvement ideas. Thanks.
As #Scott say you trying select many values into one scalar variable. You should define few variables or use concatenation.
SELECT PRODUCTS.PRODUCT_ID, PRODUCTS.PRODUCT_NAME, PRODUCTS.PRODUCT_DESCRIPTION, PRODUCTS.PRICE, PRODUCTS.PRODUCT_LOCATION, dbms_lob.getlength("THUMBNAIL") as "THUMBNAIL"
INTO :P2_PROID_ID, :P2_PROD_NAME, ...
FROM PRODUCTS
INNER JOIN TEMP_DISTANCES
ON TEMP_DISTANCES.PRODUCT_ID = PRODUCTS.PRODUCT_ID
ORDER BY DISTANCE ASC;
or
SELECT PRODUCTS.PRODUCT_ID || ', ' || PRODUCTS.PRODUCT_NAME || ', ' || PRODUCTS.PRODUCT_DESCRIPTION || ', ' || PRODUCTS.PRICE ...
INTO :P2_ROWS
FROM PRODUCTS
INNER JOIN TEMP_DISTANCES
ON TEMP_DISTANCES.PRODUCT_ID = PRODUCTS.PRODUCT_ID
ORDER BY DISTANCE ASC;

Cursor giving NO_DATA_FOUND if column with empty value included

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;

For each row of one table, count entries in another table pointing to each of those rows in Oracle

Not sure the title explains the problem well; this is what I'm working with,
I have the following tables,
-- table = kms_doc_ref_currnt_v
DOC_ID VARCHAR2(19)
TO_DOC_ID VARCHAR2(19)
BRANCH_ID NUMBER(8)
REF_TYP_CD VARCHAR2(20)
-- table = kms_fil_nm_t
DOC_ID VARCHAR2(19) PRIMARY KEY UNIQUE
For example, I can get a count of all kms_doc_ref_currnt_v records that have a to_doc_id = 59678, where 59678 is one value in kms_fil_nm_t, with this query,
select 'doc_id 59678 has ' || count(to_doc_id) as cnt from kms_doc_ref_currnt_v where branch_id=1 and ref_typ_cd in ('CONREF', 'KBA') and to_doc_id=59678;
kms_doc_ref_currnt_v.to_doc_id is a field that has one of the kms_fil_nm_t.doc_id values. kms_doc_ref_currnt_v.doc_id is also one of the values in kms_fil_nm_t.
The single query I'm looking for would loop over each kms_fil_nm_t.doc_id and count all the rows in kms_doc_ref_currnt_v that have a similar to_doc_id. Each row returned would look like the output of the query above. Here's example output,
doc_id 1 has 32
doc_id 2 has 314
doc_id 3 has 2718
doc_id 4 has 42
doc_id 5 has 128
doc_id 6 has 11235
.
.
.
Probably simple but I just can't figure it out.
Do a join with two tables and add a GROUP BY clause as below:
SELECT 'doc_id 59678 has ' || count(to_doc_id) as cnt
FROM kms_doc_ref_currnt_v kv, kms_fil_nm_t kt
WHERE kt.doc_id= kv.to_doc_id
AND kv.branch_id=1
AND kv.ref_typ_cd in ('CONREF', 'KBA')
AND kv.to_doc_id=59678
GROUP BY kv.to_doc_id;
EDIT:
To get all records from kms_doc_ref_currnt_v irrespective of their reference availability in kms_fil_nm_t and kv.to_doc_id=59678, do like this:
SELECT 'doc_id 59678 has ' || count(to_doc_id) as cnt
FROM kms_doc_ref_currnt_v kv
LEFT JOIN kms_fil_nm_t kt
ON (kt.doc_id= kv.to_doc_id )
WHERE kv.branch_id=1
AND kv.ref_typ_cd in ('CONREF', 'KBA')
GROUP BY kv.to_doc_id;
to replace the hardcoding 59678, you may want to write:
SELECT 'doc_id ' || kt.doc_id || ` has ' || count(to_doc_id) as cnt
FROM kms_doc_ref_currnt_v kv
LEFT JOIN kms_fil_nm_t kt
ON (kt.doc_id= kv.to_doc_id )
WHERE kv.branch_id=1
AND kv.ref_typ_cd in ('CONREF', 'KBA')
GROUP BY kv.to_doc_id, kt.doc_id;
You need to use an outer join between the driving table that has all the doc_id values and the dependent table that may or may not have matching entries; and a group by clause to define what your aggregate function (count()) is operating against. Something like:
select 'doc_id ' || t.doc_id || ' has ' || count(*)
from kms_fil_nm_t t
left join kms_doc_ref_currnt_v v
on v.to_doc_id = t.doc_id
and v.branch_id = 1
and v.ref_typ_cd in ('CONREF', 'KBA')
group by t.doc_id;
This assumes you want to know when a doc_id isn't used, so you want entries like doc_id 1234 has 0. If you don't want to see those then you could use an inner join instead of an outer - essentially just remove the word left - but if that is the case then you don't really need to join at all, you could just do:
select 'doc_id ' || v.to_doc_id || ' has ' || count(*)
from kms_doc_ref_currnt_v v
where v.branch_id = 1
and v.ref_typ_cd in ('CONREF', 'KBA')
group by v.to_doc_id;
Unless there are to_doc_id values which are not in the other table, which would be included in the results of this query, but excluded if the tables were joined.

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