Dynamic SQL within cursor - sql

My dynamic sql below to alter a table & create columns based on the output of a query is giving error.
Query :
DECLARE
CURSOR c1 is select distinct WP_NO from temp;
cnum VARCHAR2(255);
BEGIN
FOR cnum in c1
LOOP
EXECUTE IMMEDIATE 'Alter table temp_col add (:1 varchar2(255))' using cnum;
END LOOP;
COMMIT;
END;
Error :
PLS-00457: expressions have to be of SQL types

This is happening because bind variables are not allowed in DDL statements.
Consider trying it without using the bind variable:
DECLARE
CURSOR c1 is select distinct WP_NO from temp;
cnum VARCHAR2(255);
BEGIN
FOR cnum in c1
LOOP
EXECUTE IMMEDIATE 'Alter table temp_col add ('|| cnum ||' varchar2(255))';
END LOOP;
COMMIT;
END;

You have a conflict with the cnum symbol, which you use both as a local variable and for the current row of the cursor.
You probably want this:
DECLARE
CURSOR c1 is select distinct WP_NO from temp;
BEGIN
FOR current_row in c1
LOOP
EXECUTE IMMEDIATE 'Alter table temp_col add (:1 varchar2(255))' using current_row.WP_NO;
END LOOP;
COMMIT;
END;
As you can see, you don't need to declare the current_row variable that you use in the for loop.

Related

Use Variabel for 'ORDER BY ' Declartion in a FOR IN LOOP Oralce

In ORACLE please.
Is it possible to have a variable in the 'ORDER BY' Statment?
So iam currenty creating a procedure in which I need to have a dynamic Order by.
Is there any way to do this ?
CREATE OR REPLACE PROCEDURE TEST1
vorder varchar(250);
...
vOrder := 'number';
FOR o IN (SELECT * FROM TABLENAME order by vOrder)
LOOP
-- Some stuff ---
END LOPP;
....
The only way to really have a dynamic order by clause instead of just having a list of predefined options as suggested by Alex, would be to use a dynamic sql cursor.
TYPE curType IS REF CURSOR;
vCur curType;
vRec tablename%ROWTYPE;
vOrd VARCHAR2(250) := 'column';
BEGIN
OPEN vCur FOR 'SELECT * FROM tablename ORDER BY '||vOrd';
LOOP
FETCH vCur INTO vRec;
EXIT WHEN cur%NOTFOUND;
END LOOP;
CLOSE vCur;
END;
/

PL/SQL Procedure Use String Select Statement in for loop

I am building a procedure, where I`m first creating a select statement and store it in an VARCAHR variable.
I now want to execute that query and store the whole result set in an variable to loop through it or use directly in a for loop.
I only find examples where the Select is hard written in the for loop definition.
How do i exchange the Select statement with my variable that holds my select statement?
for r IN (SELECT ... FROM ...)
loop
--do sth;
end loop;
how i want to use it :
statement := 'SELECT .... FROM ...';
for r IN (statement) -- HOW TO DO THIS
loop
--do sth;
end loop;
For a dynamic ref cursor, you need to define everything explicitly:
declare
sqlstring long := 'select 123 as id, ''demo'' as somevalue from dual where dummy = :b1';
resultset sys_refcursor;
type demo_rectype is record
( id integer
, somevalue varchar2(30) );
demorec demo_rectype;
begin
open resultset for sqlstring using 'X';
loop
fetch resultset into demorec;
exit when resultset%notfound;
dbms_output.put_line('id=' || demorec.id || ' somevalue=' || demorec.somevalue);
end loop;
close resultset;
end;
You can parse the cursor and figure out the column names and datatypes with DBMS_SQL. Example here: www.williamrobertson.net/documents/refcursor-to-csv.shtml

How to execute results of dbms_output.put_line

There is a table contains this kind of data: select to_char(sysdate,'day') from dual in a column. I want to get results of the every query that the table keeps.
My result set should be the result of select to_char(sysdate,'day') from dual query. So in this case it is a tuesday.
SO_SQL_BODY is Varchar2.
I wrote this code but it returns only table data.
CREATE or replace PROCEDURE a_proc
AS
CURSOR var_cur IS
select SO_SQL_BODY FROM SO_SUB_VARS group by SO_SQL_BODY;
var_t var_cur%ROWTYPE;
TYPE var_ntt IS TABLE OF var_t%TYPE;
var_names var_ntt;
BEGIN
OPEN var_cur;
FETCH var_cur BULK COLLECT INTO var_names;
CLOSE var_cur;
FOR indx IN 1..var_names.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(var_names(indx).SO_SQL_BODY);
END LOOP;
END a_proc;
DECLARE
res varchar2(4000);
sql_str varchar2(1000);
BEGIN
FOR r IN
(select SO_SQL_BODY FROM SO_SUB_VARS WHERE SO_SQL_BODY IS NOT NULL
)
LOOP
sql_str := r.SO_SQL_BODY;
EXECUTE immediate sql_str INTO res;
dbms_output.put_line(sql_str);
dbms_output.put_line('***********************');
dbms_output.put_line(res);
dbms_output.put_line('***********************');
END LOOP;
END;
/
Try this - iterate to not null records - execute them and print the result.This script works supposing the fact that SO_SQL_BODY contains a query which projects only one column.Also if the projection is with more than two columns then try to use a refcursor and dbms_sql package
İf var_names(indx).SO_SQL_BODY output is a runnable sql text;
CREATE or replace PROCEDURE a_proc
AS
CURSOR var_cur IS
select SO_SQL_BODY FROM SO_SUB_VARS group by SO_SQL_BODY;
var_t var_cur%ROWTYPE;
TYPE var_ntt IS TABLE OF var_t%TYPE;
var_names var_ntt;
BEGIN
OPEN var_cur;
FETCH var_cur BULK COLLECT INTO var_names;
CLOSE var_cur;
FOR indx IN 1..var_names.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(var_names(indx).SO_SQL_BODY);
EXECUTE IMMEDIATE var_names(indx).SO_SQL_BODY;
END LOOP;
END a_proc;
You don't need a full cursor for this example. An implicit one would make it a lot shorter.
create or replace procedure a_proc is
lReturnValue varchar2(250);
begin
for q in (select so_sql_body from so_sub_vars group by so_sql_body)
loop
execute immediate q.so_sql_body into lReturnValue;
dbms_output.put_line(lReturnValue);
end loop;
end a_proc;
You should add an exception handler that will care for cases where there is a bad SQL query in your table. Also note that executing querys saved in a database table is your entry point to SQL injection.

If table have 0 rows exit procedure

I have a procedure inside a package and I want to implement a logic, wich will not insert the temp table into the main table if the temp table have 0 rows, and then go to the next procedure of the package.
IF (not exists(select 1 from temp)) THEN
RETURN;
ELSE
EXECUTE IMMEDIATE 'TRUNCATE TABLE main';
INSERT --+APPEND
INTO main
Select * from temp;
EXECUTE IMMEDIATE 'TRUNCATE TABLE temp';
END IF;
With this solution, the package is compiled with error.
Can anyone give me some tips?
you can use loop, without any variables, just first iteration, something like this
FOR a in (select 1 from temp where rownum = 1) LOOP
EXECUTE IMMEDIATE 'TRUNCATE TABLE main';
INSERT --+APPEND
INTO main
Select * from temp;
EXECUTE IMMEDIATE 'TRUNCATE TABLE temp';
END LOOP;
Just count one row and then test whether the result is 0 or 1:
declare
l_row_check integer := 0;
begin
select count(*) into l_row_check from main
where rownum = 1;
if l_row_check = 0 then
execute immediate 'truncate table main';
insert --+ append
into main
select * from temp;
execute immediate 'truncate table temp';
end if;
end;
The easiest is to use a variable to check:
--- suggested edit: add condition to select 1 row at most and avoid
-- counting big table.
select count(1) into v_count from temp where rownum <=1;
IF (v_count=0) THEN
RETURN;
ELSE
EXECUTE IMMEDIATE 'TRUNCATE TABLE main';
INSERT --+APPEND
INTO main
Select * from temp;
EXECUTE IMMEDIATE 'TRUNCATE TABLE temp';
END IF;
Some answer on here use a SELECT INTO method, but I find those a bit tricky.
Since if for example SELECT ColumnA INTO vcColumnA FROM Temp will not have any records, you will end up with the error ORA-01403: no data found.
And those can be hard to find if you have a bigger database.
To loop through a table and do something with values I think cursors and records are more safe.
For example:
DECLARE
CURSOR cTemp IS
SELECT ColumnA, ColumnB
FROM Temp;
rTemp cTemp%ROWTYPE;
BEGIN
OPEN cTemp;
LOOP
FETCH cTemp INTO rTemp;
-- Exit when we read all lines in the Temp table.
EXIT WHEN cTemp%NOTFOUND;
--Do something with every row.
--For example, print ColumnB.
DBMS_OUTPUT.PUT_LINE(rTemp.ColumnB);
END LOOP;
CLOSE cTemp;
END;
/

output data, dynamic sql

I have some problem with dynamic SQL.
I created table, after that inserted some data - it works fine.
But i have no idea how to display data. My code:
declare
begin
execute immediate 'create table name(tabl_name varchar2(30),id number)';
execute immediate 'insert into name(tabl_name,id) (select ''something'',id from table3)';
commit;
and now i would like to display name table content. How to do that? Should i use cursor with dynamic sql? Thanks in advance.
You can use cursor to loop through the records:
declare
v_tabl_name varchar2(30);
v_id number;
res_cur SYS_REFCURSOR;
begin
execute immediate 'create table name(tabl_name varchar2(30),id number)';
execute immediate 'insert into name(tabl_name,id) (select ''something'',id from table3)';
open res_cur for 'select tabl_name, id from name';
LOOP
FETCH res_cur INTO v_tabl_name, v_id;
EXIT WHEN res_cur%NOTFOUND;
dbms_output.put_line(v_tabl_name);
dbms_output.put_line(v_id);
END LOOP;
close res_cur;
end;