PL/SQL FUNCTION WITH VARCHAR PARAMETER - oracle

These are the errors that I got. https://i.stack.imgur.com/hBB2e.png
The question asked for people who is born after 30th June 1990 to take injection. It asks the user to enter his/her id. I got error saying that 'literal does not match format string'. I don't know how to return varchar in plsql function. Here is my code:
create or replace function p_immune (ptdob in date)
return varchar2
is sta_imm varchar2(30);
BEGIN
if ptdob > '30th June 1990 ' then sta_imm := 'REQUIRED';
else sta_imm := 'NOT REQUIRED';
end if;
return(sta_imm);
END p_immune; /
Accept pt_id prompt 'Enter the patient ID: '
DECLARE
v_dob patient.ptdob%type; v_ptdob patient.ptdob%type;
BEGIN
select ptdob
into v_dob
from patient
where pt_id = &pt_id;
dbms_output.put_line('Enter the patient ID: '||&pt_id);
dbms_output.put_line('The status of X-immunization :'||p_immune(v_ptdob));
END; /

Use date literal:
if ptdob > date '1990-06-30' then sta_imm := 'REQUIRED';
or TO_DATE with appropriate format mask (and language):
if ptdob > to_date('30th June 1990', 'ddth Month yyyy', 'nls_date_language = english') then sta_imm := 'REQUIRED';
Also, make sure you pass DATE datatype value to the function. How? As described above. Don't pass strings.

Related

"ORA-01861: literal does not match format string" Error in PL/SQL

Can someone help me understand what the following code line is doing wrong?
res_start_time_ := to_date(to_char(account_date_, 'YYYYMMDD ') || sched_ftime_, 'YYYYMMDD HH24:MI');
res_start_time_ and account_date_ are of DATE type.
sched_ftime_ is VARCHAR2 type and it can be NULL.
In a test scenario, I get the ORA-01861: literal does not match format string error when there is a value for account_date_ and NULL for sched_ftime_.
Can someone explain to me what I am doing wrong and how I can get rid of this error?
The source code is attempting to form a string that can be converted to a date having both a day value and a time-of-day value.
This to_char(account_date_, 'YYYYMMDD ') converts a date value into a 9 character string ending with a space which permits use of string concatenation of what should be value containing hours and minutes. Once concatenated it then attempts to convert that into a date value accurate to a minute.
However the error encountered will occur if a non-null value of sched_ftime_ isn't in a form that can be transformed to HH24:MI e.g. '123456' is to long to be interpreted as only hours and minutes.
This can be replicated:
ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';
DECLARE
res_start_time_ DATE;
account_date_ DATE := TRUNC(SYSDATE);
sched_ftime_ VARCHAR2(20) := '123456'; /* this value fails */
BEGIN
res_start_time_ := to_date(
to_char(account_date_, 'YYYYMMDD ') || sched_ftime_,
'YYYYMMDD HH24:MI'
);
DBMS_OUTPUT.PUT_LINE(res_start_time_);
END;
/
ORA-01861: literal does not match format string
ORA-06512: at line 6
However a shorter value e.g. '1234' can be interpreted as hours and minutes:
DECLARE
res_start_time_ DATE;
account_date_ DATE := TRUNC(SYSDATE);
sched_ftime_ VARCHAR2(20) := '1234'; /* this value works */
BEGIN
res_start_time_ := to_date(
to_char(account_date_, 'YYYYMMDD ') || sched_ftime_,
'YYYYMMDD HH24:MI'
);
DBMS_OUTPUT.PUT_LINE(res_start_time_);
END;
/
1 rows affected
dbms_output:
2021-08-27 12:34:00
Hence I suggest you protect the conversion into a date by limiting the length of your second parameter e.g. to 4 characters perhaps using substr()
DECLARE
res_start_time_ DATE;
account_date_ DATE := TRUNC(SYSDATE);
sched_ftime_ VARCHAR2(20) := '123456'; /* this value gets truncated */
BEGIN
res_start_time_ := to_date(
to_char(account_date_, 'YYYYMMDD ')
|| substr(sched_ftime_,1,4),
'YYYYMMDD HH24:MI'
);
DBMS_OUTPUT.PUT_LINE(res_start_time_);
END;
/
You may need other validations on that varchar2 value such that they are all digits as well. Or, if you are including the colon into the hour & minutes value then the overall length needs to be 5 chars. In short you need to vet the hours and minutes so that they are logical and valid.
nb: Kudos to MT0 for the source db<>fiddle which I extended here
The source of the issue is you are allowing the time component (sched_ftime_) to assume values that you do not know the exact format before hand. You can do this, but it comes with a coding requirement. You need to establish the permissible and then derive the FORMAT SPECIFICATION at run time, and throw an error if the value does not match a permissible pattern. What is permissible and the validation is as simple or complicated as you want. As a demonstration the following function looks for 5 patterns:hh24mi, hh24miss, hh24:mi, hh24:mi:ss, and null. For those it returns the appropriate date, for anything else it returns a application defined exception.
create or replace
function get_actual_date_time( account_date_ date
, sched_ftime_ varchar2
)
return date
is
k_format_base constant varchar2(8) := 'yyyymmdd';
k_bad_time_format_msg constant varchar2(32) := ' invalid time specification.';
-- declare regexp for valid time formats
k_regx_time_hh24mm constant varchar2(7) := '^\d{4}$';
k_regx_time_hh24mmss constant varchar2(7) := '^\d{6}$';
k_regx_time_hh24mm_s constant varchar2(11) := '^\d\d:\d\d$';
k_regx_time_hh24mmss_s constant varchar2(16) := '^\d\d:\d\d:\d\d$';
-- declare actual format specification corresponding to valid format
k_fmt_time_hh24mm constant varchar2(6) := 'hh24mi';
k_fmt_time_hh24mmss constant varchar2(8) := 'hh24miss';
k_fmt_time_hh24mm_s constant varchar2(7) := 'hh24:mi';
k_fmt_time_hh24mmss_s constant varchar2(10) := 'hh24:mi:ss';
l_time_format varchar2(16);
begin
case when sched_ftime_ is null then
l_time_format := null;
when regexp_like ( sched_ftime_,k_regx_time_hh24mm) then
l_time_format := k_fmt_time_hh24mm;
when regexp_like ( sched_ftime_,k_regx_time_hh24mmss) then
l_time_format := k_fmt_time_hh24mmss;
when regexp_like ( sched_ftime_,k_regx_time_hh24mm_s) then
l_time_format := k_fmt_time_hh24mm_s;
when regexp_like ( sched_ftime_,k_regx_time_hh24mmss_s) then
l_time_format := k_fmt_time_hh24mmss_s;
else
raise_application_error( -20109,'''' || sched_ftime_ || '''' || k_bad_time_format_msg);
end case;
return to_date(to_char(account_date_,k_format_base) || sched_ftime_
, k_format_base || l_time_format);
end get_actual_date_time;
Keep in mind the above is an example only and there are many enhancement to be made. For example is will accept time specification of 99:99:99 even though it is obviously a invalid time specification. But it fits the validation for '^\d\d:\d\d:\d\d$'. Neither does it attempt to validate the valid time specification 06:45 PM. See fiddle here.

Automatically calculate the date oracle APEX

how can the date be calculated depending on the input of a user?
That is, depending on what the user enters, 3 months will be added to the current date or 5, etc.
I tried to add a computation to the date pickers:
SUBMISSION_DATE displays only the current date (already implemented)
DATE_OF_ISSUE should be SUBMISSION_DATE +3months or +5months, depending on users input in another field.
I added as well a computation to this date with the following pl/sql function body, but it is not working.
DECLARE
sub_date DATE default null;
BEGIN
if :P75_THESISTYPE =1
then
sub_date := :P75_SUBMISSION_DATE +6;
else
sub_date := :P75_SUBMISSION_DATE +3;
END IF;
return sub_date;
end;
The page items are treated as VARCHAR2 so you need to convert them to dates before performing arithmetic on them:
DECLARE
sub_date DATE default null;
BEGIN
if :P75_THESISTYPE =1
then
sub_date := TO_DATE(:P75_SUBMISSION_DATE) +6;
else
sub_date := TO_DATE(:P75_SUBMISSION_DATE) +3;
END IF;
return sub_date;
end;
/
#TonyAndrews has the correct process, but as he followed your initial statement, it will not give the desired result. Adding an integer to a data add that number of days not months. What you need is the ADD_Months function; So:
DECLARE
sub_date DATE default null;
BEGIN
if :P75_THESISTYPE =1
then
sub_date := add_months(to_date(:P75_SUBMISSION_DATE),6);
else
sub_date := add_months(to_date(:P75_SUBMISSION_DATE),3);
end if
return sub_date;
end;
/
Of course the above assumes your variable's format matches your nls_data_format. Generally a dangerous assumption at best,

Optimizing a simple procedure

Below is my procedure, takes 51 sec to execute, I want to return cursor only if one count is found, in case anything else will return message and cursor as null. In case cursor is found message as null..
I am first taking count by query and populating data by the same query later but only in case of count being one.
is their anyway in which this could be optimized in terms of time.?
create or replace PROCEDURE sp_cp_getcrnnofrmmobdob(P_MobileNo IN VARCHAR2,
P_Dob IN VARCHAR2,
p_Output out SYS_REFCURSOR,
p_Message OUT VARCHAR2) IS
vCRN Varchar2(50) := '';
vCustid varchar2(50) := '';
vMobno varchar2(50) := '';
vCustname varchar2(400) := '';
vCustDob varchar2(50) := '';
vcount int := 0;
BEGIN
p_Message := '';
OPEN p_Output FOR
select 1 from dual;
Select count(*)
into vcount
FROM (select distinct(C.fw_customer_id_c) crn,
C.Cust_Id_n custid,
c.customername custname,
c.dob custdob,
A.MOBILE mobileno
from FCH_CASTRANSACTION.NBFC_CUSTOMER_M C,
FCH_CASMASTER.nbfc_address_m A
where A.BPID = C.Cust_Id_n and
A.mobile = P_MobileNo and
TO_CHAR(TO_DATE(C.DOB, 'DD-MON-YY'),'DD-MON-YY')=TO_CHAR(TO_DATE(P_Dob,'DD/MM/YYYY'),'DD-MON-YY'));
if (vcount = 1) then
select B.crn,
B.custid,
B.mobileno,
B.custname,
B.custdob
into vCRN, vCustid, vMobno, vCustname, vCustDob
from (select distinct(C.fw_customer_id_c) crn,
C.Cust_Id_n custid,
c.customername custname,
c.dob custdob,
A.MOBILE mobileno
from FCH_CASTRANSACTION.NBFC_CUSTOMER_M C,
FCH_CASMASTER.nbfc_address_m A
where A.BPID = C.Cust_Id_n and
A.mobile = P_MobileNo and
TO_CHAR(TO_DATE(C.DOB, 'DD-MON-YY'),'DD-MON-YY')=TO_CHAR(TO_DATE(P_Dob,'DD/MM/YYYY'),'DD-MON-YY')) B;
if ((vCRN = '') OR (vCRN IS Null)) then
p_Message := 'No data found for entered details';
else
if ((vMobno <> P_MobileNo) OR (vMobno IS Null)) then
p_Message := 'Entered mobile number is not registered with us.Please contact customer care.';
else
if ((vCustDob <> TO_CHAR(TO_DATE(P_Dob,'DD/MM/YYYY'),'DD-MON-YY')) OR (vCustDob IS Null)) then
p_Message := 'Entered date of birth is not registered with us.Please contact customer care.';
else
OPEN p_Output FOR
select vCRN as "CrnNum", vCustid as "CustId", vMobno as "MobNo", vCustname as "CustName", vCustDob as "CustDob"
from dual;
End if;
End if;
End if;
else
p_Message := 'Inconsistent details for entered data found. Please contact customer care';
End if;
EXCEPTION
WHEN NO_DATA_FOUND THEN
p_Message := 'Unable to process your request.Please contact customer care.';
OPEN p_Output FOR
SELECT 1 FROM dual;
END;
Would really appreciate if someone can help.
You can just use one SELECT ... INTO ... and catch the exception TOO_MANY_ROWS.
'' and NULL are the same thing.
It will not return a row if the mobile number does not match (or is null) so that check is redundant.
Same for the date of birth check.
DISTINCT is NOT a function - it is a keyword that applies to all the rows.
You assigning a cursor to p_output twice. Also, some systems may not like that the function can return different numbers of columns to your cursor.
So, something like this:
create or replace PROCEDURE sp_cp_getcrnnofrmmobdob(
P_MobileNo IN VARCHAR2,
P_Dob IN VARCHAR2,
p_Output out SYS_REFCURSOR,
p_Message OUT VARCHAR2
)
IS
v_dob DATE := TO_DATE( p_dob, 'DD/MM/YYYY' );
vCRN FCH_CASTRANSACTION.NBFC_CUSTOMER_M.fw_customer_id_c%TYPE;
vCustid FCH_CASTRANSACTION.NBFC_CUSTOMER_M.Cust_Id_n%TYPE;
vMobno FCH_CASMASTER.nbfc_address_m.MOBILE%TYPE;
vCustname FCH_CASTRANSACTION.NBFC_CUSTOMER_M.customername%TYPE;
vCustDob FCH_CASTRANSACTION.NBFC_CUSTOMER_M.dob%TYPE;
BEGIN
p_Message := '';
select distinct
C.fw_customer_id_c,
C.Cust_Id_n,
c.customername,
c.dob,
A.MOBILE
into vCRN, vCustid, vMobno, vCustname, vCustDob
from FCH_CASTRANSACTION.NBFC_CUSTOMER_M C
INNER JOIN FCH_CASMASTER.nbfc_address_m A
ON ( A.BPID = C.Cust_Id_n )
WHERE A.mobile = P_MobileNo
AND TO_DATE( C.DOB, 'DD-MON-YY') = v_dob;
IF vCRN IS NULL THEN
p_Message := 'No data found for entered details';
OPEN p_Output FOR
select 1 from dual;
RETURN;
END IF;
OPEN p_Output FOR
select vCRN as "CrnNum", vCustid as "CustId", vMobno as "MobNo", vCustname as "CustName", vCustDob as "CustDob"
from dual;
EXCEPTION
WHEN NO_DATA_FOUND THEN
p_Message := 'Unable to process your request.Please contact customer care.';
OPEN p_Output FOR
SELECT 1 FROM dual;
WHEN TOO_MANY_ROWS THEN
p_Message := 'Inconsistent details for entered data found. Please contact customer care';
OPEN p_Output FOR
SELECT 1 FROM dual;
END;
Looking at TO_CHAR(TO_DATE(C.DOB, 'DD-MON-YY'),'DD-MON-YY')=TO_CHAR(TO_DATE(P_Dob,'DD/MM/YYYY'),'DD-MON-YY')) in both of your queries, I suggest.
Based on this logic, you are storing the date of birth (dob) as a string. It is a shame if you do, it should be in the database as a DATE.
You are converting the strings to a date and then back to a string. In Oracle, you can compare dates, so only convert from column to DATE, not again back to a string. Such as TO_DATE(column,'column format')=TO_DATE(variable,'variable format')
Or, better yet, for your data model, consider just converting the variable input date string to match the column string format. Like column = TO_CHAR(TO_DATE(variable, 'variable format'),'column format'). There are two possible advantages here. First, the conversion will only happen once for the supplied value, but the query never has to perform a function on the column value. If the table is big. Also, since there is no function to be performed on the column value, IF there is an index on this value, the optimizer can use it (although this may not help your example based on my guess on your data model). Performance improvement here would depend on how the dob is used by your query. If Oracle is lookup up records by mobile number, then filter by dob, it shouldn't make much difference, but if it goes the other way around, looking up by dob then filtering on mobile number, this could help immensely.
tl;dr Store dates as dates, compare dates as dates, avoid functions on the column values when possible

date + 7 working days

I need to write a function that will give me a new due date for an invoice. This needs to be 12 working days after the current due date
Say the current due date is 01.Oct.2014. If I look at my calendar manually, I can see that the new date would be 17.Oct.2014 (need to exclude weekends).
However, I also have a table with Bank Holidays. This would have to be taken into consideration. So if I would have a Bank Holiday on 04.Oct.2014, the new due date should be 18.Oct.2014.
EDIT: My table with Bank Holidays would look something like this:
Year: Date: Description
2014 04.Oct.2014 Bank Holiday 1
Any help with this would be deeply appreciated, I'm stuck at this for almost a day now.
Thanks a lot in advance.
Kind regards
Gerben
Something like this should work:
DECLARE
l_date DATE := SYSDATE;
FUNCTION IS_WEEKEND(P_DATE IN DATE)
RETURN BOOLEAN
IS
l_daynum VARCHAR2(1) := to_char (P_DATE, 'D');
BEGIN
RETURN l_daynum = '6' OR l_daynum = '7';
END;
FUNCTION IS_HOLIDAY(P_DATE IN DATE)
RETURN BOOLEAN
IS
CURSOR c_exists IS
SELECT 1 FROM bank_holidays WHERE date = TRUNC(P_DATE)
;
l_count NUMBER;
BEGIN
OPEN c_exists;
l_count := c_exists%ROWCOUNT;
CLOSE c_exists;
RETURN l_count > 0;
END;
PROCEDURE ADD_WORKING_DAYS(P_DATE IN OUT DATE, P_DAYS IN NUMBER)
IS
l_workdays_added NUMBER := 0;
BEGIN
WHILE TRUE
LOOP
P_DATE := P_DATE + 1;
IF NOT IS_WEEKEND(P_DATE) AND NOT IS_HOLIDAY(P_DATE) THEN
l_workdays_added := l_workdays_added + 1;
END IF;
IF l_workdays_added = P_DAYS THEN
RETURN;
END IF;
END LOOP;
END;
BEGIN
ADD_WORKING_DAYS(l_date, 12);
END;
I ended up doing things slightly different. I have a table with all my bank holiday. I created a second table as a kind of calendar. In here, I loaded all dates in a year. I then flag it as weekend or bank holiday (2 separate columns).
I take my original due date, and add the 12 days. I then have a start and end date (v_due_date_old and v_due_date_new)
After that, I count how many days there are in my 'calendar' table, where either my flag for weekend or bank holiday is set to Yes. If v_due_date_new is on a Saturday, I add another day to my count.
I then add the new count to v_due_date_new.
As a last step, I check what day v_due_date_new is. If it is Saturday or Sunday, I add another 2 days

Error while receiving date as parameter input

I am getting an error when I plug in a date to test a parameter I am working on. The error is:
01858. 00000 - "a non-numeric character was found where a numeric was expected"
*Cause: The input data to be converted using a date format model was
incorrect. The input data did not contain a number where a number was
required by the format model.
*Action: Fix the input data or the date format model to make sure the
elements match in number and type. Then retry the operation.
Here is my test script:
set serveroutput on
declare
type tempcursor is ref cursor;
v_cur_result tempcursor;
errcode number;
errmesg varchar2(1000);
p_statusmnemonic_in acts.ct_cu_act_medrecon_pg.varchararrayplstype;
p_processtypemnemonic_in transactionprocesslog.processtypemnemonic%type;
p_primarymemberplanid_in membermedicalreconcilationhdr.primarymemberplanid%type;
p_assigneduserid_in membermedicalreconcilationhdr.assigneduserid%type;
p_accountorgid_in membermedicalreconcilationhdr.accountorgid%type;
p_reconstatusmnemonic_in membermedicalreconcilationhdr.reconciliationstatusmnemonic%type;
p_estimatedenddt_in membermedicalreconcilationhdr.estimatedenddt%type;
p_actualenddt_in membermedicalreconcilationhdr.actualenddt%type;
p_inserteddate_in membermedicalreconcilationhdr.inserteddt%type;
p_insertedby_in membermedicalreconcilationhdr.insertedby%type;
p_updateddate_in membermedicalreconcilationhdr.updateddt%type;
p_updatedby_in membermedicalreconcilationhdr.updatedby%type;
begin
p_statusmnemonic_in(1) := ('OPEN');
p_statusmnemonic_in(2) := ('SUSPENDED_PRIOR_TO_COMPARE');
ct_cu_act_medrecon_pg.sps_get_patientmedrecs_hdr
(p_statusmnemonic_in,'NO','26-JAN-14',v_cur_result, errcode, errmesg);
loop
fetch v_cur_result into p_primarymemberplanid_in,p_assigneduserid_in,p_accountorgid_in,p_reconstatusmnemonic_in,
p_estimatedenddt_in,p_actualenddt_in,p_inserteddate_in,p_insertedby_in,
p_updateddate_in,p_updatedby_in,p_processtypemnemonic_in;
dbms_output.put_line(' planid '||p_primarymemberplanid_in||' userid '||p_assigneduserid_in);
exit when v_cur_result%notfound;
end loop;
dbms_output.put_line(' error code '||errcode||' message '||errmesg);
end;
I dont get an error when the date is set to '24-JAN-13' but the second I change anything I get that error. Here are the two estimated date fields in the table I am looking at:
24-JAN-13 04.29.19.989847000 PM
28-JAN-13 08.52.27.187015000 PM
Here is my proc:
procedure sps_get_patientmedrecs_hdr (
p_statusmnemonic_in in varchararrayplstype,
p_processtypemnemonic_in in transactionprocesslog.processtypemnemonic%type,
p_estimatedenddt_in in membermedicalreconcilationhdr.estimatedenddt%type,
p_return_cur_out out sys_refcursor,
p_err_code_out out number,
p_err_mesg_out out varchar2)
is
lv_varchararray varchararray := varchararray();
begin
if p_statusmnemonic_in.count > 0
then
for rec1 in 1..p_statusmnemonic_in.count
loop
lv_varchararray.extend(1);
lv_varchararray(rec1) := p_statusmnemonic_in(rec1);
end loop;
open p_return_cur_out for
select h.membermedreconciliationhdrskey,
h.primarymemberplanid,
h.assigneduserid,
h.accountorgid,
h.reconciliationstatusmnemonic,
h.estimatedenddt,
h.actualenddt,
h.inserteddt,
h.insertedby,
h.updateddt,
h.updatedby
from membermedicalreconcilationhdr h
where h.reconciliationstatusmnemonic in (select *
from table (cast(lv_varchararray as varchararray)))
and h.estimatedenddt <= nvl(p_estimatedenddt_in, h.estimatedenddt)
and not exists (select *
from transactionprocesslog tpl
where tpl.transactiontypemnemonic = 'MEDREC'
and tpl.transactionid = h.primarymemberplanid
and nvl(p_processtypemnemonic_in, tpl.processtypemnemonic) = tpl.processtypemnemonic);
else
open p_return_cur_out for
select h.membermedreconciliationhdrskey,
h.primarymemberplanid,
h.assigneduserid,
h.accountorgid,
h.reconciliationstatusmnemonic,
h.estimatedenddt,
h.actualenddt,
h.inserteddt,
h.insertedby,
h.updateddt,
h.updatedby
from membermedicalreconcilationhdr h;
end if;
p_err_code_out := 0;
exception
when others then
p_err_code_out := -1;
p_err_mesg_out := 'error in ct_cu_act_medrecon_pg.sps_get_patientmedrecs_hdr => ' || sqlerrm;
end sps_get_patientmedrecs_hdr;
I've tried the to_date function but received the same error. Any help would be appreciated, thanks in advance.
Looks like you're treating a string like it's a date, which may or may not work depending on your NLS settings. Whenever Oracle sees a string where it expects a date it tries to do an implicit date conversion based whether that string happens to match your particular session's date formatting parameter. This is a very common source of errors.
To use a proper date you can use the to_date function like so:
to_date('26-JAN-14','DD-MON-RR')
or use the native syntax for specifying date literals, which is what I would recommend:
date'2014-1-26'

Resources