I'm using Oracle SQL Developer to test my database request just before I inject this request on my Talend project.
Here is an exampl. I have a request that is working fine on SQL Developer tools but not on my Talend project.
My sql statment has a function declaration and then an select like this:
create or replace function updateDate(p_date varchar2) return date as
l_date date;
e_bad_day exception;
pragma exception_init (e_bad_day, -1847);
begin
begin
-- try to convert
l_date := to_date(p_date,'yyyymmdd');
exception
when e_bad_day then
-- ignore the supplied day value and get last day of month
l_date := last_day(to_date(substr(p_date, 1, 6), 'yyyymm'));
end;
return l_date;
end;
/
Select ASRF_NUMASR NIR,
ASSUR_NOASSURE NOASSURE,
ASRF_CODSEX sexe,
updateDate(ASSUR_DATNAIS) as DATE_REAL
from NORMAL_ASSUR
UNION
Select ASRF_NUMASR NIR,
ASSUR_NOASSURE NOASSURE,
When i put the same text on my Talend project it's not working. It seems that it only executes my function declaration.
The exception is:
ORA-01003 aucune instruction analysé
Although I wouldn't recommend using Talend to create/replace the updateDate function at each execution of the job, as it's better to create it beforehand in your sql developer, you can try by separating your sql script using 2 components :
The DDL part of the script goes in a tOracleRow :
"create or replace function updateDate(p_date varchar2) return date as
l_date date;
e_bad_day exception;
pragma exception_init (e_bad_day, -1847);
begin
begin
-- try to convert
l_date := to_date(p_date,'yyyymmdd');
exception
when e_bad_day then
-- ignore the supplied day value and get last day of month
l_date := last_day(to_date(substr(p_date, 1, 6), 'yyyymm'));
end;
return l_date;
end;"
The DML part goes in a tOracleInput (and setting the corresponding schema on the component)
"Select ASRF_NUMASR NIR,
ASSUR_NOASSURE NOASSURE,
ASRF_CODSEX sexe,
updateDate(ASSUR_DATNAIS) as DATE_REAL
from NORMAL_ASSUR
UNION
Select ASRF_NUMASR NIR,
ASSUR_NOASSURE NOASSURE,
..."
And call them like this :
tOracleRow
|
OnSubjobOk
|
tOracleInput -- Main -- target
Related
create sequence s1 ;
declare
v_value number;
v_sql_stmt varchar2(4000);
v_seq_name varchar2(30);
BEGIN
v_seq_name:='S1'; -- **this is dynamic and the sequence will be passed in the proc as input parameter at runtime**
v_sql_stmt:= 'select :v_seq_name'||'.nextval from dual' ;
EXECUTE IMMEDIATE v_sql_stmt INTO v_value USING v_seq_name ;
--**below is working but I dont want to do in this way because of sql injection issue, let me know how to fix the above**
--EXECUTE IMMEDIATE 'select ' || v_seq_name || '.nextval from dual' INTO v_value;
dbms_output.put_line(v_value);
end;
/
the above code is throwing error, please help to fix.
If you run the commented code then it will run but I dont want to use || in execute immediate. I want to use colon : only.
the sequence name will be passed at run time. The above code will be converted to a proc later.
I understand your concern about SQL injection. To my knowledge, table/column/sequence names cannot be specified with bind variables. However, you could do a simple check before executing the unsafe code:
CREATE SEQUENCE s1;
CREATE SEQUENCE s2;
CREATE OR REPLACE FUNCTION p(seq_name VARCHAR2) RETURN NUMBER AS
v_value number;
v_sql_stmt varchar2(4000);
v_seq_name varchar2(128 BYTE);
BEGIN
v_seq_name:= DBMS_ASSERT.SIMPLE_SQL_NAME(seq_name);
v_sql_stmt:= 'select '||v_seq_name||'.nextval from dual';
EXECUTE IMMEDIATE v_sql_stmt INTO v_value;
RETURN v_value;
END p;
/
If a valid name is used, everything works as expected:
select p('s1') from dual;
1
select p('s2') from dual;
2
However, if seq_name is not a valid Oracle name, DBMS_ASSERT throws an exception:
select p('1; DROP TABLE x') from dual;
ORA-44003: invalid SQL name
ORA-06512: at "SYS.DBMS_ASSERT", line 215
ORA-06512: at "WFL.P", line 6
44003. 0000 - "invalid SQL name"
I have a code in oracle pl sql, want to really want to understand how much context switching is there
If x=0 then
curserx= select a from mytable1;
Else
curserx=select a from mytable1 where id=:x;
End;
Loop
Fetch on cursorx
Select c from mytable2 where a=curserx.a;
End loop;
This is just a sample code so please pardon any text casing and logic error.
I converted your pseudo code into PL/SQL and include comments indicating where I believe you will have a context switch from the PL/SQL engine to the SQL engine.
Note that if you are querying a non-trivial number of rows, you could use FETCH BULK COLLECT INTO and retrieve multiple rows with each fetch, greatly reducing context switches.
DECLARE
l_x_value INTEGER;
l_cursor SYS_REFCURSOR;
l_fetched mytble1.a%TYPE;
BEGIN
/* context switch to open */
IF x = 0
THEN
OPEN l_cursor FOR SELECT a FROM mytable1;
ELSE
OPEN l_cursor FOR
SELECT a
FROM mytable1
WHERE id = l_x_value;
END IF;
LOOP
/* context switch per fetch */
FETCH l_cursor INTO l_fetched;
EXIT WHEN l_cursor%NOTFOUND;
/* context switch for implicit cursor */
SELECT c
INTO l_fetched
FROM mytable2
WHERE a = curserx.a;
END LOOP;
/* context switch to close */
CLOSE l_cursor;
END;
But that's not all! Remember that the context switch works both ways: SQL -> PL/SQL and PL/SQL -> SQL. You can reduce the overhead of going from SQL to PL/SQL by declaring your function with the UDF pragma (12c+) or defining it with the WITH FUNCTION clause (also 12c+). There is still a context switch but some of the work is done at compile time instead of run time.
So in the code below, for each invocation of the function from within the SELECT, there is a switch.
CREATE OR REPLACE FUNCTION full_name (first_in IN VARCHAR2,
last_in IN VARCHAR2)
RETURN VARCHAR2
IS
BEGIN
RETURN first_in || ' ' || last_in;
END;
/
DECLARE
l_name VARCHAR2 (32767);
BEGIN
SELECT full_name (first_name, last_name) INTO l_name
FROM employees
WHERE employee_id = 100;
DBMS_OUTPUT.PUT_LINE (l_name);
END;
/
Finally a cautionary note: you should do everything you can to avoid executing SQL inside a function that is then called from SQL. The standard read consistency model that works for your SQL statement will not be "carried in" to the function's SQL. In other words, if you "outer" SELECT starts running at 10:00 and runs for an hour, and at 10:05, someone deletes rows from a table that is used in both the outer query and the query in the function (and commits), those two queries will be working with different states of those tables.
I noticed that TO_TIMESTAMP raises a variety of error codes depending on how it failed;
--01847
select to_timestamp('2016-01-0a', 'yyyy-MM-dd') from dual;
--01858
select to_timestamp('2016-01-aa', 'yyyy-MM-dd') from dual;
--01843
select to_timestamp('2016-15-01', 'yyyy-MM-dd') from dual;
Given that it doesn't raise a single error code, how can I catch the error without resorting to using the catch-all OTHERS:
DECLARE
foo timestamp;
BEGIN
select to_timestamp('2016-01-0a', 'yyyy-MM-dd') into foo from dual;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line('bad date');
END;
If you are on 12cR2, then you can use VALIDATE_CONVERSION to check the validity of a potential type conversion before making the conversion and avoid the exception entirely.
In earlier versions, you could create your own equivalent function for TIMESTAMPs that attempts the conversion and handles the exceptions. You could declare each exception-type of interest individually and log/handle the failure-modes of interest separately for each as needed, with an overall WHEN OTHERS for the stuff you don't care about or want to log/handle:
CREATE OR REPLACE FUNCTION IS_TIMESTAMP_FORMAT_OK(P_TIMESTAMP_TEXT IN VARCHAR2, P_FORMAT IN VARCHAR2 DEFAULT 'yyyy-MM-dd')
RETURN BOOLEAN
IS
--exception names
V_TIMESTAMP TIMESTAMP;
DAY_OF_MONTH EXCEPTION;
NON_NUMERIC EXCEPTION;
--etc.
PRAGMA EXCEPTION_INIT (DAY_OF_MONTH, -1847);
PRAGMA EXCEPTION_INIT (NON_NUMERIC, -1858);
BEGIN
V_TIMESTAMP := to_timestamp(P_TIMESTAMP_TEXT, P_FORMAT);
RETURN TRUE;
EXCEPTION WHEN DAY_OF_MONTH
THEN
DBMS_OUTPUT.PUT_LINE('The day of month must be between...');
RETURN FALSE;
WHEN NON_NUMERIC
THEN
DBMS_OUTPUT.PUT_LINE('Non-Numeric data was found...');
RETURN FALSE;
--etc.
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(UTL_LMS.FORMAT_MESSAGE('Unexpected timestamp problem: %s', SQLERRM));
RETURN FALSE;
END;
/
Then you can log/handle the types of interest:
DECLARE
V_CHECK BOOLEAN;
BEGIN
V_CHECK := IS_TIMESTAMP_FORMAT_OK('2016010a');
V_CHECK := IS_TIMESTAMP_FORMAT_OK('2016-01-aa');
V_CHECK := IS_TIMESTAMP_FORMAT_OK('2016-01-0a');
IF IS_TIMESTAMP_FORMAT_OK('2014-01-01')
THEN
DBMS_OUTPUT.PUT_LINE('It is ok. Yay');
END IF;
END;
/
Unexpected timestamp problem: ORA-01862: the numeric value does not match the
length of the format item
Non-Numeric data was found...
The day of month must be between...
It is ok. Yay
Or if you don't care about logging/handle different failure modes and just want to prevent broad exception-catching, you can go ahead and use WHEN OTHERS, but in isolated scope:
CREATE OR REPLACE FUNCTION
IS_TIMESTAMP_FORMAT_OK(P_TIMESTAMP_TEXT IN VARCHAR2, P_FORMAT IN VARCHAR2 DEFAULT 'yyyy-MM-dd')
RETURN BOOLEAN
IS
V_TIMESTAMP TIMESTAMP;
BEGIN
V_TIMESTAMP := to_timestamp(P_TIMESTAMP_TEXT, P_FORMAT);
RETURN TRUE;
EXCEPTION WHEN OTHERS THEN
RETURN FALSE;
END;
/
Or Inline:
DECLARE
V_MY_TIMESTAMP TIMESTAMP;
BEGIN
-- some other code ...
BEGIN
V_MY_TIMESTAMP := to_timestamp('2016-01-aa', 'yyyy-MM-dd');
EXCEPTION WHEN OTHERS THEN NULL;
END;
DBMS_OUTPUT.PUT_LINE('My Timestamp:'||V_MY_TIMESTAMP);
END;
/
My Timestamp:
I was wondering if there is a way to find dynamic SQL statements whose execution broke during runtime in case there was no proper exception handling in the PL/SQL program unit that called the dynanic SQL.
procedure will_crash is
begin
-- 1000 dynamic sql statements here ..
execute immediate 'updaXte dual set X = ''Z'' ' ;
-- ... and 1000 more dynamic sql statements here ..
commit;
end; --> NO proper exception handling for logging the last sql statement
In case a nighttime scheduled program unit contains hundreds of dynamic sql statements, I'd like to find out which one broke without debugging. Does Oracle log anything in its system views?
In you're example there's no need at all to use dynamic SQL, so the easiest solution is to rewrite it as
procedure will_crash is
begin
-- 1000 dynamic sql statements here ..
updaXte dual set X = ''Z''; -- Error at compile time!!
-- ... and 1000 more dynamic sql statements here ..
commit;
end; --> NO proper exception handling for logging the last sql statement
If the real procedure uses dynamic SQL because the SQL statements are saved elsewhere (variables or SQL table), and we're talking only about syntax error, you can achieve your objective before executing statements using DBMS_SQL package.
Here's an example:
DECLARE
lc_good VARCHAR2(200) := 'SELECT 1 FROM dual';
lc_bad VARCHAR2(200) := 'SEL1ECT a FROM dual';
lc_cursor NUMBER;
BEGIN
lc_cursor := SYS.DBMS_SQL.OPEN_CURSOR;
BEGIN
SYS.DBMS_SQL.PARSE(lc_cursor, lc_good, DBMS_SQL.V7);
dbms_output.put_line('Statement 1 is GOOD');
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line('Statement 1 is BAD: ' || SQLERRM);
END;
BEGIN
SYS.DBMS_SQL.PARSE(lc_cursor, lc_bad, DBMS_SQL.V7);
dbms_output.put_line('Statement 2 is GOOD');
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line('Statement 2 is BAD: ' || SQLERRM);
END;
SYS.DBMS_SQL.close_cursor(lc_cursor);
END;
The function output is the following:
Statement 1 is GOOD
Statement 2 is BAD: ORA-00900: ...
Note that the code only parses the SQL statements, it don't executes them.
This is the code i used in stored procedure;
CREATE OR REPLACE PROCEDURE MY_STORE_PROCEDURE (new_date in date)
IS
BEGIN
execute immediate 'INSERT INTO TEMP_1 ( ID CHAR(10),
A_CNT NUMBER,
JOIN_DT DATE,
)
SELECT
L1.ID,
L1.A_CNT,
L1.JOIN_DT,
FROM ACTVY_1 L1
WHERE L1.JOIN_DT = new_date';
END;
===========================================================
Below is the code i used to call store procedure with passing value. value is date which store procedure reciece and used to pull date from a table. but it is giving me error.
DECLARE
a_date DATE;
BEGIN
a_date :=to_DATE ('01-NOV-2013', 'DD-MON-YYYY');
MY_STORE_PROCEDURE(a_date);
END;
Please suggest is there any syntax error or what is issue.
Based on your example, there is no reason to use dynamic SQL. You also have a bunch of errors. Try this:
CREATE OR REPLACE PROCEDURE MY_STORE_PROCEDURE (new_date IN DATE)
IS
BEGIN
INSERT INTO TEMP_1 (ID, A_CNT, JOIN_DT)
SELECT L1.ID, L1.A_CNT, L1.JOIN_DT
FROM ACTVY_1 L1
WHERE L1.JOIN_DT = new_date;
END;