PLS-00103 Encountered the symbol "END" - sql

I'm running into the issue PL-00103: Encountered the symbol "END" on the second to last line of the following procedure. The purpose of the procedure is to see if a previous version of an email exists when a new one is sent... and expire the old email. (emails are by default set to a far away expiration date when created so I check to see if the expiration date is further than the current date).
PROCEDURE EXPIRE_STUFF_PRC
(
PI_EMAIL_NBR_STR IN VARCHAR2,
PO_SUCCESS_FLG OUT VARCHAR2,
PO_OUT_MSG OUT VARCHAR2
) AS
L_SUCCESS VARCHAR2(1) := 'N';
L_EMAIL_ID NUMBER;
L_PREV_EMAIL_VER_ID NUMBER := 0;
L_PREV_EMAIL_EXP_DT DATE;
BEGIN
BEGIN
SELECT
ITEM.EMAIL_ID
INTO L_EMAIL_ID
FROM HR_EMAIL ITEM
WHERE ITEM.EMAIL_NBR_STR = PI_EMAIL_NBR_STR;
END;
BEGIN
SELECT
VER_ID.EMAIL_VER_ID
INTO L_PREV_EMAIL_VER_ID
FROM (
SELECT
EMAIL_VER_ID
FROM HR_EMAIL_VER
WHERE EMAIL_ID = L_EMAIL_ID
ORDER BY EMAIL_VER_ID DESC
) VER_ID
WHERE ROWNUM = 2;
EXCEPTION
WHEN NO_DATA_FOUND
THEN PO_SUCCESS_FLG := 'Y';
PO_OUT_MSG := 'LESS THAN 2 VERSIONS';
END;
END;
BEGIN
IF (L_PREV_EMAIL_VER_ID > 0) THEN
SELECT
VER.EXP_DT
INTO L_PREV_EMAIL_EXP_DT
FROM HR_EMAIL_VER VER
WHERE VER.EMAIL_VER_ID = L_PREV_EMAIL_VER_ID;
IF (L_PREV_EMAIL_EXP_DT > SYSDATE) THEN
UPDATE HR_EMAIL_VER
SET EXP_DT = SYSDATE
WHERE EMAIL_VER_ID = L_PREV_EMAIL_VER_ID;
END IF;
PO_SUCCESS_FLG := 'Y';
PO_OUT_MSG := 'SUCCESS';
END IF;
END;
END;
END EXPIRE_STUFF_PRC;
I've tried to move around ENDs and BEGINs to no avail...
I've changed the name of stuff due to NDA so if there is any confusion I'll be happy to clarify as I might have missed something.
Thank you in advance for any help!

You have used unnecessary BEGIN/END in your code. There could be a necessity if you want to handle exceptions in a particular section. The best way to format and check for syntax is to use a nice code editor.
I have removed all the unwanted BEGIN/END and this should compile fine:
CREATE OR REPLACE PROCEDURE expire_stuff_prc (
pi_email_nbr_str IN VARCHAR2,
po_success_flg OUT VARCHAR2,
po_out_msg OUT VARCHAR2
) AS
l_success VARCHAR2(1) := 'N';
l_email_id NUMBER;
l_prev_email_ver_id NUMBER := 0;
l_prev_email_exp_dt DATE;
BEGIN
SELECT
item.email_id
INTO l_email_id
FROM
hr_email item
WHERE
item.email_nbr_str = pi_email_nbr_str;
BEGIN
SELECT
ver_id.email_ver_id
INTO l_prev_email_ver_id
FROM
(
SELECT
email_ver_id
FROM
hr_email_ver
WHERE
email_id = l_email_id
ORDER BY
email_ver_id DESC
) ver_id
WHERE
ROWNUM = 2;
EXCEPTION
WHEN no_data_found THEN
po_success_flg := 'Y';
po_out_msg := 'LESS THAN 2 VERSIONS';
END;
IF ( l_prev_email_ver_id > 0 ) THEN
SELECT
ver.exp_dt
INTO l_prev_email_exp_dt
FROM
hr_email_ver ver
WHERE
ver.email_ver_id = l_prev_email_ver_id;
IF ( l_prev_email_exp_dt > sysdate ) THEN
UPDATE hr_email_ver
SET
exp_dt = sysdate
WHERE
email_ver_id = l_prev_email_ver_id;
END IF;
po_success_flg := 'Y';
po_out_msg := 'SUCCESS';
END IF;
END expire_stuff_prc;
/

Ideal way to write a stored procedure is to handle all exception for each of the block/statement. You have one additional END that has to be removed, additionally i have added messages to be returned when first and last block goes into any exception.
Block is in sense logically grouped DML operation, it can be just a single select or group of DML operation.Decision to be taken based on the business logic.
PROCEDURE EXPIRE_STUFF_PRC
(
PI_EMAIL_NBR_STR IN VARCHAR2,
PO_SUCCESS_FLG OUT VARCHAR2,
PO_OUT_MSG OUT VARCHAR2
) AS
L_SUCCESS VARCHAR2(1) := 'N';
L_EMAIL_ID NUMBER;
L_PREV_EMAIL_VER_ID NUMBER := 0;
L_PREV_EMAIL_EXP_DT DATE;
BEGIN
BEGIN
SELECT
ITEM.EMAIL_ID
INTO L_EMAIL_ID
FROM HR_EMAIL ITEM
WHERE ITEM.EMAIL_NBR_STR = PI_EMAIL_NBR_STR;
EXCEPTION WHEN OTHERS THEN
PO_SUCCESS_FLG := 'Y';
PO_OUT_MSG := 'COULD NOT FETCH EMAIL_ID';
END;
BEGIN
SELECT
VER_ID.EMAIL_VER_ID
INTO L_PREV_EMAIL_VER_ID
FROM (
SELECT
EMAIL_VER_ID
FROM HR_EMAIL_VER
WHERE EMAIL_ID = L_EMAIL_ID
ORDER BY EMAIL_VER_ID DESC
) VER_ID
WHERE ROWNUM = 2;
EXCEPTION
WHEN NO_DATA_FOUND
THEN PO_SUCCESS_FLG := 'Y';
PO_OUT_MSG := 'LESS THAN 2 VERSIONS';
END;
BEGIN
IF (L_PREV_EMAIL_VER_ID > 0) THEN
SELECT
VER.EXP_DT
INTO L_PREV_EMAIL_EXP_DT
FROM HR_EMAIL_VER VER
WHERE VER.EMAIL_VER_ID = L_PREV_EMAIL_VER_ID;
IF (L_PREV_EMAIL_EXP_DT > SYSDATE) THEN
UPDATE HR_EMAIL_VER
SET EXP_DT = SYSDATE
WHERE EMAIL_VER_ID = L_PREV_EMAIL_VER_ID;
END IF;
PO_SUCCESS_FLG := 'Y';
PO_OUT_MSG := 'SUCCESS';
END IF;
EXCEPTION
WHEN OTHERS THEN
PO_SUCCESS_FLG := 'Y';
PO_OUT_MSG := 'UNABLE TO UPDATE HR_EMAIL_VER';
END;
END EXPIRE_STUFF_PRC;

Related

Oracle - How handling function error and return null instead

How handling error in functon and return null instead any errors?
Simple example:
create or replace Function MY_FUNC
(
p_par IN number
)
RETURN varchar2
IS
BEGIN
return (Select name from my_table where id = p_par);
END;
create or replace Function MY_FUNC
(
p_year IN number,
p_month IN number,
p_day IN number
)
RETURN varchar2
IS
v_return varchar2(100);
BEGIN
Select to_char(p_day)||''||substr(to_char(TO_DATE(p_year || '-' || p_month || '-' || p_day, 'YYYY-MM-DD'),'DY'),0,1) into v_return from dual;
return v_return;
END;
Select MY_FUNC(2021,6,30) from dual; --OK
Select MY_FUNC(2021,6,31) from dual; --Need catch error
Return null if don't have record in table, or return null on ORA-01839: date not valid for month specified for anything.
My guess is that you want something like this where you catch the no_data_found exception
create or replace Function MY_FUNC
(
p_par IN my_table.id%type
)
RETURN my_table.name%type
IS
l_name my_table.name%type;
BEGIN
begin
select name
into l_name
from my_table
where id = p_par;
exception
when no_data_found
then
l_name := null;
end;
return l_name;
END;
I'm not sure where an "ORA-01839: date not valid for month" error could be coming from since there are no obvious dates in your code. You can, however, catch additional exceptions for other statements in your code in just the same way that I'm catching the no_data_found exception here.
create or replace Function MY_FUNC
(
p_year IN number,
p_month IN number,
p_day IN number
)
RETURN varchar2
IS
v_return varchar2(100);
invalid_date exception;
pragma exception_init( invalid_date, -01839 );
BEGIN
begin
Select to_char(p_day) || '' ||
substr(to_char(TO_DATE(p_year || '-' || p_month || '-' || p_day,
'YYYY-MM-DD'),
'DY'),
0,
1)
into v_return
from dual;
exception
when invalid_date
then
v_return := null;
end;
return v_return;
END;
In this specific case, though, you can simplify your logic (assuming you're on 12.2 or later)
create or replace Function MY_FUNC
(
p_year IN number,
p_month IN number,
p_day IN number
)
RETURN varchar2
IS
v_return varchar2(100);
v_dt date;
BEGIN
v_dt := to_date( p_year || '-' || p_month || '-' || p_day default null on conversion error,
'YYYY-MM-DD' );
if( v_dt is null )
then
v_return := null;
else
v_return := to_char(p_day) ||
substr( to_char(v_dt, 'DY' ),
0,
1 );
end if;
return v_return;
END;
You could use the EXCEPTION key word.
The NO_DATA_FOUND is the exception type.
If you don'nt know the type you can use the OTHERS key word.
Edit (Thanks to Justin)
Of course you have to store it in a variable.
To return the result.
create or replace Function MY_FUNC
(
p_par IN number
)
RETURN varchar2
IS
l_result varchar2;
BEGIN
Select name into l_result from my_table where id = p_par;
return l_result;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN NULL;
END;

Adapt function to procedure

I have the below function and I would like to turn it into a procedure. What would be the changes I need to accomplish it? I would like to turn it into a procedure because it's kind of a one purpose use function, and I would prefer to have it inside my main package along with my other procedures.
I would like to, instead of returning the table, creating it inside the procedure itself.
CREATE OR REPLACE FUNCTION pick_values RETURN t1_prueba_table
PIPELINED
IS
TYPE t2_type IS
TABLE OF t2%rowtype;
t2_data t2_type;
v_pc t1.pc%TYPE;
BEGIN
-- https://stackoverflow.com/a/67516191/1509264
-- License: CC BY-SA 4.0
FOR cur IN (
SELECT *
FROM t1
ORDER BY pc, r
) LOOP
IF v_pc IS NULL OR v_pc <> cur.pc THEN
v_pc := cur.pc;
SELECT *
BULK COLLECT INTO t2_data
FROM t2
WHERE pc = cur.pc;
END IF;
DECLARE
a_freqs int_list := int_list();
cum_freq INT := 0;
taken string_list := split_string(cur.an, ', ');
idx INT;
c t2.a%TYPE;
BEGIN
a_freqs.extend(t2_data.count);
FOR i IN 1..t2_data.count LOOP
IF t2_data(i).a = cur.ao
AND t2_data(i).c > 0
THEN
-- If there is an "ao" value and it has capacity then assign it to "c"
c := t2_data(i).a;
-- Decrement the appropriate "t2_data" row to show it has been used.
t2_data(i).c := t2_data(i).c - 1;
-- Set the "cum_freq" to 0 so the loop where values are randomly assigned is skipped.
cum_freq := 0;
-- Exit the loop
EXIT;
ELSIF (
t2_data(i).a = cur.ay
AND t2_data(i).c > 0
) OR (
cur.ay IS NULL
AND t2_data(i).a NOT MEMBER OF taken
AND t2_data(i).c > 0
)
THEN
a_freqs(i) := cum_freq + t2_data(i).c;
cum_freq := cum_freq + t2_data(i).c;
ELSE
a_freqs(i) := cum_freq;
END IF;
END LOOP;
IF cum_freq > 0 THEN
idx := floor(dbms_random.value(0, cum_freq));
FOR i IN 1..t2_data.count LOOP
IF idx < a_freqs(i) THEN
c := t2_data(i).a;
t2_data(i).c := t2_data(i).c - 1;
EXIT;
END IF;
END LOOP;
END IF;
PIPE ROW (
t1_prueba_data(cur.pc, cur.vk, cur.ay, cur.ao, cur.an, cur.r, c)
);
END;
END LOOP;
END;
Kindly let me know if any additional information needed.
A package can contain both procedures and functions.
There is no need to convert this from a function to a procedure to include it in a package.
eg
create or replace package test AS
PROCEDURE test_procedure (in_test in varchar2(100));
FUNCTION assessment_newid_f RETURN assessments.assessment_id%TYPE;
END;
create or replace package body test AS
PROCEDURE test_procedure (in_test in varchar2(100)) IS
BEGIN
...
END;
FUNCTION assessment_newid_f RETURN assessments.assessment_id%TYPE IS
BEGIN
...
RETURN ...;
END;
END;

Generate dynamic SQL query with multiple filter option(from application)added in single CLOB - Oracle

There is application in which filter option includes start date, end date, postcode, etc where user can select multiple option from single filter. Ex: User can select 5 different start date, end date and 3 different postcode or can select 1 start date, end date and no postcode. There are 7 other filters and each filter user can select multiple options. (Consider E-Commerce website like Amazon. If you want to buy mobile, you use filter for camera range like 16MP,32MP,48MP and company like 'Samsung','Motorola', 'Nokia' and similarly for processor and screen size as well. So as a developer you want to store all the value in CLOB and give the result)
Input will come something like
<date>
<start_date>01/01/2019</start_date>
<end_date>31/01/2019</end_date>
<start_date>01/03/2019</start_date>
<end_date>31/03/2019</end_date>
<start_date>01/05/2019</start_date>
<end_date>31/05/2019</end_date>
</date>
<pc>
<postcode>56012</postcode>
<postcode>56000</postcode>
<postcode>56234</postcode>
</pc>
Now my query should look something like
select col1,col2,col3 from table_name where between start_date and end_date and between start_date and end_date... and postcode like '56012' and postcode like '56000'
Result set retruned should again be sent in clob.
Is there any way out to solve this problem?
create or replace procedure get_data(p_filter IN clob, p_result clob)
is
v_query clob;
V_condition clob;
v_lp_cnt integer;
v_result clob;
v_where_clause clob;
begin
--Considering output will be returned in XML
v_query := 'SELECT XMLELEMENT (
"data", XMLAGG
(XMLELEMENT
(
"whole_data",
XMLELEMENT ("final_date", final_date),
......... from table_name where col2='xyz'';
--Considering that filter values will be less than 1000
--date
V_condition := NULL;
v_lp_cnt := 0;
FOR crec IN(SELECT t.start_date, t.end_date
FROM XMLTABLE('/Filter/final_date/Date'
PASSING xmltype(p_filter)
COLUMNS
start_date VARCHAR2(240) PATH 'Start_Date',
end_date VARCHAR2(240) PATH 'End_Date')t
WHERE 1 = 1
)
loop
v_lp_cnt := v_lp_cnt +1;
IF v_lp_cnt =1
THEN
v_condition := ' AND (ext.final_date between to_date('''|| crec.start_date ||''',''dd/mm/yyyy'') and to_date('''|| crec.end_date ||''',''dd/mm/yyyy'')';
v_where_clause := v_where_clause||v_condition;
v_condition:= null;
ELSIF (v_lp_cnt >1 and v_lp_cnt <1000)
THEN
v_condition := ' OR ext.final_date between to_date('''|| crec.start_date ||''',''dd/mm/yyyy'') and to_date('''|| crec.end_date ||''',''dd/mm/yyyy'')';
v_where_clause := v_where_clause||v_condition;
end if;
end loop;
IF v_lp_cnt>0
THEN
v_where_clause := v_where_clause|| ')';
END IF;
--postcode
v_condition := NULL;
v_lp_cnt := 0;
FOR crec IN(SELECT (t.postcode) postcode
FROM XMLTABLE('/Filter/pc/postcode'
PASSING xmltype(p_filter)
COLUMNS postcode VARCHAR2(30)PATH '/postcode')t
WHERE 1 = 1
)
loop
v_lp_cnt := v_lp_cnt + 1;
IF v_lp_cnt = 1 THEN
v_condition := ' AND (postcode in ('''|| crec.postcode ||'''';
v_where_clause := v_where_clause||v_condition;
v_condition := NULL;
ELSE
v_condition := v_condition||','||''''|| crec.postcode ||'''';
v_where_clause := v_where_clause||v_condition;
v_condition := NULL;
END IF;
END LOOP;
IF v_lp_cnt >0
THEN
v_where_clause := v_where_clause||v_condition||'))';
end if;
v_query := v_query || v_where_clause;
execute immediate v_query into v_result;
p_result := v_result;
end;

Trigger compilation error

I need some help in creating a trigger.
create or replace trigger trigger_one
before insert on Funtom_timesheet
for each row
Declare
V_id number;
V_hours number;
Begin
Select max(timesheet_ID)+1 into v_id from Funtom_timesheet
:new.timesheet_ID :=v_id;
select grade_hours into V_hours
from funtom_grade join funtom_employee
on emp_grade = grade_id
where empid = :new.timesheet_emp;
if V_hours >:new.timesheet_hours
else
:new.timesheet_overtime :=
:new.timesheet_hours-V_hours
:new.timesheet_hours:= V_hours;
END IF;
END;
/
please tell me which part of my code is wrong so I could work on it,
Thanks
You have many syntax errors - missing ; and then. There can't be if only with else part and without expression on it.
CREATE OR REPLACE TRIGGER TRIGGER_ONE
BEFORE INSERT ON FUNTOM_TIMESHEET
FOR EACH ROW
DECLARE
V_ID NUMBER;
V_HOURS NUMBER;
BEGIN
SELECT MAX(TIMESHEET_ID) + 1 INTO V_ID FROM FUNTOM_TIMESHEET;
:NEW.TIMESHEET_ID := V_ID;
SELECT GRADE_HOURS
INTO V_HOURS
FROM FUNTOM_GRADE
JOIN FUNTOM_EMPLOYEE
ON EMP_GRADE = GRADE_ID
WHERE EMPID = :NEW.TIMESHEET_EMP;
IF V_HOURS > :NEW.TIMESHEET_HOURS THEN
NULL;
ELSE
:NEW.TIMESHEET_OVERTIME := :NEW.TIMESHEET_HOURS - V_HOURS;
:NEW.TIMESHEET_HOURS := V_HOURS;
END IF;
END;
/
Also better use SEQUENCES instead of:
SELECT MAX(TIMESHEET_ID) + 1 INTO V_ID FROM FUNTOM_TIMESHEET;
:NEW.TIMESHEET_ID := V_ID;
When selecting form the same table as inserting, you can get MUTATING trigger error (http://www.dba-oracle.com/t_avoiding_mutating_table_error.htm)

IF else condition in sql stored procedure

I'm trying to compile the stored procedure:
create
procedure checkFroud2(code IN varchar2, p_recordset OUT SYS_REFCURSOR)
AS
BEGIN
OPEN p_recordset FOR
if code='C' then
select * from emp
//dbms_output.putline('this is if block')
else if code='F' then
dbms_output.putline('this is else block')
else
dbms_output.putline('last else')
end if;
end checkFroud2;
but this is showing compile time errors. Can anybody suggest where the problem is?
CREATE
PROCEDURE checkFroud2(
code IN VARCHAR2,
p_recordset OUT SYS_REFCURSOR)
AS
BEGIN
IF code='C' THEN
dbms_output.put_line('this is if block');
OPEN p_recordset FOR
SELECT * FROM emp;
ELSIF code='F' THEN
--you can open p_recordset with dummy as
/*open p_recordset for select * from dual where 1 = 0; */
dbms_output.put_line('this is else block');
ELSE
/*open p_recordset for select * from dual where 1 = 0; */
dbms_output.put_line('last else');
END IF;
END checkFroud2;
/
var o refcursor;
BEGIN
CHECKfroud2
('C',:o);
END;
/
PRINT O;
The correct code is as follows:
create procedure checkFroud2(code IN varchar2, p_recordset OUT SYS_REFCURSOR)
AS
BEGIN
OPEN p_recordset FOR
if code='C' then
select * from emp
//dbms_output.putline('this is if block');
elsif code='F' then
dbms_output.putline('this is else block');
else
dbms_output.putline('last else');
end if;
end checkFroud2;
ELSE IF doesn't exist use ELSIF, also remove // before dbms_output.putline('this is if block').
This is an example in oracle 11g
CREATE OR REPLACE PROCEDURE PROC_EMP_CHECK
(
EMPNO1 IN NUMBER
, EMPNO2 IN NUMBER
)
AS
EMPONE_NOT_EXISTS EXCEPTION;
EMPTWO_NOT_EXISTS EXCEPTION;
BOTHEMP_NOT_EXISTS EXCEPTION;
EMPCOUNT1 NUMBER;
EMPCOUNT2 NUMBER;
BEGIN
SELECT COUNT(1) INTO EMPCOUNT1 FROM EMPLOYEES WHERE EMPLOYEE_ID=EMPNO1;
SELECT COUNT(1) INTO EMPCOUNT2 FROM EMPLOYEES WHERE EMPLOYEE_ID=EMPNO2;
BEGIN
IF( EMPCOUNT1=0 AND EMPCOUNT2=0)THEN
RAISE BOTHEMP_NOT_EXISTS;
ELSIF ( EMPCOUNT1=0) THEN
RAISE EMPONE_NOT_EXISTS;
ELSIF ( EMPCOUNT2=0) THEN
RAISE BOTHEMP_NOT_EXISTS;
dbms_output.put_line('ELSE BLOCK');
END IF;
END;
EXCEPTION
WHEN EMPONE_NOT_EXISTS THEN
dbms_output.put_line('EMP One not exit');
WHEN EMPTWO_NOT_EXISTS THEN
dbms_output.put_line('EMP two not exit');
WHEN BOTHEMP_NOT_EXISTS THEN
dbms_output.put_line('both not exit');
WHEN OTHERS THEN
dbms_output.put_line(SQLERRM);
END PROC_EMP_CHECK;
Use ELSIF instead of ELSE IF. By using ELSE IF you're opening up a new nested IF-block, which you're not closing.
Couple of errors:
1. Open record set for ??? For what??
2. ELSIF and no ELSE IF
3. where is ;
CREATE PROCEDURE CHECKFROUD2 ( CODE IN VARCHAR2,
P_RECORDSET OUT SYS_REFCURSOR )
AS
BEGIN
OPEN P_RECORDSET FOR SELECT * FROM DUAL;
IF CODE = 'C'
THEN
SELECT * FROM EMP;
ELSIF CODE = 'F'
THEN
DBMS_OUTPUT.PUTLINE ( 'this is else block' );
ELSE
DBMS_OUTPUT.PUTLINE ( 'last else' );
END IF;
END CHECKFROUD2;