Q-Quote in execute immediate PL/SQL - plsql

I have following Update statement:
UPDATE COL_VAL SET DESC_VALUE=q'['TOM','JOHN','MARIE','VANI','PUTIN']' WHERE TYPE_NAME||TYPE='OLDHOL' ;
When I put above q-qoute in execute immediate, PL/SQL is not able to recognize syntax.
DECLARE
upd_stmnt varchar2(4000);
BEGIN
upd_stmnt := q'[UPDATE COL_VAL SET DESC_VALUE=q'['TOM','JOHN','MARIE','VANI','PUTIN']' WHERE TYPE_NAME||TYPE='OLDHOL']';
EXECUTE IMMEDIATE upd_stmnt;
END;
/
Please help.

seems that you can't nest q quoted strings, please try this one
DECLARE
upd_stmnt varchar2(4000);
BEGIN
upd_stmnt := q'[UPDATE COL_VAL SET DESC_VALUE = '''TOM'',''JOHN'',''MARIE'',''VANI'',''PUTIN''' WHERE TYPE_NAME || TYPE='OLDHOL']';
EXECUTE IMMEDIATE upd_stmnt;
END;
/

Related

ORA-00904 - "NORM": invalid identifier - string in dynamic sql

Im trying to execute below section of code but get an ORA-00904 error.
Declare
i_status varchar2(4) := 'NORM';
vsql varchar2(4000);
...
...
Begin
...
...<Part of larger dynamic sql>
If i_status is not null Then
vSql := vSql || ' And account.astatus = ' ||i_status|| '';
End if;
execute immediate (vSql) into tmp,ssn;
<Do something with tmp, ssn>
End;
An exception is raised at line "execute immediate" with error
ORA-00904 - "NORM": invalid identifier
column account.astatus has type char(4 byte)
I assume the problem is that I am trying to pass string variable NORM in the where clause without adding quotes ' '. How do get around this issue?
Thanks.
You can easily dig into your code and check where the issue exist by printing your VSQL before executing it.
Declare
i_status varchar2(10) := 'NORM';
vsql varchar2(4000):= 'Select * from dual where 1=3';
Begin
If i_status is not null Then
vSql := vSql || ' And account.astatus = ' ||i_status|| '';
End if;
dbms_output.put_line(vSql);
--execute immediate (vSql) into tmp,ssn;
End;
When you run this block you can see the statement that is getting generated which shows :
Select * from dual where 1=3 And account.astatus = NORM
Now you can easily note that your account.astatus = NORM is not correct so you can replace it with:
i_status varchar2(10) := '''NORM''';
or using q quotes:
i_status varchar2(10) := q'['NORM']';
Nevertheless what Boneist mentioned is the best practice which avoids sql injection.
The simple answer is to use bind variables, meaning you avoid the whole thorny issue of sql injection that you open yourself up to when you hardcode your variables into the dynamic sql. You also save yourself the faff of having to work out how to include the single-quotes to go around the string that your dynamic sql is currently missing.
Using bind variables, your code becomes:
Declare
i_status varchar2(4) := 'NORM';
vsql varchar2(4000);
...
...
Begin
...
...<Part of larger dynamic sql>
If i_status is not null Then
vSql := vSql || ' And account.astatus = :i_status';
End if;
execute immediate (vSql) into tmp,ssn using i_status;
<Do something with tmp, ssn>
End;

Can we use a parameter in a pl sql subprogram other than in where clause

Can we use the parameters in a pl sql subprogram other than in where clause
Yes. Once you declare the variables in a PLSQL program, you can use it anywhere in the program. See below an example
DECLARE
var VARCHAR2 (100); -- Variable declared
BEGIN
var := 'My name is jack'; -- Assigning a string to the varibale
DBMS_OUTPUT.put_line (var); -- Displaying it.
SELECT 'My name is Mack' INTO var FROM DUAL;
DBMS_OUTPUT.put_line (var);
END;
I hope this helps.
Yes, you surely can.
Create or replace procedure test(a_param_1 in int, a_param_2 out int)
as
v_var int;
begin
dbms_output.put_line(a_param);--you can print the param
v_var := a_param ;-- you can assign it to some other value
a_param_2 := 1; --Out parameter can be assigned a value.
end;

00900. 00000 - "invalid SQL statement" for EXECUTE IMMEDIATE

I am trying to use Dynamic query in my code below but getting error (00900. 00000 - "invalid SQL statement"). Kindly suggest where i am mistaking in the code.
create or replace PROCEDURE CreateInsertTmpTable
AS
crttmp VARCHAR2(200);
intrtmp VARCHAR2(200);
printTableValues VARCHAR2(1000);
BEGIN
crttmp := 'CREATE GLOBAL TEMPORARY TABLE my_temp_table ON COMMIT PRESERVE ROWS AS SELECT * FROM VWBLKDATA WHERE 1=0';
EXECUTE IMMEDIATE crttmp;
intrtmp := 'INSERT INTO my_temp_table SELECT * FROM VWBLKDATA';
EXECUTE IMMEDIATE intrtmp;
printTableValues := ' for data in(SELECT * from my_temp_table)
loop
dbms_output.put_line(data.ID);
end loop';
EXECUTE IMMEDIATE printTableValues;
COMMIT;
END CreateInsertTmpTable;
I think you're overdoing the EXECUTE IMMEDIATE; you can run INSERT statements and PL/SQL without them like:
begin
for i in 1..10 loop
insert into test (some_column) values (to_char(i));
end loop;
end;
But anyways, it looks like you're last EXECUTE IMMEDIATE is trying to execute a partial PL/SQL anonymous block; it's missing a "begin" and "end;"
I would suggest just executing the for loop like so:
for data in (SELECT * from my_temp_table)
loop
dbms_output.put_line(data.ID);
end loop;
or else you'll need to add a begin/end around it in the text (and the "end loop" needs a trailing ";"):
printTableValues := 'begin
for data in (SELECT * from my_temp_table)
loop
dbms_output.put_line(data.ID);
end loop;
end;';

PLS-00103 ERROR, what is wrong in the code

CREATE OR REPLACE PROCEDURE proc2_del_rows
(v_tname VARCHAR2,
v_condition VARCHAR2 DEFAULT NULL)
AS
sql_stmt VARCHAR2(500);
where_clause VARCHAR2(200) := 'WHERE'||' '||v_condition;
BEGIN
IF v_condition IS NULL THEN
where_clause := NULL;
END IF;
sql_stmt := 'DELETE FROM :1'||' '||where_clause;
EXECUTE IMMEDIATE sql_stmt USING v_tname;
COMMIT;
END;
/
The table name can't be a bind variable. Do a DBMS_ASSERT on the input table name parameter and make sure it is a valid table name literal, and then directly concatenate it to the delete statement. This will at least protect you against sql injection.
I'd like to know the reason behind doing a delete using a procedure and granting execute on this procedure to individual users, rather than granting a delete on the table to a user directly, which would somewhat be easier to control/restrict. I don't see how this is better in terms on security if that is what you are going for.
CREATE or replace PROCEDURE proc2_del_rows
(v_tname VARCHAR2,
v_condition VARCHAR2 DEFAULT NULL)
AS
sql_stmt VARCHAR2(500);
where_clause VARCHAR2(200) := 'WHERE'||' '||v_condition;
BEGIN
IF v_condition IS NULL THEN
where_clause := NULL;
END IF;
sql_stmt := 'DELETE FROM '||v_tname||' '||where_clause;
EXECUTE IMMEDIATE sql_stmt;
END;
/
To include a single-quote character within a string literal you need to double up the single quotes, as in proc2_del_rows('EMP', 'JOB=''CLERK''').
Documentation here

opening implicit cursor with for loop

I have a stored procedure that has following pl/sql block. This block was using select query in for statement but i need to change that static variable to dynamic query. As I changed that it has error. Is there any way to use variable with FOR LOOP in implicit cursor.
declare
sql_query varchar2(32767) := 'select ctlchar ';
kpiNameQuery varchar2(600);
isWg boolean := true;
begin
IF isWG then
kpiNameQuery := 'select distinct KPI_NAME from weeklykpi where kpi_name in (select kpi_wg from auxillary.kpi_types) order by 1';
Else
kpiNameQuery := 'select distinct KPI_NAME from weeklykpi where kpi_name in (select kpi_wg1 from auxillary.kpi_types) order by 1';
End IF;
for KPI_NAME in kpiNameQuery
loop
sql_query := sql_query || ' , min(case when KPI_NAME = '''||x.KPI_NAME||''' then KPI_VALUE end) as '||x.KPI_NAME;
dbms_output.put_line(sql_query);
end loop;
end;
You can achieve similar functionality with the following using cursor
declare
type t_cursor is ref cursor;
c_cursor t_cursor;
l_sql varchar2(512);
l_var number;
begin
l_sql := 'select count(*) from emp'; -- do dynamic check before here for
-- correct sql
open c_cursor for l_sql;
loop
fetch c_cursor
into l_var;
exit When c_cursor%notfound;
DBMS_OUTPUT.put_line ('val '||l_var);
end loop;
close c_cursor;
end;
Unfotunately no, the doc states:
If the dynamic SQL statement is a SELECT statement that returns multiple rows, native dynamic SQL gives you these choices:
Use the EXECUTE IMMEDIATE statement with the BULK COLLECT INTO clause.
Use the OPEN FOR, FETCH, and CLOSE statements.
So you will have to use a REF cursor (or EXECUTE IMMEDIATE and loop over the results).
Incidentally, in your case you could go for static SQL and have comparable performance:
BEGIN
FOR cc IN (SELECT DISTINCT KPI_NAME
FROM weeklykpi
WHERE kpi_name IN (SELECT CASE WHEN l_variable = 1
THEN kpi_wg
ELSE kpi_wg1
END
FROM auxillary.kpi_types) LOOP
ORDER BY 1
-- do something
END LOOP;
END;
You'll have to use some other type than boolean though since it's unknown to SQL.

Resources