PL/SQL Function returning wrong result - sql

I've a PL/SQL function below that's returning a wrong result in SQL Navigator and SQL Developer, but is returning the right answer in SQL Plus.
We've a script running that's executing it and returning the wrong answer too, so trying to fix it. Can anyone see any issues with it? It works fine for most people, but I've a few people going into and returning null/nothing in SQL Navigator and Developer. It's not populating l_end_date for them, and thus credits not populating.
Works fine then in SQL Plus for some reason.
create or replace function mis_get_mem_lcr_credits(p_mem_no in number) RETURN number is
--
v_lcr_credit number;
l_mem_no number;
l_start_date date;
l_end_date date;
l_dob date;
l_18th_date date;
--
cursor c1 is
select mem_no, ind_birth_dt
from cd_individual
where mem_no = l_mem_no
and pkg_mem_utils.get_member_age(mem_no,ind_birth_dt) >= 18
and nvl(ind_student_flag,'N') = 'N'
order by mem_no, ind_birth_dt;
--
cursor c2 is
select distinct m_effdt,
m_termdt
from cd$v_member_contracts9 cd1,
cd_member_product_link cd2
where cd1.mem_no = l_mem_no
and cd1.policy_no = cd2.policy_no
and cd1.m_effdt = cd2.mem_product_eff_dt --.2
and (l_18th_date between cd1.m_effdt and cd1.m_termdt OR cd1.m_effdt > l_18th_date)--.3 18 at time of contract effective date
and nvl(cd1.lapsed_to_start,'N') = 'N'
and cd2.product_id not in (14,41,31) -- Exclude No Cover, DentalProtect and HealthProtect
and cd2.product_id NOT IN (select distinct product_id
from cd_product_options
where nvl(allowed_for_lcr,'Y') = 'N')
order by cd1.m_effdt ASC;
--
begin
--
l_mem_no := p_mem_no;
v_lcr_credit := 0;
l_dob := null;
--
for crec in c1 loop
--
l_dob := crec.ind_birth_dt;
--
-- l_18th_date := substr(to_char(l_dob,'DD/MM/YYYY'),0,6)||(substr(to_char(l_dob,'DD/MM/YYYY'),7,4)+18);
if to_char(l_dob) like '29-02%' then
l_18th_date := add_months(to_date(l_dob+1),216 );
else
l_18th_date := add_months(to_date(l_dob), 216);
end if;
--
for crec2 in c2 loop
--
if crec2.m_termdt > sysdate then
--
l_end_date := sysdate;
--
else
--
l_end_date := crec2.m_termdt;
--
end if;
--
if v_lcr_credit = 0 then --earliest contract
--
if l_18th_date between crec2.m_effdt and crec2.m_termdt then
--
v_lcr_credit := v_lcr_credit + months_between(l_end_date,l_18th_date);
--
else
--
v_lcr_credit := v_lcr_credit + months_between(l_end_date,crec2.m_effdt);
--
end if;
--
else
--
v_lcr_credit := v_lcr_credit + months_between(l_end_date,crec2.m_effdt);
--
end if;
--
end loop;
--
end loop;
--
return round(nvl(v_lcr_credit,0));
--
end mis_get_mem_lcr_credits;
/
show errors
spool off
exit

Never, ever use to_date() on a DATE value.
to_date() converts a varchar to a date.
If you call it with a DATE the date value gets converted to a varchar which then gets converted back to a date which it was to begin with - and being subject to the evil implicit data type conversion twice in that process.
The variable l_dob is defined as DATE so you have to change
add_months(to_date(l_dob+1),216 );
...
add_months(to_date(l_dob), 216);
to
add_months(l_dob+1,216);
...
add_months(l_dob, 216);

Could be because of different values of
NLS_TERRITORY, NLS_DATE_FORMAT etc. in different environments.
So I would suggest to set explicitly these values in your script. e.g. something like EXECUTE IMMEDIATE 'ALTER SESSION SET NLS_TERRITORY=''AMERICA''';
Some References:
NLS_DATE_FORMAT
NLS_TERRITORY

Related

PLSQL Check if varchar2 contains True

I wrote a PLSQL Script which should check if one of the selected rows contains TRUE if yes then he should print me a text.. but he fetches more than one row.
DECLARE
tmp varchar2(20);
BEGIN
Select Status into tmp
From Cdb_Dv_Status;
IF tmp = 'TRUE' THEN
DBMS_Output.put_line('the output is false');
ELSE
DBMS_Output.put_line('the output is true');
END IF;
end;
/
This is what i get when i select it with SQL
NAME STATUS
------------------- -----------
DV_ENABLE_STATUS FALSE
DV_APP_PROTECTION NOT CONFIGURED
DV_CONFIGURE_STATUS FALSE
DV_APP_PROTECTION NOT CONFIGURED
DV_ENABLE_STATUS FALSE
DV_CONFIGURE_STATUS FALSE
DV_APP_PROTECTION NOT CONFIGURED
DV_CONFIGURE_STATUS FALSE
DV_ENABLE_STATUS FALSE
DV_CONFIGURE_STATUS FALSE
DV_APP_PROTECTION NOT CONFIGURED
DV_ENABLE_STATUS FALSE
this doesnt work either.. he tells me i have to declare tmp
BEGIN
for rec in (
select status tmp
from Cdb_Dv_Status
)loop
IF rec.tmp='TRUE' then
dbms_output.put_line('Database Vault wird genutzt');
end if;
end loop;
IF rec.tmp='TRUE' then
dbms_output.put_line('wird genutzt');
ELSE
dbms_output.put_line('Database Vault wird nicht genutzt');
END IF;
end;
/
You may get some too_many_rows or no_data_found exceptions depending on the data for the current case, rather prefer using a COUNT aggregation as in the following code block to check the existence
DECLARE
tmp INT;
BEGIN
SELECT COUNT(*)
INTO tmp
FROM Cdb_Dv_Status
WHERE Status = 'TRUE';
IF tmp > 0 THEN
DBMS_Output.put_line('the output is true');
ELSE
DBMS_Output.put_line('the output is false');
END IF;
END;
/
Demo
If all you need to do is to check if the value 'TRUE' appears at least once in the column STATUS of table (or view) CDB_DV_STATUS, you can do something like this:
declare
str varchar2(5);
begin
select case when 'TRUE' in (select status from cdb_dv_status)
then 'true' else 'false' end
into str
from dual;
dbms_output.put_line('The output is ' || str);
end;
/

Change from Default Date to preferred date in this DBMS_SQL?

This code is from the proposed solution:
https://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:9541646100346616701
My query is 'select * from table'
The second column is a DATE and this routine writes it as appears in my IDE session ('DD-MON-YY');
How can I report the values in DATE columns to 'YYYY-MM-DD'?
procedure clob_maker(p_query varchar2) is
l_theCursor integer default dbms_sql.open_cursor;
l_columnValue varchar2(4000);
l_status integer;
l_descTbl dbms_sql.desc_tab;
l_colCnt number;
n number := 0;
l_data clob;
begin
dbms_sql.parse( l_theCursor, p_query, dbms_sql.native );
dbms_sql.describe_columns( l_theCursor, l_colCnt, l_descTbl );
for i in 1 .. l_colCnt loop
dbms_sql.define_column(l_theCursor, i, l_columnValue, 4000);
end loop;
l_status := dbms_sql.execute(l_theCursor);
while ( dbms_sql.fetch_rows(l_theCursor) > 0 ) loop
for i in 1 .. l_colCnt loop
dbms_sql.column_value( l_theCursor, i, l_columnValue );
--dbms_output.put_line(l_columnValue); --this puts every column on a separate line
l_data := l_data || l_columnValue ||',';
end loop;
dbms_output.put_line(l_data);
n := n + 1;
end loop;
--dbms_output.put_line(l_data);
end clob_maker;
clob_maker('select * from mlb_catcher_birthdays fetch first 10 rows only');
The default format in which dates are represented is controlled by NLS parameter ns_date_format. You can just change this parameter at session level:
alter session set nls_date_format = 'YYYY-MM-DD';
You could also use TO_CHAR(), with the same format specifier as second argument - but this does not fit very well to your use case, since you would then need to check the datatype of each column before printing its value.

While Loop and If/Else in PL/SQL

I am trying to write a procedure that will produce the following output
exec WinOrLose(4)
Welcome to the Win or Lose Game. Your number is 4.
You win.
You lose.
You win.
You lose.
==> You lose!
So far I have this:
CREATE or REPLACE Procedure WinOrLose (
p_choice number ) AS
v_answer number;
DECLARE
v_answer := p_choice
BEGIN
dbms_output.put_line ('Welcome to the Win or Lose Game. Your number is ' ||
v_answer);
FOR v_answer in 1..10
IF MOD(v_answer, 2) = 0 THEN -- v_answer is even
dbms_output.put_line (You lose)
END;
/
I'm unsure of where to go from there. My thought process (psuedocode) is this:
SET v_answer := 1
While Loop (outside)
MOD(v_answer,2) = 0 then dbms.output (YOU LOSE)
ELSE
dbms.output (YOU WIN)
end if;
v_answer := p_choice
CREATE or REPLACE Procedure WinOrLose (
p_choice number ) AS
BEGIN
dbms_output.put_line ('Welcome to the Win or Lose Game. Your number is ' ||
p_choice);
FOR v_counter in 1..p_choice LOOP
IF (MOD(v_counter, 2) = 0)
THEN
dbms_output.put_line ('You win');
ELSE
dbms_output.put_line ('You lose');
END IF;
END LOOP;
IF (MOD(p_choice , 2) = 0)
THEN
dbms_output.put_line ('==> You win!');
ELSE
dbms_output.put_line ('==> You lose!');
END IF;
END;
/

PL SQL : custom data type declaration of the type of this expression is incomplete or malformed function

I have create a package with a function like this :
create or replace
PACKAGE TRANSAC_ERRONNEES AS
TYPE dateArrayVar IS TABLE OF DATE;
FUNCTION calc_date_moyenne(dateArrayIn dateArrayVar
) RETURN DATE;
END TRANSAC_ERRONNEES;
Body :
CREATE OR REPLACE PACKAGE BODY TRANSAC_ERRONNEES AS
FUNCTION calc_date_moyenne(dateArrayIn datearrayvar) RETURN DATE IS
dateFinal DATE;
tempsEnSeconde NUMBER;
tempsMoyenEnSeconde NUMBER;
BEGIN
tempsEnSeconde := 0;
tempsMoyenEnSeconde := 0;
FOR i IN 1..dateArrayIn.count loop
tempsEnSeconde := to_number(to_char(dateArrayIn(i), 'HH24')) * 60 * 60 + to_number(to_char(dateArrayIn(i), 'MI')) * 60 + to_number(to_char(dateArrayIn(i), 'SS'));
tempsMoyenEnSeconde := tempsMoyenEnSeconde+tempsEnSeconde;
end loop;
tempsMoyenEnSeconde := tempsMoyenEnSeconde/dateArrayIn.count;
dateFinal := to_date(TO_CHAR(dateArrayIn(1),'DD-MM-YYYY') || ' ' || TO_CHAR(to_date(tempsMoyenEnSeconde,'sssss'), 'HH24:MI:SS'), 'DD-MM-YYYY HH24:MI:SS');
dbms_output.put_line(TO_CHAR(dateFinal,'DD-MM-YYYY HH24:MI:SS'));
RETURN dateFinal;
END calc_date_moyenne;
END TRANSAC_ERRONNEES;
/
I try to test this function like this :
DECLARE
dates dateArrayVar;
resultat DATE;
BEGIN
dates := dateArrayVar(SYSDATE, SYSDATE);
resultat := transac_erronnees.calc_date_moyenne(dates);
END;
I'm getting this following error :
PLS-00320: the declaration of the type of this expression is
incomplete or malformed
Please let me know the error. Thanks in advance.
You are using it in incorrect way. You need to use array with extend to assign memory and then assign value in it.
DECLARE
dates dateArrayVar;
resultat DATE;
BEGIN
dates.extend(2); -- Since you want to assign two values, extend by 2
dates(1) := SYSDATE;
dates(2) := SYSDATE;
resultat := transac_erronnees.calc_date_moyenne(dates);
END;
Update: Second thing is that DateArrayVar has scope only within the package that you have created, in the anonymous block, Oracle cannot see that declaration. One solution is to create external type that is visible to all other procedures and anonymous block or to test, create a procedure within the package and call it from out side.
create or replace
PACKAGE TRANSAC_ERRONNEES AS
TYPE dateArrayVar IS TABLE OF DATE;
FUNCTION calc_date_moyenne(dateArrayIn dateArrayVar
) RETURN DATE;
PROCEDURE test;
END TRANSAC_ERRONNEES;
/
CREATE OR REPLACE PACKAGE BODY TRANSAC_ERRONNEES AS
FUNCTION calc_date_moyenne(dateArrayIn datearrayvar) RETURN DATE IS
dateFinal DATE;
tempsEnSeconde NUMBER;
tempsMoyenEnSeconde NUMBER;
BEGIN
tempsEnSeconde := 0;
tempsMoyenEnSeconde := 0;
FOR i IN 1..dateArrayIn.count loop
tempsEnSeconde := to_number(to_char(dateArrayIn(i), 'HH24')) * 60 * 60 + to_number(to_char(dateArrayIn(i), 'MI')) * 60 + to_number(to_char(dateArrayIn(i), 'SS'));
tempsMoyenEnSeconde := tempsMoyenEnSeconde+tempsEnSeconde;
end loop;
tempsMoyenEnSeconde := tempsMoyenEnSeconde/dateArrayIn.count;
dateFinal := to_date(TO_CHAR(dateArrayIn(1),'DD-MM-YYYY') || ' ' || TO_CHAR(to_date(tempsMoyenEnSeconde,'sssss'), 'HH24:MI:SS'), 'DD-MM-YYYY HH24:MI:SS');
dbms_output.put_line(TO_CHAR(dateFinal,'DD-MM-YYYY HH24:MI:SS'));
RETURN dateFinal;
END calc_date_moyenne;
PROCEDURE test
dates dateArrayVar;
resultat DATE;
BEGIN
dates.extend(2); -- Since you want to assign two values, extend by 2
dates(1) := SYSDATE;
dates(2) := SYSDATE;
resultat := transac_erronnees.calc_date_moyenne(dates);
END;
END TRANSAC_ERRONNEES;
/
And to test, call it as
BEGIN
TRANSAC_ERRONNEES.TEST;
END;
/
DateArrayVar has scope only within the package
I found the solution : I had to specifiate the package : TRANSAC_ERRONNEES.dateArrayVar;
So :
DECLARE
dates TRANSAC_ERRONNEES.dateArrayVar;
resultat DATE;
BEGIN
dates := TRANSAC_ERRONNEES.dateArrayVar(SYSDATE, SYSDATE);
resultat := transac_erronnees.calc_date_moyenne(dates);
dbms_output.put_line(TO_CHAR(resultat,'DD-MM-YYYY HH24:MI:SS'));
END;
It's works now, Thanks

Oracle SQL: Find {tags} in a sql string

I have SQL strings that my users write. They look like:
SELECT Name, Age from Users WHERE Name LIKE '%a%' AND {UsersWhere}
On the oracle server side when such an SQL is to be executed I want to replace the {tags} first. The replacements for the {tags} will be valid SQL sub strings I am holding in a table. Pre-manufactered sub sqls. So the treated string will be valid SQL.
Is there some fancy build-in Oracle function to make this happen?
Thanks for a hint!
I have written a small function for anyone interested:
CREATE OR REPLACE FUNCTION SA.REPLACE_VARIABLES (p_sql IN VARCHAR2)
RETURN VARCHAR2
IS
vs_return VARCHAR2 (4000);
-- Deklarationen
vs_sql VARCHAR2(4000);
vs_substring VARCHAR2(4000);
vs_variable VARCHAR2(200);
vs_variable_content VARCHAR2(4000);
BEGIN
vs_sql := p_sql;
IF INSTR(p_sql, '{') > 0 THEN
vs_substring := vs_sql;
WHILE LENGTH(vs_substring) > 0 LOOP
IF INSTR(vs_substring, '{') <> 0
THEN
vs_variable := SUBSTR(vs_substring, INSTR(vs_substring, '{'), INSTR(vs_substring, '}') - INSTR(vs_substring, '{') + 1);
-- Do whatever you want with the variable
--vs_sql := REPLACE(vs_sql, vs_variable, vs_variable_content);
-- Substring verkürzen
vs_substring := SUBSTR(vs_substring, INSTR(vs_substring, vs_variable) + LENGTH(vs_variable) + 1);
ELSE
vs_substring := '';
END IF;
END LOOP;
END IF;
RETURN vs_sql;
EXCEPTION
WHEN OTHERS
THEN
-- Err -handle
END REPLACE_VARIABLES;
/
I'd just keep it simple:
v_sql := REPLACE(v_sql, '{UsersWhere}', '...whatever you need...');