PL/SQL nested loop (loop within a loop) - oracle

Below is a PL/SQL I'm working on
declare
v_sql varchar2(500);
BEGIN
for t in (
SELECT distinct ID
FROM TABLEB
) loop
for c in (
select * from (
select 'delete from ' as test
from dual
union all
select 'TABLEA'||' where ' as test
from dual
union all
select 'ID='||t.ID
from dual
)
) loop
v_sql := v_sql || c.test;
end loop;
dbms_output.put_line(v_sql);
end loop;
END;
/
The result I'm getting is this
delete from TABLEA where ID=1
delete from TABLEA where ID=1delete from TABLEA where ID=2
I want
delete from TABLEA where ID=1
delete from TABLEA where ID=2
Any help on the PLSQL will be appreciated

What is the purpose of the inner FOR loop? It does nothing that requires a loop, and can be simply rewritten like this:
declare
v_sql varchar2(500);
begin
for t in (select distinct id from tableb) loop
v_sql := 'delete from tablea where id = ' || t.id ||';';
dbms_output.put_line(v_sql);
end loop;
end;
/
BTW, it seems that you're missing the terminating semicolon in line v_sql := ...
Demonstration on HR's DEPARTMENTS table:
SQL> declare
2 v_sql varchar2(500);
3 begin
4 for t in (select distinct department_id id from departments) loop
5 v_sql := 'delete from tablea where id = ' || t.id ||';';
6 dbms_output.put_line(v_sql);
7 end loop;
8 end;
9 /
delete from tablea where id = 10;
delete from tablea where id = 20;
delete from tablea where id = 30;
delete from tablea where id = 40;
delete from tablea where id = 50;
delete from tablea where id = 60;
<snip>

You're not clearing the buffer after you've printed the statement, so you're appending the next statement to the first one. To clear the buffer, add
v_sql := NULL;
after the line which reads
dbms_output.put_line(v_sql);
Best of luck.

Related

Oracle dynamic find number of rows and columns per table

I have the following query, which returns the number of rows per table in a schema.
Can this also be modified to RETURN the number of columns per table too?
CREATE table stats (
table_name VARCHAR2(128),
num_rows NUMBER,
num_cols NUMBER
);
/
DECLARE
val integer;
BEGIN
for i in (SELECT table_name FROM all_tables WHERE owner = 'Schema')
LOOP
EXECUTE IMMEDIATE 'SELECT count(*) from ' || i.table_name INTO val;
INSERT INTO stats VALUES (i.table_name,val);
END LOOP;
END;
/
You can use the ALL_TAB_COLS dictionary table:
DECLARE
val integer;
BEGIN
FOR i IN (
SELECT table_name,
COUNT(*) AS num_cols
FROM all_tab_cols
WHERE owner = 'Schema'
GROUP BY table_name
)
LOOP
EXECUTE IMMEDIATE 'SELECT count(*) from ' || i.table_name INTO val;
INSERT INTO stats VALUES (i.table_name,val, i.num_cols);
END LOOP;
END;
/
db<>fiddle here

error ORA-00998: must name this expression with a column alias

I tried to create table from all_tab_columns but it throws me an error.
like error ORA-00998: must name this expression with a column alias.
i tried to figure it out but not working.
Declare
CURSOR c1 IS
SELECT COLUMN_NAME FROM (select 'standard_hash(MY_NAME) AS MY_NAME' COLUMN_NAME from DUAL
UNION
SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS WHERE TABLE_NAME='TABLE1' AND COLUMN_NAME<>'MY_NAME');
cols c1%ROWTYPE;
sqlstmt VARCHAR2(4000);
BEGIN
OPEN c1;
LOOP
FETCH c1 into cols;
EXIT WHEN c1%NOTFOUND;
sqlstmt := sqlstmt ||cols.column_name||',';
END LOOP;
CLOSE c1;
sqlstmt := 'CREATE TABLE TABLE2 AS SELECT '||substr(sqlstmt, 1, length(sqlstmt)-1)||' FROM TABLE1';
DBMS_OUTPUT.PUT_LINE(sqlstmt);
EXECUTE IMMEDIATE sqlstmt;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('error '||sqlerrm);
END;
/
You just have to give the name to the expression as follows:
sqlstmt := 'CREATE TABLE TABLE2 AS SELECT '
||substr(sqlstmt, 1, length(sqlstmt)-1)
||' as column_name FROM TABLE1'; -- column_name will bre name of the column of new table
As it will become the name of the column of newly created table.
I think this can be simplified somewhat.
You don't need to declare an explicit cursor and a record type and fetch each row into it, as this is already built into the language as the Cursor FOR loop construction:
declare
sqlStmt long := 'create table table2 as select ';
begin
for r in (
select 'standard_hash(my_name) as my_name' column_name
from dual
union
select column_name
from all_tab_columns
where table_name = 'TABLE1'
and column_name <> 'MY_NAME'
)
loop
sqlStmt := sqlStmt || r.column_name || ', ';
end loop;
sqlStmt := sqlStmt || rtrim(sqlStmt,', ') || ' from table1';
dbms_output.put_line(sqlStmt);
execute immediate sqlStmt;
end;
/
But you can do this without any loop, as the listagg function can already build a comma-separated list for you. It also makes it easier to retain the order of columns in the original table.
declare
selectList long;
sqlStmt long;
begin
select listagg(
case column_name
when 'MY_NAME' then 'standard_hash(my_name) as my_name'
else lower(column_name)
end, ', '
) within group (order by column_id) as select_list
into selectList
from user_tab_columns c
where c.table_name = 'TABLE1';
sqlStmt :=
'create table table2 pctfree 0 nologging parallel as '||chr(10)||
'select '||selectList||chr(10)||
'from table1';
dbms_output.put_line(sqlStmt||';');
execute immediate sqlstmt;
end;

How to insert multiple row result of dynamic sql to another Table?

I write one dynamic SQL which the result of it is a table with 2 columns and multiple rows, I want to insert it to another table with 4 columns that 2 of them will be filled by the result of dynamic SQL, I try to use collection but don't know how to insert result to another table
CREATE OR REPLACE PROCEDURE P_C_SM_Failure_error_Code_P2P AS
v_month VARCHAR2(16); -- to get Month for each table
v_day VARCHAR2(16); -- to get day for each table
v_ERRCODE t_c_rpt_resultmsg.code%TYPE;
v_ERRMSG t_c_rpt_resultmsg.MESSAGE%TYPE;
v_param VARCHAR2(16);
v_sql VARCHAR2(3000);
v_result number;
type t_c_result is record (Err_code varchar2(2000), Err_count number);
type v_t_result is table of t_c_result index by PLS_INTEGER;
v_t1_result v_t_result;
BEGIN
v_sql :='0';
v_param := 'Gateway_G';
v_result := '0';
select to_char(sysdate - 1,'MM') into v_month from dual;
select to_char(sysdate - 1,'DD') into v_day from dual;
-- Get count of P2P
v_sql := '(select count(*), error_code from (
select error_code from sm_histable'||v_month||''||v_day||'#ORASMSC01 where
orgaccount = '''||v_param||''' and destaccount = '''||v_param||''' and
sm_status <> 1 union all
select error_code from sm_histable'||v_month||''||v_day||'#ORASMSC02 where
orgaccount = '''||v_param||''' and destaccount = '''||v_param||''' and
sm_status <> 1 )
group by error_code)';
EXECUTE IMMEDIATE v_sql bulk collect into v_t1_result;
--insert into t_c_rpt_result2 values (trunc(sysdate, 'DD'), v_errcount,
v_err_code,'Failure_error_Code_P2P');
--for indx in 1 .. v_t1_result.COUNT
--loop
--dbms_output.put_line (v_t1_result (indx).Err_code);
--end loop;
You may append the constant values of date and the error message to the subquery and run a dynamic insert. It should also work if you remove the outer parentheses of your dynamic sql since constants can be included in group by. Always remember to pass values as bind variables rather than concatenating them (v_param). Also, specify the column names explicitly in an INSERT statement.
v_sql := '(select count(*) as cnt, error_code
from (
select error_code from sm_histable'||v_month||''||v_day||'#ORASMSC01
where orgaccount = :x and destaccount = :x and sm_status <> 1
union all
select error_code from sm_histable'||v_month||''||v_day||'#ORASMSC02
where orgaccount = :x and destaccount = :x and sm_status <> 1 )
group by error_code)';
EXECUTE IMMEDIATE v_sql bulk collect into v_t1_result using v_param;
EXECUTE IMMEDIATE 'insert into t_c_rpt_result2(err_dt,err_msg,errcount,error_code)
select :dt,:msg,cnt,error_code from '|| v_sql
USING trunc(sysdate, 'DD'),'Failure_error_Code_P2P',v_param;
I think you are looking at an excellent use case for FORALL. The collection you are populating needs to be done with execute immediate since you are dynamically constructing the table name. But the insert into t_c_rpt_result2 looks static to me.
BEGIN
v_sql :=
'(select count(*) as cnt, error_code
from (
select error_code from sm_histable'
|| v_month
|| ''
|| v_day
|| '#ORASMSC01
where orgaccount = :x and destaccount = :x and sm_status <> 1
union all
select error_code from sm_histable'
|| v_month
|| ''
|| v_day
|| '#ORASMSC02
where orgaccount = :x and destaccount = :x and sm_status <> 1 )
group by error_code)';
EXECUTE IMMEDIATE v_sql BULK COLLECT INTO v_t1_result USING v_param;
FORALL indx IN 1 .. v_t1_result.COUNT
INSERT INTO t_c_rpt_result2 (err_dt,
err_msg,
errcount,
ERROR_CODE)
VALUES (TRUNC (SYSDATE, 'DD'),
'Failure_error_Code_P2P',
v_t1_result (indx).cnt,
v_t1_result (indx).ERROR_CODE);
END;
Find more examples of FORALL on LiveSQL here. Of course, even if your insert was dynamic, you can use FORALL - put the execute immediate directly "inside" the FORALL statement. But I don't think that complexity is justified here.
Hope that helps!

PLSQL convert single row in one string

Hi I'm looking for a Oracle function for convert all record of one row in a single string
TABLE_A
|a|b|c|1|
|d|e|f|2|
|g|h|i|3|
select * from TABLE_A where COL4='1';
must return
"abc"
select * from TABLE_A where COL4='2';
"def"
I can't use CONCAT
SELECT COL1||COL2||COL3 from TABLE_A
Becouse I don't know the number and the name of the columns in the table.
Is it possible?
Please, try this, working for record with ID=1, you can change that, or add a loop if needed:
DECLARE
CURSOR CUR IS
SELECT COLUMN_NAME
FROM ALL_TAB_COLS
WHERE TABLE_NAME='TABLE_A';
STMT VARCHAR(1000);
RESULT VARCHAR(1000);
BEGIN
STMT := 'SELECT ';
FOR COL in CUR
LOOP
STMT := STMT || COL.COLUMN_NAME || '||';
END LOOP;
STMT := STMT || ''''' FROM TABLE_A WHERE ID=1';
DBMS_OUTPUT.PUT_LINE( STMT );
EXECUTE IMMEDIATE
STMT
INTO RESULT;
DBMS_OUTPUT.PUT_LINE( 'RESULT: ' || RESULT );
END;

oracle read column names from select statement

I wonder how read column names in oracle. Ok, I know that there is table named USER_TAB_COLUMNS which gives info about it, but if I have 2 or 3 level nested query and I don't know column names. Or I just have simple query with join statement and i want to get column names. How to do that? any idey?
select * from person a
join person_details b where a.person_id = b.person_id
thanks
I would go for:
select 'select ' || LISTAGG(column_name , ',') within group (order by column_id) || ' from T1'
from user_tab_columns
where table_name = 'T1';
to get a query from database. To get columns with types to fill map you can use just:
select column_name , data_type
from user_tab_columns
where table_name = 'T1';
I assume you are looking for this:
DECLARE
sqlStr VARCHAR2(1000);
cur INTEGER;
columnCount INTEGER;
describeColumns DBMS_SQL.DESC_TAB2;
BEGIN
sqlStr := 'SELECT a.*, b.*, SYSDATE as "Customized column name"
FROM person a JOIN person_details b
WHERE a.person_id = b.person_id';
cur := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(cur, sqlStr, DBMS_SQL.NATIVE);
DBMS_SQL.DESCRIBE_COLUMNS2(cur, columnCount, describeColumns);
FOR i IN 1..columnCount LOOP
DBMS_OUTPUT.PUT_LINE ( describeColumns(i).COL_NAME );
END LOOP;
DBMS_SQL.CLOSE_CURSOR(cur);
END;

Resources