How to insert old date into a table correctly? - sql

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.

Related

sql injection - risk with SELECT

I would like to read user query from variable as (:user_query:) and execute it - in Oracle it will be like:
DECLARE
ddl_qry CLOB;
user_query VARCHAR2(20) := 'SELECT 100 FROM dual';
BEGIN
ddl_qry := 'INSERT INTO a (v) VALUES ((SELECT CAST(( ' || user_query || ') AS NUMBER) as COUNTER FROM dual))';
EXECUTE IMMEDIATE ddl_qry;
END;
I need to insert result of user_query to table 'a'.
I don't care if it will fail or sth, but is this safe? :) Is there any option if string user_query with SQL will drop my database or do sth else?
Or sb can construct a query that will drop my database?
If we fix the (many) syntax errors in your PL/SQL block we come to:
DECLARE
ddl_qry CLOB;
user_query VARCHAR2(20) := '1024';
BEGIN
ddl_qry := 'INSERT INTO a (v) VALUES ((SELECT CAST(( :user_query ) AS NUMBER) as COUNTER FROM dual))';
EXECUTE IMMEDIATE ddl_qry USING user_query;
END;
/
It will work and there is not a SQL injection vulnerability as it uses a bind variable :user_query to input the value.
However, that does not mean that it is particularly good as you:
Do not need to use dynamic SQL;
Do not need to select from the DUAL table; and
Do not need to explicitly CAST the input to a NUMBER as, if the column you are inserting into is a NUMBER then, there will be an implicit cast.
So the above code can be simplified to:
DECLARE
user_query VARCHAR2(20) := '1024';
BEGIN
INSERT INTO a (v) VALUES ( user_query );
END;
/
There is still no SQL injection vulnerability and the query is much simpler.
db<>fiddle here
Update
in Oracle it will be like:
DECLARE
ddl_qry CLOB;
user_query VARCHAR2(20) := 'SELECT 100 FROM dual';
BEGIN
ddl_qry := 'INSERT INTO a (v) VALUES ((SELECT CAST(( ' || user_query || ') AS NUMBER) as COUNTER FROM dual))';
EXECUTE IMMEDIATE ddl_qry;
END;
That has huge SQL injection vulnerabilities.
If user_query is, instead, set to:
SELECT CASE
WHEN EXISTS(
SELECT 1
FROM users
WHERE username = 'Admin'
AND password_hash = STANDARD_HASH( 'my$ecretPassw0rd', 'SHA256' )
)
THEN 100
ELSE 0
END
FROM DUAL
If you get the value 100 then you know that:
There is a table called users;
It has columns username and password_hash;
There is an Admin user; and
You've verified their password.
Please don't use dynamic SQL and string concatenation if you do not need to.

delete query using execute Immediate

I want convert varchar2 into date but its occur error give me solution
DECLARE
ADATE VARCHAR2(100):='02/20/1981';
BEGIN
EXECUTE IMMEDIATE 'DELETE FROM emp WHERE HIREDATE= '||to_char(to_date(ADATE,'mm/dd/yy'),'mm/dd/yy');
DBMS_OUTPUT.PUT_LINE('DELETE');
END;
You can also use this one:
DECLARE
ADATE VARCHAR2 (100) := '02/20/1981';
BEGIN
EXECUTE IMMEDIATE
'DELETE FROM emp WHERE HIREDATE= :aDate' using TO_DATE(ADATE, 'mm/dd/yyyy');
DBMS_OUTPUT.PUT_LINE ('DELETE');
COMMIT;
END;
You can use this. Make sure to use USING clause to pass the bind variable to avoid SQL injection. Also as mentioned by #Kaushik, there is no need to do TO_CHAR.
DECLARE
ADATE VARCHAR2 (100) := '02/20/1981';
BEGIN
EXECUTE IMMEDIATE
'DELETE FROM emp WHERE HIREDATE= TO_DATE(:ADATE, ''mm/dd/yyyy'')' using adate;
DBMS_OUTPUT.PUT_LINE ('DELETE');
COMMIT;
END;
You don't need to convert it to TO_CHAR. The quotes have to be right and use proper date formats.
DECLARE
ADATE VARCHAR2(100):='02/20/1981';
BEGIN
EXECUTE IMMEDIATE 'DELETE FROM emp WHERE HIREDATE=to_date('''||ADATE||''',''mm/dd/yyyy'')';
DBMS_OUTPUT.PUT_LINE('DELETE');
END;
For your delete operation, you don't even need an EXECUTE IMMEDIATE. A block like this should be sufficient.
DECLARE
ADATE VARCHAR2(100):='02/20/1981';
BEGIN
DELETE FROM emp WHERE HIREDATE=to_date(ADATE,'mm/dd/yyyy');
DBMS_OUTPUT.PUT_LINE('DELETE');
END;

ORA-00984: column not allowed here - Dynamic 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>).

PL/SQL Dynamic Loop Value

My goal is to keep a table which contains bind values and arguments, which will later be used by dbms_sql. The below pl/sql example is basic, it's purpose is to illustrate the issue I am having with recalling values from prior loop objects.
The table account_table holds acccount information
CREATE TABLE account_table (account number, name varchar2(100)));
INSERT INTO mytest
(account, name)
VALUES
(1 ,'Test');
COMMIT;
The table MYTEST holds bind information
CREATE TABLE mytest (bind_value varchar2(100));
INSERT INTO mytest (bind_value) VALUES ('i.account');
COMMIT;
DECLARE
v_sql VARCHAR2(4000) := NULL;
v_ret VARCHAR2(4000) := NULL;
BEGIN
FOR I IN (
SELECT account
FROM account_table
WHERE ROWNUM = 1
) LOOP
FOR REC IN (
SELECT *
FROM mytest
) LOOP
v_sql := 'SELECT ' || rec.bind_value || ' FROM dual';
EXECUTE IMMEDIATE v_sql INTO v_ret;
dbms_output.put_line ('Account: ' || v_ret);
END LOOP;
END LOOP;
END;
/
I cannot store the name i.account and later use the value that object. My idea was to use NDS; but, while the value of v_sql looks ok (it will read "Select i.account from dual"), an exception of invalid identifier would be raised on i.account. Is there a way to get the value of the object? We are using Oracle 11g2.
Thanks!
Dynamic SQL will not have the context of your PL/SQL block. You cannot use identifiers that are defined in the PL/SQL block in your dynamic SQL statement. So you cannot reference i.account and reference the loop that you have defined outside of the dynamic SQL statement. You could, of course, dynamically generate the entire PL/SQL block but dynamic PL/SQL is generally a pretty terrible approach-- it is very hard to get that sort of thing right.
If you are trying to use the value of i.account, however, you can do something like
v_sql := 'SELECT :1 FROM dual';
EXECUTE IMMEDIATE v_sql
INTO v_ret
USING i.account;
That doesn't appear to help you, though, if you want to get the string i.account from a table.
Are you always trying to access the same table with the same where clause, but just looking for a different column each time? If so, you could do this:
p_what_I_want := 'ACCOUNT';
--
SELECT decode(p_what_I_want
,'ACCOUNT', i.account
, 'OTHER_THING_1', i.other_thing_1
, 'OTHER_THING_2', i.other_thing_2
, 'OTHER_THING_1', i.default_thing) out_thing
INTO l_thing
FROM table;
The above statement is not dynamic but returns a different column on demand...

writing a generic procedure in oracle

i want to write procedure which accents name of 2 tables as arguments and then compare the number or rows of the 2.
Also i want to each field of the 2 columns.The row which has a missmatch shold be
moved to another error table.
Can anyone give a PL/SQL procedure for doing this.
I want to achive this in oracle 9
Pablos example wont work, the idea is right though.
Something like this do it.
create or replace PROCEDURE COMPARE_ROW_COUNT(T1 IN VARCHAR2, T2 IN VARCHAR2) AS
v_r1 number;
v_r2 number;
v_sql1 varchar2(200);
v_sql2 varchar2(200);
BEGIN
v_sql1 := 'select count(1) from ' || T1;
v_sql2 := 'select count(1) from ' || T2;
EXECUTE IMMEDIATE v_sql1 into v_r1;
EXECUTE IMMEDIATE v_sql2 into v_r2;
dbms_output.put_line(T1 || ' count = ' || v_r1 || ', ' || T2 || ' count = ' || v_r2);
END;
DBMS_SQL is your friend for such operations.
You can use dynamic sql in PL/SQL. EXECUTE IMMEDIATE is your friend.
So, if you take two table names and trying to compare their row counts, you would do something like:
CREATE OR REPLACE PROCEDURE COMPARE_ROW_COUNT(T1 IN VARCHAR2(200), T2 IN VARCHAR2(200)) AS
v_cursor integer;
v_r1 integer;
v_r2 integer;
v_sql varchar2(200);
BEGIN
v_sql := "select count(1) into :1 from " || T1;
EXECUTE IMMEDIATE v_sql USING v_r1;
v_sql := "select count(1) into :1 from " || T2;
EXECUTE IMMEDIATE v_sql USING v_r2;
-- compare v_r1 and v_r2
END;
Not 100% sure about PL/SQL syntax. It's been a while since the last time I coded in great PL/SQL!
You can achieve same results with similar approach using DBMS_SQL. Syntax is a little bit more complicated though.
I am just posting here to note that all answers gravitate around dynamic SQL, and do not turn the attention to the implied problems using it.
Consider passing the following string as first or second parameter:
dual where rownum = 0 intersect
SELECT 0 FROM dual WHERE exists (select 1 from user_sys_privs where UPPER(privilege) = 'DROP USER')
I'll leave it to that.
To answer your question - Oracle actually stores these values in the data dictionary, so if you have access to it:
CREATE OR REPLACE PROCEDURE COMPARE_ROW_COUNT(T1 IN VARCHAR2, T2 IN VARCHAR2) AS
v_text varchar2(1000);
BEGIN
select listagg(owner || ' ' || table_name || ' count = ' || num_rows, ',')
into v_text
from all_tables --user, all or dba tables depends on requirements
where table_name in (T1, T2);
dbms_output.put_line(v_text);
exception
when others then raise; -- Put anything here, as long as you have an exception block
END COMPARE_ROW_COUNT;