Ref Cursor Exceptions - oracle

I have a couple of questions arounbd ref_cursors. Below is a ref_cursor that returns a a single row to a Unix calling script based on what is passed in and although the select looks a little untidy, it works as expected.
My first question is that in the select I join to a lookup table to retrieve a single lookup value 'trigram' and on testing found that this join will occasionally fail as no value exists. I have tried to capture this with no_data_found and when others exception but this does not appear to be working.
Ideally if the join fails I would still like to return the values to the ref_cursor but add something like 'No Trigram' into the trigram field - primarily I want to capture exception.
My second question is more general about ref_cursors - While initially I have created this in its own procedure, it is likely to get called by the main processing procedure a number of times, one of the conditions requires a seperate select but the procedure would only ever return one ref_cur when called, can the procdure ref_cur out be associated with 2 queries.
CREATE OR REPLACE PROCEDURE OPC_OP.SiteZone_status
(in_site_id IN AW_ACTIVE_ALARMS.site_id%TYPE
,in_zone_id IN AW_ACTIVE_ALARMS.zone_id%TYPE
,in_mod IN AW_ACTIVE_ALARMS.module%TYPE
,p_ResultSet OUT TYPES.cursorType
)
AS
BEGIN
OPEN p_ResultSet FOR
SELECT a.site_id,'~',a.zone_id,'~',b.trigram,'~',a.module,'~',a.message_txt,'~',a.time_stamp
FROM AW_ACTIVE_ALARMS a, AW_TRIGRAM_LOCATION b
WHERE a.site_id = b.site_id
AND a.zone_id = b.zone_id
AND a.site_id = in_site_id
AND a.zone_id = in_zone_id
AND a.module LIKE substr(in_mod,1,3)||'%'
AND weight = (select max(weight) from AW_ACTIVE_ALARMS c
WHERE c.site_id = in_site_id
AND c.zone_id = in_zone_id
AND c.module LIKE substr(in_mod,1,3)||'%');
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.PUT_LINE('No Data Found');
END SiteZone_status;
I have modified my code to adopt answers provided and this now works as expected as a standalone procedure within my package, which when called via a UNIX script using:
v_process_alarm=$(sqlplus -s user/pass <
set colsep ','
set linesize 500
set pages 0 feedback off;
set serveroutput on;
VARIABLE resultSet REFCURSOR
EXEC alarm_pkg.rtn_active_alarm($site,$zone,$module, :resultSet);
PRINT :resultSet
EOF
)
However the procedure returning the ref cursor is to be called from the main processing procedure as I only want to return values if certain criteria are met. I have add an out refcurosr to my main procedure and set a variable to match, I then call my ref cursor procedure from here but this fails to compile
with the message 'Wrong number or types of argument in call'
My question is what is the correct way to call a procedure that has out refcursor from within a procedure and then return these values from there back to the calling script.

Oracle doesn't know whether a query will return rows until you fetch from the cursor. And it is not an error for a query to return 0 rows. So you will never get a no_data_found exception from opening a cursor. You'll only get that if you do something like a select into a local variable in which case a query that returns either 0 or more than 1 row is an error.
It sounds like you want to do an outer join to the AW_TRIGRAM_LOCATION table rather than a current inner join. This will return data from the other tables even if there is no matching row in aw_trigram_location. That would look something like this (I have no idea why every other column is a hard-coded tilde character, that seems exceptionally odd)
SELECT a.site_id,'~',
a.zone_id,'~',
nvl(b.trigram, 'No Trigram Found'),'~',
a.module,'~',
a.message_txt,'~',
a.time_stamp
FROM AW_ACTIVE_ALARMS a
LEFT OUTER JOIN AW_TRIGRAM_LOCATION b
ON( a.site_id = b.site_id AND
a.zone_id = b.zone_id )
WHERE a.site_id = in_site_id
AND a.zone_id = in_zone_id
AND a.module LIKE substr(in_mod,1,3)||'%'
AND weight = (select max(weight)
from AW_ACTIVE_ALARMS c
WHERE c.site_id = in_site_id
AND c.zone_id = in_zone_id
AND c.module LIKE substr(in_mod,1,3)||'%');
I'm not quite sure that I understand your last question. You can certainly put logic in your procedure to run a different query depending on an input parameter. Something like
IF( <<some condition>> )
THEN
OPEN p_ResultSet FOR <<query 1>>
ELSE
OPEN p_ResultSet FOR <<query 2>>
END IF;
Whether it makes sense to do this rather than adding additional predicates or creating separate procedures is a question you'd have to answer.

You can use a left outer join to your look-up table, which is clearer if you use ANSI join syntax rather than Oracle's old syntax. If there is no record in AW_TRIGRAM_LOCATION then b.trigram will be null, and you can then use NVL to assign a dummy value:
OPEN p_ResultSet FOR
SELECT a.site_id,'~',a.zone_id,'~',NVL(b.trigram, 'No Trigram'),'~',
a.module,'~',a.message_txt,'~',a.time_stamp
FROM AW_ACTIVE_ALARMS a
LEFT JOIN AW_TRIGRAM_LOCATION b
ON b.site_id = a.site_id
AND b.zone_id = a.zone_id
WHERE a.zone_id = in_zone_id
AND a.module LIKE substr(in_mod,1,3)||'%'
AND weight = (select max(weight) from AW_ACTIVE_ALARMS c
WHERE c.site_id = in_site_id
AND c.zone_id = in_zone_id
AND c.module LIKE substr(in_mod,1,3)||'%');
You won't get NO_DATA_FOUND opening a cursor, only when you fetch from it (depending on what is actually consuming this). It's a bad idea to catch WHEN OTHERS anyway - you would want to catch WHEN NO_DATA_FOUND, though it wouldn't help here. And using dbms_output to report an error relies on the client enabling its display, which you can't generally assume.

Related

getting error of character string buffer too small even after changing to varchar(32000)

getting error QRA-06502 of character string buffer too small even after changing to varchar(32000)
create or replace function product_purchase(CUSTOMER_COD VARCHAR2) return varchar is
CODE VARCHAR2(32000);
BEGIN
CODE := ' ';
FOR PRODUCTS_INFO IN (SELECT CUSTOMER_CODE,PRODUCT_NAME
FROM ORDER_DETAIL JOIN ORDERS
ON ORDER_DETAIL.ORDER_ID = ORDERS.ORDER_ID)
LOOP
CODE := CODE || PRODUCTS_INFO.CUSTOMER_CODE||PRODUCTS_INFO.PRODUCT_NAME ||',';
END LOOP;
RETURN CODE;
END product_purchase;
/
ERROR MAINLY OCCURS WHILE SELECTING DATA AND PUTTING IT IN THE FUNCTION
SELECT CUSTOMER_CODE ,product_purchase(CUSTOMER_CODE),COUNT(PRODUCT_NAME)
FROM ORDER_DETAIL JOIN ORDERS
ON ORDER_DETAIL.ORDER_ID = ORDERS.ORDER_ID
GROUP BY CUSTOMER_CODE,product_purchase(CUSTOMER_CODE)
HAVING COUNT(PRODUCT_NAME)=3;
Even after changing to varchar2(32000)? Well, that doesn't guarantee that you'll get the result (obviously).
The way I see it, it is cursor query that is wrong. If you're calling a function as
product_purchase(CUSTOMER_CODE)
it means that you're passing a parameter to it (you didn't post the whole function code so I'll presume that parameter's name is par_customer_code; note that it shouldn't be just customer_code as it is equal to column name and you'll get unexpected result once you use the parameter).
Exactly - use the parameter. Code you wrote doesn't use it. Should've been
SELECT CUSTOMER_CODE,PRODUCT_NAME
FROM ORDER_DETAIL JOIN ORDERS
ON ORDER_DETAIL.ORDER_ID = ORDERS.ORDER_ID
WHERE customer_code = par_customer_code --> this
I guess that - applying that change - you won't have problems.
However, if you do, then check whether code you wrote actually works, run the cursor query itself and see how many rows it returns. Then, restrict number of rows. How? A simple way is to use rownum:
SELECT CUSTOMER_CODE,PRODUCT_NAME
FROM ORDER_DETAIL JOIN ORDERS
ON ORDER_DETAIL.ORDER_ID = ORDERS.ORDER_ID
WHERE customer_code = par_customer_code
AND rownum <= 10; --> this
and run your code again.
If it works, and if it turns out that varchar2 isn't capable of storing that much data, use CLOB datatype instead:
DECLARE
CODE CLOB; --> this

Converting function from Oracle PL/SQL to MS SQL Server 2008

I have several Oracle functions that are similar to the one below. I don't know much about Oracle and although I have made in roads on a major query re-write. I'd like to ask for some help on how to convert this function to SQL Server 2008.
I have tried using the online conversion tool at www.sqlines.com and benefited from many pages there... but not successful in converting this function....
Thanks in advance, John
Oracle source:
function OfficeIDMainPhoneID(p_ID t_OfficeID)
return t_OfficePhoneID
is
wPhID t_OfficePhoneID;
wPhID1 t_OfficePhoneID;
cursor cr_phone
is
select Office_PHONE_ID,IS_PHONE_PRIMARY
from Office_PHONE
where Office_ID = p_ID
order by SEQ_NUMBER;
begin
wPhID :=NULL;
wPhID1:=NULL;
for wp in cr_phone
loop
if wPhID is NULL
then wPhID1:=wp.Office_PHONE_ID;
end if;
if wp.IS_PHONE_PRIMARY = 'Y'
then
wPhID:=wp.Office_PHONE_ID;
Exit;
end if;
end loop;
if wPhID is NULL
then wPhID:=wPhID1;
end if;
return(wPhID);
end OfficeIDMainPhoneID;
SQL Server attempt:
create function OfficeIDMainPhoneID(#p_ID t_OfficeID)
returns t_OfficePhoneID
as
begin
declare #wPhID t_OfficePhoneID;
declare #wPhID1 t_OfficePhoneID;
declare cr_phone cursor local
for
select Office_PHONE_ID,IS_PHONE_PRIMARY
from Office_PHONE
where Office_ID = #p_ID
order by SEQ_NUMBER;
set #wPhID =NULL;
set #wPhID1=NULL;
declare wp cursor for cr_phone
open wp;
fetch wp into;
while ##fetch_status=0
begin
if #wPhID is NULL
begin set #wPhID1=wp.Office_PHONE_ID;
end
if wp.IS_PHONE_PRIMARY = 'Y'
begin
set #wPhID=wp.Office_PHONE_ID;
Exit;
end
fetch wp into;
end;
close wp;
deallocate wp;
if #wPhID is NULL
begin set #wPhID=#wPhID1;
end
return(#wPhID);
end ;
To answer the question about the functions as written
If you just want to fix the cursor so it works, one problem is the two "fetch wp into;" statements. You are saying "fetch the data and put it into" and then not giving it anything to put it into. Declare a couple of variables, put the data into them, then later use the variables, not the code. You need one variable per item returned in your cursor definition, so one each for Office_PHONE_ID and IS_PHONE_PRIMARY.
Also, you are trying to declare variables (and the function) as t_OfficePhoneID, I suspect that should be something like INT OR BIGINT instead (whatever the table definition for the column is).
Declare #OP_ID INT, #ISPRIMARY CHAR(1) --Or whatever the column is
later (in two locations),
fetch wp into (#OP_ID, #ISPRIMARY);
then use #OP_ID instead of wp.Office_PHONE_ID, and so on.
HOWEVER, I would throw away all the code in the function after declaring #wPhID, and do something else. Cursors suck if you can get what you want with a simple set based request. If you work your way through the oracle code, it is doing the following:
Get the id of the first phone number marked primary (in sequence order). If it didn't find one of those, just get the id of the first non-primary phone number in sequence order. You can do that with the following
set #wPhID = select TOP 1 Office_PHONE_ID
from Office_PHONE
where Office_ID = #p_ID
order by CASE WHEN IS_PHONE_PRIMARY = 'Y' THEN 0 ELSE 1 END, SEQ_NUMBER;
Return #wPhID and you're done.
I used "CASE WHEN IS_PHONE_PRIMARY = 'Y' THEN 0 ELSE 1 END" in the order by because I don't know what other values are possible, so this will always work. If you know the only possible values are 'Y' and 'N', you could use something like the following instead
order by IS_PHONE_PRIMARY DESC, SEQ_NUMBER;

Issue while executing stored procedure which consists both update and insert statements

I am new to PLSQL and I am trying to execute this stored procedure shown here.
This stored procedure will check for a particular row and based on the count update the table or insert. But I am getting below errors as a whole.
31/18 PL/SQL: ORA-00928: missing SELECT keyword
31/1 PL/SQL: SQL Statement ignored
37/26 PL/SQL: ORA-00933: SQL command not properly ended
36/1 PL/SQL: SQL Statement ignored
I tried my best to solve them. Could you please help in solving the issue?
This is the procedure I have written for this task:
CREATE OR REPLACE PROCEDURE LPR_LP_TEST.SP_PTMS_NOTES
(
p_app_lse_s IN mjl.app_lse_s%TYPE,
p_dt_ent_s IN mjl.dt_ent_s%TYPE,
p_note_type_s IN mjl.note_type_s%TYPE,
p_prcs_c IN mjl.prcs_c%TYPE,
p_prio_c IN mjl.prio_c%TYPE,
p_note_title_s IN mjl.note_title_s%TYPE,
p_info1_s IN mjl.info1_s%TYPE,
p_info2_s IN mjl.info2_s%TYPE
)
AS
v_rowcount_i number;
v_lien_date mjl.info1_s%TYPE;
--v_lien_date NMAC_PTMS_NOTEBK_SG.LIEN_DT%TYPE;
v_asst_amount mjl.info2_s%TYPE;
BEGIN
app_lse_s:=trim(app_lse_s);
dbms_output.put_line(app_lse_s);
select LIEN_DT,ASES_PRT_1_AM
INTO v_lien_date,v_asst_amount
from NMAC_PTMS_NOTEBK_SG
where LSE_ID ='&2';
select count(*) into v_rowcount_i from MJL where trim(app_lse_s) ='&2';
if v_rowcount_i = 0 then
begin
Insert into MJL
('app_lse_s','dt_ent_s','note_type_s','prcs_c','prio_c','note_title_s','info
1_s','Info2_s')
values ('&2','sysdate','SPPT','Y','1','Property Tax
Assessment','v_lien_date','v_asst_amount');
end;
else
begin
update mjl
set dt_ent_s = 'sysdate' and note_type_s = 'SPPT' and prcs_c = 'Y' and
prio_c = '1' and note_title_s = 'Property Tax Assessment' and info1_s =
'v_lien_date' and Info2_s = 'v_asst_amount'
where trim(app_lse_s) = '&2';
end;
end if;
commit;
end;
/
I believe your procedure should look something like:
CREATE OR REPLACE PROCEDURE LPR_LP_TEST.SP_PTMS_NOTES
(
p_app_lse_s IN mjl.app_lse_s%TYPE,
p_dt_ent_s IN mjl.dt_ent_s%TYPE,
p_note_type_s IN mjl.note_type_s%TYPE,
p_prcs_c IN mjl.prcs_c%TYPE,
p_prio_c IN mjl.prio_c%TYPE,
p_note_title_s IN mjl.note_title_s%TYPE,
p_info1_s IN mjl.info1_s%TYPE,
p_info2_s IN mjl.info2_s%TYPE
)
AS
v_rowcount_i number;
v_lien_date mjl.info1_s%TYPE;
--v_lien_date NMAC_PTMS_NOTEBK_SG.LIEN_DT%TYPE;
v_asst_amount mjl.info2_s%TYPE;
v_app_lse_s mjl.app_lse_s%TYPE;
BEGIN
v_app_lse_s := trim(p_app_lse_s);
-- I hope this dbms_output line is for temporary debug purposes only
-- and will be removed in the production version!
dbms_output.put_line(app_lse_s);
merge into mjl tgt
using (select lse_s app_lse_s,
sysdate dt_ent_s,
'SPPT' note_type_s,
'Y' prcs_c,
'1' prio_c,
'Property Tax Assessment' note_title_s,
lien_dt info1_s,
ases_prt_1_am info2_s
from nmac_ptms_notebk_sg
where lse_id = v_app_lse_s) src
on (tgt.app_lse_s = src.app_lse_s)
when matched then
update set tgt.dt_ent_s = src.dt_ent_s,
tgt.note_title_s = src.note_title_s,
tgt.info1_s = src.info1_s,
tgt.info2_s = src.info2_s
where tgt.dt_end_s != src.dt_ent_s
or tgt.note_title_s != src.note_title_s
or tgt.info1_s != src.info1_s
or tgt.info2_s != src.info2_s
when not matched then
insert (tgt.app_lse_s,
tgt.dt_ent_s,
tgt.note_type_s,
tgt.prcs_c,
tgt.prio_c,
tgt.note_title_s,
tgt.info1_s,
tgt.info2_s)
values (src.app_lse_s,
src.dt_ent_s,
src.note_type_s,
src.prcs_c,
src.prio_c,
src.note_title_s,
src.info1_s,
src.info2_s);
commit;
end;
/
Things for you to note about your procedure and what I did to come up with the above procedure:
You have a tendency to enclose everything in single quotes. Single quotes are used to declare something as a string, i.e. some_variable := 'string value'. If you put single quotes around something that is actually an identifier, you are really telling Oracle to treat it as a string - which will result in all sorts of errors! The only time you should use quotes around an identifier is when the identifier's name is case sensitive, and you should use double-quotes. E.g. select * from "lower_case_tablename". (N.B. I say "should" here, but that's a guideline; you can use double-quotes around non-case-sensitive identifier names, but if you do so, the name should be in uppercase - i.e. select * from "DUAL";).
Your update statement syntax was incorrect - updates to several columns in a single statement are separated by commas, not ands.
The begins and ends around your insert and update statements are unnecessary.
If you're going to have an implicit cursor (i.e. the select ... into <variable list> from ... in the procedure body), you need to make sure you handle the NO_DATA_FOUND and TOO_MANY_ROWS exceptions that might be thrown up.
I set up a variable to store the trimmed value passed in by the parameter p_app_lse_s - I assume that this is what you meant to do? I also replaced all the calls to '&2' with the variable.
If you need to do an upsert (i.e. insert if the row doesn't already exist, otherwise update) then consider a MERGE statement. If you absolutely must keep them separate, then don't check for the existence of the row first; do the insert first and check for a DUP_VAL_ON_INDEX error - then do the update in the exception handler. Alternatively, do the update first and check SQL%ROWCOUNT to see if rows were amended and if not, then do the insert. A MERGE is preferable, though, since it means there's no opportunity for someone to insert a row in a different session in the split second it takes the database to go between the two statements.
By using a MERGE statement, I was able to incorporate all your logic into a single SQL statement, which makes your procedure simpler and easier to debug. For a start, I'm betting the other parameters in your procedure need to be used inside the procedure; it's easy to update the source query in the merge statement to replace the hardcoded values with the parameter names! I'll leave that as an exercise for you to do.
If you're getting the info1_s and info2_s values from the nmac_ptms_notebk_sg, do you really need the p_info1_s and p_info2_s parameters? They wouldn't seem to be needed, IMHO.
Finally, this procedure is doing the work a single app_lse_s at a time. If your database processing is OLTP, that's fine. If it's doing batch processing, and your code looks something like the following pseudo-code:
for each row in <this cursor>
loop
execute the lpr_lp_test.sp_ptms_notes procedure
end loop
then you'd be better off merging the sp_ptms_notes procedure into the calling procedure and doing the work in a single MERGE statement.
ETA: If you have a staging table (which could be an external table or a Global Temporary Table (GTT) or even a normal heap table) that contains the data you want to load into your database, then your merge statement would become something like:
merge into mjl tgt
using (select trim(st.app_lse_s) app_lse_s,
sysdate dt_ent_s,
'SPPT' note_type_s,
'Y' prcs_c,
'1' prio_c,
'Property Tax Assessment' note_title_s,
npns.lien_dt info1_s,
npns.ases_prt_1_am info2_s
from staging_table st
inner join nmac_ptms_notebk_sg npns-- maybe left outer join?
on trim(st.app_lse_s) = npns.lse_s) src
on (tgt.app_lse_s = src.app_lse_s)
when matched then
update set tgt.dt_ent_s = src.dt_ent_s,
tgt.note_title_s = src.note_title_s,
tgt.info1_s = src.info1_s,
tgt.info2_s = src.info2_s
where tgt.dt_end_s != src.dt_ent_s
or tgt.note_title_s != src.note_title_s
or tgt.info1_s != src.info1_s
or tgt.info2_s != src.info2_s
when not matched then
insert (tgt.app_lse_s,
tgt.dt_ent_s,
tgt.note_type_s,
tgt.prcs_c,
tgt.prio_c,
tgt.note_title_s,
tgt.info1_s,
tgt.info2_s)
values (src.app_lse_s,
src.dt_ent_s,
src.note_type_s,
src.prcs_c,
src.prio_c,
src.note_title_s,
src.info1_s,
src.info2_s);
You can see that I've joined the nmac_ptms_notebk_sg table to the staging table, and used that to generate the set of data that needs to be merged into your mjl table. If your file/staging table also contains information for the other columns (dt_ent_s, note_type_s, etc) then you can replace the hardcoded values with the columns from the staging table.

Oracle Get two variables returned from select query inside of a package

I modified the procedure to make it smaller but I really only want to run the select query once. This will reduce the cost of running the procedure. How can I get the prevContectID and nextContentID without running the query twice. This is replacing a previous procedure so I do not want to change the IN and OUT so I do not have to find every where it is being called.
procedure getSeq(theContentID IN table.contentID%type,
prevContentID OUT table.contentID%type,
nextContentID OUT table.contentID%type)
BEGIN
SELECT myPrev into prevContentID, myNext into nextContentID
from myTable
where contentID=theContentID;
RETURN;
END getSeq;
The shown procedure most likely doesn't compile. The correct syntax for SELECT ... INTO using several variables is:
SELECT myPrev, myNext INTO prevContentID, nextContentID
from myTable
where contentID = theContentID;
You can also use a cursor to fetch the values from myTable.For your approach you need to do proper exception handling ,when theContentID does not exists in myTable,because that will give you NO_DATA_FOUND exception.
PROCEDURE getSeq (theContentID IN table.contentID%TYPE,
prevContentID OUT table.contentID%TYPE,
nextContentID OUT table.contentID%TYPE)
IS
CURSOR getcontentID_cur
IS
SELECT myPrev, myNext
FROM myTable
WHERE contentID = theContentID;
BEGIN
OPEN getcontentID_cur;
FETCH getcontentID_cur
INTO prevContentID, nextContentID;
CLOSE getcontentID_cur;
END getSeq;

Why I'm getting the ORA-01003: no statement parsed error?

Why am I getting this error and what does it mean by no statement parsed.
ORA-01003: no statement parsed
Here is the code:
PROCEDURE ORIGINAL_TABLE.UPDATE_GROUPS IS
-- cursor loaded with the swam groups
CURSOR cursor1 IS
SELECT ID, NEW_DESCRIPTION
FROM NEW_TABLE.NEW_GROUP_TABLE#DB_LINK.X;
BEGIN
FOR C1_REC IN cursor1 LOOP
UPDATE
ORIGINAL_TABLE."GROUPS"
SET
GROUP_ID = C1_REC.ID
WHERE
ORIGINAL_TABLE."GROUPS".DESCRIPTION = C1_REC.NEW_DESCRIPTION;
IF (SQL%ROWCOUNT = 0) THEN
INSERT INTO
ORIGINAL_TABLE.GROUPS("GROUP_ID", "DESCRIPTION")
VALUES (C1_REC.ID, C1_REC.NEW_DESCRIPTION);
END IF;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLERRM);
END;
What I try to do with the code above is to update and old table with the values from a new table and in case that the new group doesn't exist insert it.
Update: Changed %ROWCOUNT > 0 for %ROWCOUNT = 0
Use MERGE statement, it does update/insert stuff more efficiently and pay attention your plsql doesn't provide it is intended for. It tries to make an update statement and if a record found it inserts another record. In order to fix it use
IF (SQL%ROWCOUNT = 0)
I presume the reason of the issue is the . in DBLINK name.
Moreover I would suggest to get rid of quotes for tables/fields just in case as well as schema name.
Another words delete all ORIGINAL_TABLE.
merge into groups g
using (
SELECT ID, NEW_DESCRIPTION
FROM NEW_TABLE.NEW_GROUP_TABLE#DB_LINK.X
) nt
on (nt.NEW_DESCRIPTION = g.description )
when matched then update set g.group_id = nt.id
when non matched then insert(GROUP_ID, DESCRIPTION)
values(nt.id, nt.NEW_DESCRIPTION)

Resources