ORA-00984: column not allowed here - Dynamic SQL - sql

This is my package code.
CREATE OR REPLACE PACKAGE BODY FMSSMART.GENERIC_PURGER
AS
PROCEDURE GET_PARTITIONID_JULIAN (i_date IN DATE,
i_number_of_partitions IN NUMBER,
o_partitionID OUT NUMBER)
IS
BEGIN
SELECT MOD (TO_NUMBER (TO_CHAR (TO_DATE (i_date, 'YYYYMMDD'), 'j')),
i_number_of_partitions)
INTO o_partitionID
FROM DUAL;
DBMS_OUTPUT.put_line (o_partitionID);
END GET_PARTITIONID_JULIAN;
PROCEDURE DELETE_TBL_SML
(
i_tablename IN VARCHAR2,
o_retcode OUT NUMBER,
o_errormsg OUT VARCHAR2
)
IS
stmt VARCHAR2(1000);
o_start_time DATE;
o_end_time DATE;
BEGIN
select to_char(sysdate, 'YYYYMMDDHH24MISS') into o_start_time from dual;
stmt := 'DELETE FROM ' ||i_tablename;
EXECUTE IMMEDIATE stmt;
COMMIT;
select to_char(sysdate, 'YYYYMMDDHH24MISS') into o_end_time from dual;
EXCEPTION WHEN OTHERS THEN
o_retcode := SQLCODE;
o_errormsg := substr(SQLERRM, 1, 200);
INSERT INTO AUDIT_PROC_TBL (error_number, error_message, package_name, procedure_name, start_time, end_time) VALUES (o_retcode, o_errormsg, 'GENERIC_PURGER','DELETE_TBL_SML', o_start_time, o_end_time);
return;
END DELETE_TBL_SML;
PROCEDURE INSERT_AUDIT_PROC_TBL
(
i_retcode IN NUMBER,
i_errormsg IN VARCHAR2,
i_package_name IN VARCHAR2,
i_procedure_name IN VARCHAR2,
i_start_time IN DATE,
i_end_time IN DATE
)
IS
BEGIN
EXECUTE IMMEDIATE 'INSERT INTO AUDIT_PROC_TBL (error_number, error_message, package_name, procedure_name, start_time, end_time) VALUES (i_retcode, i_errormsg, i_package_name, i_procedure_name,i_start_time, i_end_time)';
COMMIT;
RETURN;
END INSERT_AUDIT_PROC_TBL;
END GENERIC_PURGER;
/
On Execution:
set autocommit off;
set serveroutput on size 1000000;
ALTER SESSION SET NLS_DATE_FORMAT='YYYYMMDD HH24:MI:SS';
EXECUTE INSERT_AUDIT_PROC_TBL(-1, 'NA', 'GENERIC_PURGER','DELETE_TBL_SML', '20150212164527', '20150212164527');
I encountered an error which gives me:
Session altered.
BEGIN INSERT_AUDIT_PROC_TBL(-1, 'NA', 'GENERIC_PURGER','DELETE_TBL_SML', '20150212164527', '20150212164527'); END;
*
ERROR at line 1:
ORA-00984: column not allowed here
ORA-06512: at "FMSSMART.INSERT_AUDIT_PROC_TBL", line 12
ORA-06512: at line 1

The error you're getting is not from how you're calling the procedure, but what the procedure is doing. The ORA-00984 error is reported against line 12 of the FMSSMART.INSERT_AUDIT_PROC_TBL procedure, which is:
EXECUTE IMMEDIATE 'INSERT INTO AUDIT_PROC_TBL (error_number, error_message, package_name, procedure_name, start_time, end_time) VALUES (i_retcode, i_errormsg, i_package_name, i_procedure_name,i_start_time, i_end_time)';
You're using dynamic SQL here when you don't need to; there's nothing dynamic and static SQL would be fine:
INSERT INTO AUDIT_PROC_TBL (error_number, error_message, package_name,
procedure_name, start_time, end_time)
VALUES (i_retcode, i_errormsg, i_package_name,
i_procedure_name,i_start_time, i_end_time);
For future reference though, when you do use dynamic SQL you need to use bind variables for the values that are passed in; you have a bind placeholder in the dynamic SQL statement which is indicated by a colon, e.g. :var1, and then you supply the actual values with the using clause. At the moment in your original version i_retcode is being interpreted as a column name, not as your variable, which is out of scope to the dynamic context. So you would use something like:
EXECUTE IMMEDIATE 'INSERT INTO AUDIT_PROC_TBL (error_number, '
|| 'error_message, package_name, procedure_name, start_time, end_time) '
|| 'VALUES (:retcode, :errormsg, :package_name, :procedure_name, '
|| ':start_time, :end_time)'
USING i_retcode, i_errormsg, i_package_name, i_procedure_name,
i_start_time, i_end_time;
I've split the statement onto multiple lines for readability; the concatenation via || means the final string is the same as if it was all on one line.
I have a couple of other observations beyond the scope of the question:
You're setting your NLS_DATE_FORMAT in the session and then relying on implicit conversion; using a format that doesn't match your string anyway oddly. It would be better to explicitly pass a date value in your call, e.g. to_date('20150212164527', 'YYYYMMDDHH24MISS') or with a timestamp literal.
It's even worse inside your DELETE_TBL_SML package as you're still relying on the session NLS setting, which you won't always control, and you're explicitly converting dates to strings only to implicitly convert them back. Instead of select to_char(sysdate, 'YYYYMMDDHH24MISS') into o_start_time from dual; just to o_start_time := sysdate).
committing or rolling back in a procedure is usually considered bad practice; it's better for the caller to decide if and when to commit, as it may be doing other things your procedure isn't aware of that should be treated as part of the same transaction.
catching exceptions you don't handle, especially when others, is usually a bug. Although you're returning the code and message to the caller here, they then have to look for it. It's almost always better to let the exception propagate back up to the caller - which can feed into a commit/rollback decision, too.
The delete procedure could be simplified to:
PROCEDURE DELETE_TBL_SML(i_tablename IN VARCHAR2) IS
o_start_time DATE;
o_end_time DATE;
BEGIN
o_start_time := sysdate;
EXECUTE IMMEDIATE 'DELETE FROM ' ||i_tablename;
o_end_time := sysdate;
INSERT INTO AUDIT_PROC_TBL (error_number, error_message, package_name,
procedure_name, start_time, end_time)
VALUES (SQLERRCODE, substr(SQLERRM, 1, 200), 'GENERIC_PURGER',
'DELETE_TBL_SML', o_start_time, o_end_time);
END DELETE_TBL_SML;
unless you only want to audit errors, in which case you would need to enclose the execute in its own sub-block; and then call it as exec FMSSMART.GENERIC_PURGER.DELETE_TBL_SML(<your table name>).

Related

Oracle - Trigger and Procedure compilation errors

I am trying to write a procedure to display a day of the week, but the error I get is that the "ORA-01841: The (full) year must be between -4713 and +9999 and cannot be 0".
The other problem is I made a trigger that checks if column in SCOTT.BONUS.SALARY has been updated and calculates "howmuch" - raise and returns it. It says that NEW.SAL should be declared, but how can it be declared if its a column name... ?
I think im pretty close but I am missing something, Can anyone help please? Much Appreciated.
-- trigger --
CREATE OR REPLACE TRIGGER Raise
BEFORE DELETE OR INSERT OR UPDATE ON SCOTT.BONUS
FOR EACH ROW
WHEN (NEW.SAL > 0.1*OLD.SAL)
DECLARE
howmuch number;
BEGIN
howmuch := 0.1*NEW.SAL;
dbms_output.put_line('Bonus changed to 10% - : ' || howmuch);
END;
/
-- Procedure --
CREATE OR REPLACE PROCEDURE Birth_Day(data IN varchar, Dey OUT varchar) IS
BEGIN
select to_char(date'data', 'Day') INTO Dey from dual;
END;
/
-- Starting procedure Birth_Day --
DECLARE
Dey varchar(20);
begin
Birth_Day('10/11/2020',Dey);
end;
This expression is not right:
to_char(date'data', 'Day')
The database tries to evaluate literal string 'data' as a date in ISO format ('YYYY-MM-DD'), and fails.
You need to use to_date() first to turn your variable string to a date, and then to_char():
to_char(to_date(data, 'DD/MM/YYYY'), 'Day')

Ways of handling unknown data type in oracle

I have a statement that executes a sql like this:
execute immediate cursor_rule.rule_sql into rule_result ;
my problem is that the output of rule_sql can be anything from null, to boolean to a number.
How do I define rule_result in a situation like this?
You can use:
DECLARE
rule_result VARCHAR2(4000);
BEGIN
EXECUTE IMMEDIATE :your_sql INTO rule_result;
EXCEPTION
WHEN NO_DATA_FOUND THEN
NULL; -- Handle what should happen when the SQL returns zero rows.
WHEN TOO_MANY_ROWS THEN
NULL; -- Handle what should happen when the SQL returns two or more rows.
END;
/
If the result of your sql statement is a:
String data type then it gets stored in the rule_result as is.
numeric data type then Oracle will implicitly call TO_CHAR on it to convert it to a VARCHAR2 value exactly long enough to hold its significant digits.
DATE data type then Oracle will implicitly call TO_CHAR( date_value, NLS_DATE_FORMAT ) using the NLS_DATE_FORMAT session parameter as the format model to convert it to a string.
TIMESTAMP data type then Oracle will implicitly call TO_CHAR( timestamp_value, NLS_TIMESTAMP_FORMAT ) using the NLS_TIMESTAMP_FORMAT session parameter as the format model to convert it to a string.
You can parse the SQL statement using DBMS_SQL to discover the column data type. For example:
declare
l_cursor_id pls_integer := dbms_sql.open_cursor;
l_pointless_count pls_integer;
l_desc_cols dbms_sql.desc_tab;
l_sql long := 'select dummy as teststring, 123 as testnum, sysdate as testdate from dual';
begin
dbms_sql.parse(l_cursor_id, l_sql, dbms_sql.native);
dbms_sql.describe_columns(l_cursor_id, l_pointless_count, l_desc_cols);
for i in 1..l_desc_cols.count loop
dbms_output.put_line
( rpad(l_desc_cols(i).col_name,31) || lpad(l_desc_cols(i).col_type,4) );
end loop;
dbms_sql.close_cursor(l_cursor_id);
end;
Output:
TESTSTRING 1
TESTNUM 2
TESTDATE 12
Type codes are defined in DBMS_TYPES and the documentation (which as I discovered last week do not necessarily agree).

How to insert old date into a table correctly?

I am trying to insert an old date (01/01/1888) into a table within pl/sql code, but I only got value (01/01/1988) inserted.
What will be the correct way to insert this old date into a table?
declare
p_date date;
p_eventDate date;
sql_str varchar2(2000);
begin
select to_date('01-01-1888','dd-mm-yyyy') into p_eventDate from dual;
p_date := add_months(sysdate,-60);
if( p_eventDate < p_date)
then
sql_str := 'insert into test_date values (''' ||p_eventDate ||''')';
sql_str := 'insert into test_date values (to_date(''' ||p_eventDate ||''',''dd-mm-yyyy''))';
else
sql_str := 'insert into test_date values (''' ||p_date ||''')';
end if;
EXECUTE IMMEDIATE sql_str;
commit;
--dbms_output.put_line(sql_str);
end;
When I check the table test_date, value 01/01/1988 and 01/01/0088 inserted.
Thanks!
I think you code can be simplified.
If I understand well, you need to insert the minimum value from two variables; if so, you don't need dynamic SQL:
declare
p_date date;
p_eventDate date;
begin
select to_date('01-01-1888','dd-mm-yyyy') into p_eventDate from dual;
p_date := add_months(sysdate,-60);
insert into test_date(d) values ( least(p_date, p_eventDate));
commit;
end;
Keeping aside the dynamic SQL, the main issue in your code is that you are using dates in strings without casting them, thus relying on implicit conversions.
If, for some reason, you want to use dynamic SQL, a good way could be with bind variables, with something like:
...
sql_str := 'insert into test_date values (:1)';
execute immediate sql_str using someVariable;
...
If you want to keep the structure of your code, your SQL string should be:
sql_str := 'insert into test_date values ( to_date(''' || to_char(p_date, 'dd-mm-yyyy') || ''', ''dd-mm-yyyy''))';
that is, you first have to cast the date variable to string, use it to concatenate your statement and then, in within your statement, cast back the string to a date type.

Error in Oracle (PLS-00103: Encountered the symbol "="...) trying to select into a variable for use in a cursor

I'm creating a procedure. I'm selecting a max date from a table into a var, and then intending to use that var in sql for a cursor. It looks like:
l_max_update_date DATE;
--Param var
l_max_update_date := NULL;
SELECT max(pivlog.last_update_date) as maxdate into l_max_update_date
FROM piv_verification_log pivlog;
...and then...
--No data in log table? Create a max_update_date
IF l_max_update_date IS NULL
THEN
l_max_update_date := TO_TIMESTAMP('2014/SEP/1 00:00:00', 'YYYY/MON/DD HH24:MI:SS');
END IF;
Finally...
--Get affected employees (those who have certified since the max(last_update_date) listed in the log table)
CURSOR affected_employees_cursor
IS
SELECT [columns]
FROM [tables]
WHERE LAST_UPDATE_DATE > l_max_update_date;
But, whenever I compile, I get this error message:
[Error] PLS-00103 (47: 22): PLS-00103: Encountered the symbol "=" when expecting one of the following:
constant exception
table long double ref
char time timestamp
which points at this line:
l_max_update_date := NULL;
I appreciate your insight. I'm thinking it has to do with the order or location in the procedure where I'm defining the var and cursor(?).
Thank you.
You can't have executable code before the first BEGIN. It would help if you'd post all the code for your procedure, but given what can be seen above it looks to me like your procedure should be something like:
CREATE OR REPLACE PROCEDURE SOME_PROC AS
l_max_update_date DATE := NULL; -- not really needed - variables are
-- initialized to NULL if no other
-- initial value is given.
CURSOR affected_employees_cursor IS
SELECT [columns]
FROM [tables]
WHERE LAST_UPDATE_DATE > l_max_update_date;
rowAffected_employee affected_employees_cursor%ROWTYPE;
BEGIN
BEGIN
SELECT max(pivlog.last_update_date) as maxdate
into l_max_update_date
FROM piv_verification_log pivlog;
EXCEPTION
WHEN NO_DATA_FOUND THEN
l_max_update_date := NULL;
END;
--No data in log table? Create a max_update_date
IF l_max_update_date IS NULL THEN
l_max_update_date := TO_DATE('2014/SEP/1 00:00:00', 'YYYY/MON/DD HH24:MI:SS');
END IF;
OPEN affected_employees_cursor;
LOOP
FETCH affected_employees_cursor
INTO rowAffected_employee;
EXIT WHEN affected_employees_cursor%NOTFOUND;
-- do something useful with the data fetched from the cursor
END LOOP;
CLOSE affected_employees_cursor;
END SOME_PROC;
Share and enjoy.
Further to Bob's answer, the cursor will use whatever value l_max_update_date has at the point the cursor is opened, so it doesn't have to be set before the cursor is declared.
If you'd prefer that to be more obvious in your code then you could also pass the date to the cursor as a parameter:
CURSOR affected_employees_cursor (p_last_update_date DATE) IS
SELECT [columns]
FROM [tables]
WHERE LAST_UPDATE_DATE > p_max_update_date;
and then call it with:
OPEN affected_employees_cursor (l_max_update_date);
Or you could combine the lookup-up into the cursor definition, as long as you only open it once, and skip the separate look-up and check:
CREATE OR REPLACE PROCEDURE SOME_PROC AS
CURSOR affected_employees_cursor IS
SELECT [columns]
FROM [tables]
WHERE LAST_UPDATE_DATE > (
SELECT COALESCE(MAX(pivlog.last_update_date), DATE '2014-09-01')
FROM piv_verification_log pivlog
);
rowAffected_employee affected_employees_cursor%ROWTYPE;
BEGIN
OPEN affected_employees_cursor;
LOOP
FETCH affected_employees_cursor
INTO rowAffected_employee;
EXIT WHEN affected_employees_cursor%NOTFOUND;
-- do something useful with the data fetched from the cursor
END LOOP;
CLOSE affected_employees_cursor;
END SOME_PROC;
/
Or even simpler use an implicit cursor loop:
CREATE OR REPLACE PROCEDURE SOME_PROC AS
BEGIN
FOR rowAffected_employee In (
SELECT [columns]
FROM [tables]
WHERE LAST_UPDATE_DATE > (
SELECT COALESCE(MAX(pivlog.last_update_date), DATE '2014-09-01')
FROM piv_verification_log pivlog
)
)
LOOP
-- do something useful with the data fetched from the cursor
END LOOP;
END SOME_PROC;
/
Of course, depending on what you're doing with the data fetched form the cursor, this might be something that doesn't need PL/SQL at all and could be done in plain SQL.

pl/sql - to_date not working with execute immediate parameter

i wanna be able to execute my below proc like so:
exec procname('29-JAN-2011');
proc code is:
PROCEDURE procname(pardate VARCHAR2) IS
vardate DATE := to_date(pardate, 'DD-MON-YYYY');
SQLS VARCHAR2(4000);
BEGIN
SQLS := 'SELECT cola, colb
FROM tablea
WHERE TRUNC(coldate) = TRUNC(TO_DATE('''||pardate||''',''DD/MON/YYYY''))';
EXECUTE IMMEDIATE SQLS;
END;
It keeps throwing error:
ORA-00904: "JAN": invalid identifier.
It compiles, but it throws the error when I run this command:
EXEC procname('29-JAN-2011');
You declare a variable which casts the input parameter to a date: why not use it?
Also, the TRUNC() applied to a date removes the time element. You don't need it here because the value you're passing has no time.
So, your code should be:
PROCEDURE procname(pardate VARCHAR2) IS
vardate DATE := to_date(pardate, 'DD-MON-YYYY');
SQLS VARCHAR2(4000) := 'select cola, colb FROM tablea
WHERE TRUNC(coldate) = :1';
l_a tablea.cola%type;
l_b tablea.colb%type;
BEGIN
EXECUTE IMMEDIATE SQLS
into l_a, l_b
using vardate;
END;
Specifying the dynamic SQL statement with a bind variable and executing it with the USING syntax is a lot more efficient. Note that we still have to SELECT into some variables.
You're using two different notations in the two calls to to_date. I think one of them (the second) is wrong.