Dynamic PL/SQL date parameter with time value retained - oracle

It might be a silly problem but I cant find a solution with "DATE" type passed in a PL/SQL proc which is called dynamically. What I need is to pass both date and time parts in the called proc:
create or replace
PROCEDURE DATE_TIME_TEST ( dte_Date_IN IN DATE )
IS
vch_SQL_Stmnt VARCHAR2(2000);
BEGIN
DBMS_OUTPUT.PUT_LINE('Date is :'||TO_CHAR(dte_Date_IN, 'DD-Mon-YYYY HH24:MI:SS'));
END;
/
declare
v_sql varchar2(2000);
begin
v_sql := 'begin DATE_TIME_TEST( dte_Date_IN => '''||
sysdate || ''''|| '); end;';
execute immediate v_sql;
end;
/
The output here is - Date is :27-Aug-2013 00:00:00.
I want it to be - Date is :27-Aug-2013 13:01:09

Use bind variables
SQL> create or replace procedure proc( p_dt in date )
2 as
3 begin
4 dbms_output.put_line( to_char( p_dt, 'yyyy-mm-dd hh24:mi:ss' ));
5 end;
6 /
Procedure created.
SQL> declare
2 l_sql varchar2(1000);
3 begin
4 l_sql := 'begin proc(:dt); end;';
5 execute immediate l_sql using sysdate;
6 end;
7 /
2013-08-26 22:14:26
PL/SQL procedure successfully completed.
The problem with your code is that in order to build up your string, Oracle has to convert the DATE to a VARCHAR2. It does that using your session's NLS_DATE_FORMAT. But your session's NLS_DATE_FORMAT probably doesn't include the time component so the time is lost when your procedure is actually called. Using bind variables means that you don't have to deal with that sort of implicit conversion (it is also more efficient and more secure).
If you really wanted to avoid using bind variables, you could explicitly cast sysdate to a string using a to_char and then put a to_date in the dynamic procedure call. But that's a lot of extra code and a number of unnecessary conversions.
SQL> ed
Wrote file afiedt.buf
1 declare
2 l_sql varchar2(1000);
3 begin
4 l_sql := q'{begin proc(to_date('}' ||
5 to_char(sysdate, 'yyyy-mm-dd hh24:mi:ss') ||
6 q'{', 'yyyy-mm-dd hh24:mi:ss')); end;}';
7 execute immediate l_sql;
8* end;
SQL> /
2013-08-26 22:19:52
PL/SQL procedure successfully completed.

Related

How to execute Dynamic sql with insert statement in Oracle

Below sql command is not working in procedure
PROCEDURE P_EMPDETAIL
AS
V_WHERE := 'E.EMP_ID = 123'B
BEGIN
EXECUTE IMMEDIATE 'INSERT INTO EMPLOYEE E ' || V_WHERE || ;
END;
It seems to me there are various issues with your syntax and approach (you shouldn't be using dynamic SQL this way), perhaps you should learn PL/SQL and reference the manuals. The insert statement is also wrong. Below is the correct syntax.
CREATE OR REPLACE PROCEDURE P_EMPDETAIL as
V_WHERE varchar2(100);
BEGIN
V_WHERE := 'E.EMP_ID = 123';
EXECUTE IMMEDIATE 'INSERT INTO EMPLOYEE E (colname) values (1) ' || V_WHERE;
END;
Well, not exactly like that (obviously; otherwise, you wouldn't be asking for help).
It is unclear what you want to do because syntax is really strange. If you wanted to insert a row into the table, then:
SQL> CREATE TABLE employees
2 (
3 emp_id NUMBER
4 );
Table created.
SQL> CREATE OR REPLACE PROCEDURE p_empdetail (par_emp_id IN NUMBER)
2 AS
3 l_str VARCHAR2 (200);
4 BEGIN
5 l_str := 'insert into employees (emp_id) values (:1)';
6
7 EXECUTE IMMEDIATE l_str
8 USING par_emp_id;
9 END;
10 /
Procedure created.
Testing:
SQL> EXEC p_empdetail(123);
PL/SQL procedure successfully completed.
SQL> SELECT * FROM employees;
EMP_ID
----------
123
SQL>

Oracle rewrite a procedure to be generic

I have a procedure, which is working well that other applications want to use.
As you can see table and column names are hardcoded into the procedure, which makes it difficult to share the code. Is there a way this can be rewritten so it could be shared. I want to avoid passing in more values if possible as it will make the code awkward and klunky.
Any suggestions would be greatly appreciated.
SET NLS_DATE_FORMAT = 'MMDDYYYY HH24:MI:SS';
CREATE table t(
seq_num integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
c CLOB,
create_date DATE DEFAULT SYSDATE
);
/
insert into t (c) values (
rpad('X',20,'X')
);
/
create or replace procedure lob_append( p_id in number, p_text in varchar2 )
as
l_clob clob;
l_text varchar2(32760);
l_system_date_time VARCHAR2(50);
begin
select c into l_clob from t where seq_num = p_id for update;
SELECT TO_CHAR (SYSDATE, 'MMDDYYYY HH24:MI:SS') into l_system_date_time from dual;
-- newline each time code is appended for clarity.
l_text := chr(10) || p_text || chr(10) || '['||l_system_date_time||']'||chr(10);
dbms_lob.writeappend( l_clob, length(l_text), l_text );
end;
/
exec lob_append(1, rpad('Z',20,'Z'));
exec lob_append(1, rpad('Y',10,'Y'));
select * from t;
/
Don't SELECT from the table in the procedure, instead pass the CLOB into the procedure; that way you do not need to use dynamic SQL in the procedure:
CREATE PROCEDURE lob_append(
p_clob IN OUT CLOB,
p_text IN VARCHAR2
)
AS
l_text varchar2(32760);
BEGIN
-- newline each time code is appended for clarity.
l_text := chr(10)
|| p_text || chr(10)
|| '['||TO_CHAR (SYSDATE, 'MMDDYYYY HH24:MI:SS')||']'||chr(10);
dbms_lob.writeappend(p_clob, length(l_text), l_text );
END;
/
Then, when you want to call it:
DECLARE
l_clob CLOB;
BEGIN
SELECT c INTO l_clob FROM t WHERE seq_num = 1 FOR UPDATE;
lob_append(l_clob, rpad('Z',20,'Z'));
END;
/
DECLARE
l_clob CLOB;
BEGIN
SELECT c INTO l_clob FROM t WHERE seq_num = 1 FOR UPDATE;
lob_append(l_clob, rpad('Y',10,'Y'));
END;
/
db<>fiddle here
This is how I understood the question.
Is there a way this can be rewritten so it could be shared.
Yes, by using dynamic SQL. You'd compose all statements you need into a varchar2 local variable and then run it using execute immediate. It means that you'd, actually, have to pass table/column names into the procedure so that you'd be able to use them in a "generic" way.
Pay attention to SQL injection, i.e. bad people might try to misuse that code. Read more about DBMS_ASSERT package.
I want to avoid passing in more values if possible as it will make the code awkward and klunky.
Well, that's right the opposite of what you'll have to do. If the procedure has to be "generic", you have to pass table/column names (as I already said), so that means more parameters than you have now.
Is it worth it? I don't like dynamic SQL. Although it seems that it "solves" some problems, it brings in another. Code is difficult to maintain and debug. Basically, there's no such thing as free lunch. There are benefits, and there are drawbacks.

ora-01722 invalid number while updating record

I have a table where i have to update multiple records on one button click. I am trying to update multiple record using below simple query.
UPDATE tablename SET column1=1 WHERE
idcolumn IN ('1','2','3')
where datatype of idcolumn is Number. If i run this query manually its working perfectly. But if i pass these ('1','2','3') parameteres through procedure then it is showing me below error i.e. (ora-01722 invalid number).
I tried to_number() function but still it is showing me above error.
Proc:
CREATE OR REPLACE PROCEDURE procname(idpara VARCHAR2,
RCT_OUT OUT SYS_REFCURSOR) IS
BEGIN
UPDATE tablename SET column1 = 1 WHERE idcolumn IN (idpara);
COMMIT;
OPEN RCT_OUT FOR
SELECT 'RECORD UPDATED SUCCESSFULLY' RESULT FROM DUAL;
END;
The procedure does not understand IN (idpara) with idpara being '1','2','3' as IN ('1','2','3') but as IN (q'!'1','2','3'!'). In other words, it is not searching for '1' and '2' and '3' but for '1,2,3'. But while '1' can be converted to a number '1,2,3' can not.
Here is a test case for you to show you:
select * from dual;
-- X
-- notice I have 'X' in the in list below
set serveroutput on
declare
idpara varchar2(400) := q'!'X','2','3'!';
v_out varchar2(400);
begin
select count(*) into v_out from dual where dummy in (idpara);
dbms_output.put_line(v_out);
end;
/
-- 0
declare
idpara varchar2(400) := q'!'X','2','3'!';
v_out varchar2(400);
sql_stmt VARCHAR2(1000) := NULL;
begin
sql_stmt :='select count(*) from dual where dummy in ('||idpara||')';
execute immediate sql_stmt into v_out;
dbms_output.put_line(v_out);
end;
/
-- 1
One solution inside of procname would be to build a pl/sql object of numbers and use that in the update. There is a lot of info out there on how to do it. E.g. here Convert comma separated string to array in PL/SQL And here is info on how to use the object in the IN-clause Array in IN() clause oracle PLSQL

oracle out ref cursor to a loop

How to pass the 'values' of the output cursor o_cur to a further loop?
CREATE OR REPLACE PROCEDURE dyn_cursor (o_cur OUT SYS_REFCURSOR)
IS
script VARCHAR2 (4000);
BEGIN
script := 'select sysdate-1 notnow, sysdate today from dual union all select sysdate+1 notnow, sysdate today from dual';
OPEN o_cur FOR script;
-- the question is related to this block:
for i in o_cur
loop
DBMS_OUTPUT.PUT_LINE(i.notnow);
end loop;
-----------------------
END;
FETCH is usually handled outside the procedure when the refcursor is an OUT variable
CREATE OR REPLACE PROCEDURE dyn_cursor (o_cur OUT SYS_REFCURSOR)
IS
script VARCHAR2 (4000);
BEGIN
script := 'select sysdate-1 notnow, sysdate today from dual union all select sysdate+1 notnow, sysdate today from dual';
OPEN o_cur FOR script;
END;
/
SET SERVEROUTPUT ON
DECLARE
v_cur SYS_REFCURSOR;
v_notnow DATE;
v_today DATE;
BEGIN
dyn_cursor(v_cur);
LOOP
FETCH v_cur INTO
v_notnow,
v_today;
EXIT WHEN v_cur%notfound;
dbms_output.put_line(v_notnow);
END LOOP;
END;
/
02-10-18
04-10-18
PL/SQL procedure successfully completed.
This seems a strange construct. I would create a view you can reuse at different places.
CREATE OR REPLACE VIEW VW_Times AS
SELECT sysdate-1 notnow, sysdate today FROM dual
UNION ALL
SELECT sysdate+1 notnow, sysdate today FROM dual;
and
CREATE OR REPLACE PROCEDURE OutputTime ()
IS
CURSOR o_cur IS
SELECT * FROM VW_Times;
BEGIN
FOR i IN o_cur
LOOP
DBMS_OUTPUT.PUT_LINE(i.notnow);
END LOOP;
END;
Tanks to post of Kaushik Nayak, I found the acceptable solution:
CREATE OR REPLACE PROCEDURE dyn_cursor (o_cur OUT SYS_REFCURSOR)
IS
script VARCHAR2 (4000);
type array is table of VARCHAR2(20) index by binary_integer;
l_datapoint1 array;
l_datapoint2 array;
BEGIN
script := 'select sysdate-1 notnow, sysdate today from dual union all select sysdate+1 notnow, sysdate today from dual';
OPEN o_cur FOR script;
LOOP
FETCH o_cur bulk collect into l_datapoint1, l_datapoint2;
for i in 1 .. l_datapoint1.count
loop
DBMS_OUTPUT.PUT_LINE( l_datapoint1(i) || ', ' || l_datapoint2(i) );
end loop;
exit when o_cur%notfound;
end loop;
close o_cur;
END;

measure time of an sql statement in a procedure in plsql

I must write a procedure which save the execute time of any sql-statement in a table.
The procedure is calling by exec measuresqltime('sql statement as string');
My idea is like this:
--declarations
timestart NUMBER;
BEGIN
dbms_output.enable;
timestart:=dbms_utility.get_time();
EXECUTE IMMEDIATE sql
COMMIT;
dbms_output.put_line(dbms_utility.get_time()-timestart);
-- save time
But it didn't work for me for a SELECT *... clause. (I think sql need a INTO-order)
Is there a way to execute any sql-atatements in a procedure?
If your SQL statement is a SELECT, you need to fetch from the cursor to have a meaningful measure of its execution time.
If you don't fetch from the cursor, you only measure the time spent in "parse" and "execution" phases, whereas much of the work is usually done in the "fetch" phase for SELECT statements.
You won't be able to fetch with EXECUTE IMMEDIATE or OPEN cursor FOR 'string' if you don't know the number of columns the actual statement will have. You will have to use the dynamic SQL package DBMS_SQL if the number/type of columns of the SELECT is unknown.
Here's an example:
SQL> CREATE OR REPLACE PROCEDURE demo(p_sql IN VARCHAR2) AS
2 l_cursor INTEGER;
3 l_dummy NUMBER;
4 timestart NUMBER;
5 BEGIN
6 dbms_output.enable;
7 timestart := dbms_utility.get_time();
8 l_cursor := dbms_sql.open_cursor;
9 dbms_sql.parse(l_cursor, p_sql, dbms_sql.native);
10 l_dummy := dbms_sql.execute(l_cursor);
11 LOOP
12 EXIT WHEN dbms_sql.fetch_rows(l_cursor) <= 0;
13 END LOOP;
14 dbms_sql.close_cursor(l_cursor);
15 dbms_output.put_line(dbms_utility.get_time() - timestart);
16 END;
17 /
Procedure created.
SQL> exec demo('SELECT * FROM dual CONNECT BY LEVEL <= 1e6');
744
PL/SQL procedure successfully completed.
Note that this will measure the time needed to fetch to the last row of the SELECT.
completing devosJava answered... avoid using it at dawn ;P
PROCEDURE MY_PROCEDURE IS
timeStart TIMESTAMP;
timeEnd TIMESTAMP;
timeSecond NUMBER
BEGIN
timeStart := SYSTIMESTAMP;
-- YOUR CODE HERE
timeEnd := SYSTIMESTAMP;
timeSecond :=((extract(hour from timeEnd)*3600)+(extract(minute from timeEnd)*60)+extract(second from timeEnd))-((extract(hour from timeStart)*3600)+(extract(minute from timeStart)*60)+extract(second from timeStart));
dbms_output.put_line('finished: '||timeSecond||' seconds');
END MY_PROC;
To calculate the duration for an execution time
PROCEDURE MY_PROCEDURE IS
timeStart TIMESTAMP;
timeEnd TIMESTAMP;
BEGIN
timeStart := SYSTIMESTAMP;
-- YOUR CODE HERE
timeEnd := SYSTIMESTAMP;
INSERT INTO PROC_RUNTIMES (PROC_NAME, START_TIME, END_TIME)
VALUES ('MY_PROCEDURE ', timeStart , timeEnd );
END MY_PROC;
INSERT INTO PROC_RUNTIMES (PROC_NAME, START_TIME, END_TIME)
VALUES ('PROC_NAME', TO_CHAR
(SYSDATE, 'DD/MM/YYYY HH24:MI:SS'),NULL );
Your query here;
INSERT INTO PROC_RUNTIMES (PROC_NAME, START_TIME, END_TIME)
VALUES ('PROC_NAME',NULL, TO_CHAR
(SYSDATE, 'DD/MM/YYYY HH24:MI:SS'));

Resources