Inserting values in a table in FOR loop statement in pl sql - sql

I want to run the loop from i=1 to i=12 and insert each value of M_1,M_2 and so on till M_12 in M_Maintenance table.I have tried but it is not working.Please help me out.
The code which I have tried is:-
BEGIN
FOR i IN 1 .. 12
LOOP
DECLARE
M_i NUMBER;
MONTH_i_COUNT NUMBER;
M_i_Maintenance NUMBER;
MONTH_i_SUM NUMBER;
BEGIN
SELECT ROUND (
( ( (SELECT SUM (MONTH_i_COUNT)
FROM XXBAXY.XXBAXY_BREAKDOWN_TAB
WHERE LOSS_CAT = 'Maintenance Related Losses')
+ (SELECT MONTH_i_COUNT
FROM XXBAXY.XXBAXY_BREAKDOWN_TAB
WHERE LOSS_CAT = 'Maintenance Related Losses'
AND description = 'SAFTY SHEET PRO. ')
- (SELECT MONTH_i_COUNT
FROM XXBAXY.XXBAXY_BREAKDOWN_TAB
WHERE LOSS_CAT = 'Maintenance Related Losses'
AND description =
'PREVENTIVE MAINTENANCE '))
/ (SELECT MONTH_i_SUM FROM XXBAXY.XXBAXY_ATTR_SUM_TAB))
* 100,
3
)
INTO M_i
FROM DUAL;
EXCEPTION
WHEN ZERO_DIVIDE
THEN
M_i := 0;
END;
INSERT INTO M_Maintenance (M_i_Maintenance)
VALUES (M_i);
END LOOP;
END;

Let's first make your query easier to read and better perfoming, if I don't mistake it should be this one:
SELECT ROUND(
SUM(CASE description
WHEN 'SAFTY SHEET PRO. ' THEN 2*MONTH_i_COUNT
WHEN 'PREVENTIVE MAINTENANCE ' THEN -MONTH_i_COUNT
ELSE MONTH_i_COUNT
END) / MIN(MONTH_i_SUM) *100, 3)
FROM XXBAXY.XXBAXY_BREAKDOWN_TAB
CROSS JOIN XXBAXY.XXBAXY_ATTR_SUM_TAB
WHERE LOSS_CAT = 'Maintenance Related Losses'
In this case your PL/SQL code can look like this (skipping the exception handler):
DECLARE
m NUMBER;
sqlstr VARCHAR2(1000);
BEGIN
FOR i IN 1..12 LOOP
sqlstr :=
'SELECT ROUND( '
' SUM(CASE description '
' WHEN ''SAFTY SHEET PRO. '' THEN 2*MONTH_'||i||'_COUNT '
' WHEN ''PREVENTIVE MAINTENANCE '' THEN -MONTH_'||i||'_COUNT '
' ELSE MONTH_'||i||'_COUNT '
' END) / MIN(MONTH_'||i||'_SUM) *100, 3) '
'FROM XXBAXY.XXBAXY_BREAKDOWN_TAB '
' CROSS JOIN XXBAXY.XXBAXY_ATTR_SUM_TAB '
'WHERE LOSS_CAT = ''Maintenance Related Losses''';
EXECUTE IMMEDIATE sqlstr INTO m;
EXECUTE IMMEDIATE 'INSERT INTO M_Maintenance (M_'||i||'_Maintenance) VALUES (:m)' USING m;
END LOOP;
END;

Did you mean, that your table XXBAXY.XXBAXY_BREAKDOWN_TAB contains fields MONTH_1_COUNT, MONTH_2_COUNT, MONTH_3_COUNT and so on? If so, first step should be is redesigning this table because of violation of 1NF. It's bad design, a way of immutable pain. If no, it's unclear why you try to use uninitialized local varables in SQL statement without any field references.
Generally it looks that you should avoid a FOR LOOP and PL/SQL and write a single insert/select statement which will produces all rows required.
Elaborating: your source has a common structure of
begin
for i in MIN..MAX loop
select something into value
from table1
where some_condition
and id = i;
insert into table2 values (value);
end loop;
end;
Almost always better way is do this as
insert into table2
select something
from table1
where some_condition
and id in (MIN..MAX);

Related

PL/SQL multiply Sql statements with if and then

Im trying to write a PL/SQL Code which should ask if something is there count it and
if tmp > 0
so if the object = 'VALID' then execute the rest of the sql statements if not skip everything in the Then case. When I Execute my code it dont execute the code.
DECLARE
tmp INT;
BEGIN
SELECT COUNT(*)
INTO tmp
FROM CDB_registry
WHERE Status = 'VALID';
IF tmp > 0 THEN
Col c.Status Format A6
Col Containers Format A20
Select c.Status, v.name as Containers
From Cdb_Ols_Status c, V\$Containers v
Where c.con_id = v.con_id
And c.Name = 'OLS_DIRECTORY_STATUS'
Order By v.Con_ID;
prompt ****************************************************************************************
DECLARE
tmp INT;
BEGIN
SELECT COUNT(*)
INTO tmp
FROM Cdb_Ols_Status
WHERE Status = 'TRUE'
AND name = 'OLS_DIRECTORY_STATUS';
IF tmp > 0 THEN
DBMS_Output.put_line('The Output is true');
ELSE
DBMS_Output.put_line('The Output is wrong');
END IF;
END;
/
ELSE
DBMS_Output.put_line('There is no value');
END IF;
END;
/
Here's one options; I presumed some things (read comments within code) & agree with #gsalem's comment.
You commented that
cdb_ols_status table does not exist when tmp < 0
It means that you'll have to use dynamic SQL (execute immediate) because - if table doesn't exist, code won't even compile. I guess that's why you said that code I previously posted won't work.
DECLARE
tmp INT;
BEGIN
SELECT COUNT (*)
INTO tmp
FROM cdb_registry
WHERE status = 'VALID';
IF tmp > 0
THEN
-- If this SELECT return only 1 row, you could declare local variables to fetch
-- those values INTO them.
-- Otherwise, it depends on what you want to do with them; if you'd just want to
-- DISPLAY their values (which is what your SQL*Plus COL ... FORMAT commands suggest),
-- then see whether a LOOP helps.
FOR cur_r IN ( SELECT c.status, v.name AS containers
FROM cdb_ols_status c, v$containers v
WHERE c.con_id = v.con_id
AND c.name = 'OLS_DIRECTORY_STATUS'
ORDER BY v.con_id)
LOOP
DBMS_OUTPUT.put_line (cur_r.status || ' - ' || cur_r.containers);
END LOOP;
-- The rest of the script should be ran because TMP value is greater than 0.
EXECUTE IMMEDIATE 'select count(*) from cdb_ols_status '
|| q'[where status = 'TRUE' and name = 'OLS_DIRECTORY-STATUS']'
INTO tmp;
IF tmp > 0
THEN
DBMS_OUTPUT.put_line ('The Output is true');
ELSE
DBMS_OUTPUT.put_line ('The Output is wrong');
END IF;
ELSE
DBMS_OUTPUT.put_line ('There is no value');
END IF;
END;
/

Errors in PLSQL -

Morning,
I'm trying to write a script that will convert Unload tables (UNLD to HDL files) creating a flat file using PLSQL. I keep getting syntax errors trying to run it and would appreciate some help from an expert out there!
Here are the errors:
Error(53,21): PLS-00330: invalid use of type name or subtype name
Error(57,32): PLS-00222: no function with name 'UNLDTABLE' exists in this scope
Our guess is that the unldTable variable is being treated as a String, rather than a database table object (Not really expereinced in PLSQL)
CREATE OR REPLACE PROCEDURE UNLD_TO_HDL (processComponent IN VARCHAR2)
IS
fHandle UTL_FILE.FILE_TYPE;
concatData VARCHAR2(240);
concatHDLMetaTags VARCHAR2(240);
outputFileName VARCHAR2(240);
TYPE rowArrayType IS TABLE OF VARCHAR2(240);
rowArray rowArrayType;
emptyArray rowArrayType;
valExtractArray rowArrayType;
hdlFileName VARCHAR2(240);
unldTable VARCHAR2(240);
countUNLDRows Number;
dataType VARCHAR2(240);
current_table VARCHAR2(30);
value_to_char VARCHAR2(240);
BEGIN
SELECT HDL_FILE_NAME
INTO hdlFileName
FROM GNC_HDL_CREATION_PARAMS
WHERE PROCESS_COMPONENT = processComponent;
SELECT UNLD_TABLE
INTO unldTable
FROM GNC_HDL_CREATION_PARAMS
WHERE PROCESS_COMPONENT = processComponent
FETCH NEXT 1 ROWS ONLY;
SELECT LISTAGG(HDL_META_TAG,'|')
WITHIN GROUP(ORDER BY HDL_META_TAG)
INTO concatHDLMetaTags
FROM GNC_MIG_CONTROL
WHERE HDL_COMP = processComponent;
SELECT DB_FIELD
BULK COLLECT INTO valExtractArray
FROM GNC_MIG_CONTROL
WHERE HDL_COMP = processComponent
ORDER BY HDL_META_TAG;
fHandle := UTL_FILE.FOPEN('./', hdlFileName, 'W');
UTL_FILE.PUTF(fHandle, concatHDLMetaTags + '\n');
SELECT num_rows INTO countUNLDRows FROM user_tables where table_name = unldTable;
FOR row in 1..countUNLDRows LOOP
rowArray := emptyArrayType;
FOR value in 1..valExtractArray.COUNT LOOP
rowArray.extend();
SELECT data_type INTO dataType FROM all_tab_columns where table_name = unldTable AND column_name = valExtractArray(value);
IF dataType = 'VARCHAR2' THEN (SELECT valExtractArray(value) INTO value_to_char FROM current_table WHERE ROWNUM = row);
ELSIF dataType = 'DATE' THEN (SELECT TO_CHAR(valExtractArray(value),'YYYY/MM/DD') INTO value_to_char FROM current_table WHERE ROWNUM = row);
ELSIF dataType = 'NUMBER' THEN (SELECT TO_CHAR(valExtractArray(value)) INTO value_to_char FROM current_table WHERE ROWNUM = row);
ENDIF;
rowArray(value) := value_to_char;
END LOOP;
concatData := NULL;
FOR item in 1..rowArray.COUNT LOOP
IF item = rowArray.COUNT
THEN concatData := (COALESCE(concatData,'') || rowArray(item));
ELSE concatData := (COALESCE(concatData,'') || rowArray(item) || '|');
END IF;
END LOOP;
UTL_FILE.PUTF(fHandle, concatData + '/n');
END LOOP;
UTL_FILE.FCLOSE(fHandle);
END;
Thanks,
Adam
I believe it is just an overlook in your code. You define unldTable as a varchar, which is used correctly until you try to access it as if it were a varray on line 51
rowArray(value) := unldTable(row).valExtractArray(value);
Given that you have not defined it as a varray, unldTable(row) is making the interpreter believe that you are referring to a function.
EDIT
Now that you have moved on, you should resolve the problem of invoking SELECT statements on tables that are unknown at runtime. To do so you need to make use of Dynamic SQL; you can do it in several way, the most direct being an Execute immediate statement in your case:
mystatement := 'SELECT valExtractArray(value) INTO :value_to_char FROM ' || current_table || ' WHERE ROWNUM = ' || row;
execute immediate mystatement USING OUT value_to_char;
It looks like you need to generate a cursor as
select [list of columns from GNC_MIG_CONTROL.DB_FIELD]
from [table name from GNC_HDL_CREATION_PARAMS.UNLD_TABLE]
Assuming setup like this:
create table my_table (business_date date, id integer, dummy1 varchar2(1), dummy2 varchar2(20));
create table gnc_hdl_creation_params (unld_table varchar2(30), process_component varchar2(30));
create table gnc_mig_control (db_field varchar2(30), hdl_comp varchar2(30), hdl_meta_tag integer);
insert into my_table(business_date, id, dummy1, dummy2) values (date '2018-01-01', 123, 'X','Some more text');
insert into gnc_hdl_creation_params (unld_table, process_component) values ('MY_TABLE', 'XYZ');
insert into gnc_mig_control (db_field, hdl_comp, hdl_meta_tag) values ('BUSINESS_DATE', 'XYZ', '1');
insert into gnc_mig_control (db_field, hdl_comp, hdl_meta_tag) values ('ID', 'XYZ', '2');
insert into gnc_mig_control (db_field, hdl_comp, hdl_meta_tag) values ('DUMMY1', 'XYZ', '3');
insert into gnc_mig_control (db_field, hdl_comp, hdl_meta_tag) values ('DUMMY2', 'XYZ', '4');
You could build a query like this:
select unld_table, listagg(expr, q'[||'|'||]') within group (order by hdl_meta_tag) as expr_list
from ( select t.unld_table
, case tc.data_type
when 'DATE' then 'to_char('||c.db_field||',''YYYY-MM-DD'')'
else c.db_field
end as expr
, c.hdl_meta_tag
from gnc_hdl_creation_params t
join gnc_mig_control c
on c.hdl_comp = t.process_component
left join user_tab_columns tc
on tc.table_name = t.unld_table
and tc.column_name = c.db_field
where t.process_component = 'XYZ'
)
group by unld_table;
Output:
UNLD_TABLE EXPR_LIST
----------- --------------------------------------------------------------------------------
MY_TABLE to_char(BUSINESS_DATE,'YYYY-MM-DD')||'|'||ID||'|'||DUMMY1||'|'||DUMMY2
Now if you plug that logic into a PL/SQL procedure you could have something like this:
declare
processComponent constant gnc_hdl_creation_params.process_component%type := 'XYZ';
unloadSQL long;
unloadCur sys_refcursor;
text long;
begin
select 'select ' || listagg(expr, q'[||'|'||]') within group (order by hdl_meta_tag) || ' as text from ' || unld_table
into unloadSQL
from ( select t.unld_table
, case tc.data_type
when 'DATE' then 'to_char('||c.db_field||',''YYYY/MM/DD'')'
else c.db_field
end as expr
, c.hdl_meta_tag
from gnc_hdl_creation_params t
join gnc_mig_control c
on c.hdl_comp = t.process_component
left join user_tab_columns tc
on tc.table_name = t.unld_table
and tc.column_name = c.db_field
where t.process_component = processComponent
)
group by unld_table;
open unloadCur for unloadSQL;
loop
fetch unloadCur into text;
dbms_output.put_line(text);
exit when unloadCur%notfound;
end loop;
close unloadCur;
end;
Output:
2018/01/01|123|X|Some more text
2018/01/01|123|X|Some more text
Now you just have to make that into a procedure, change dbms_output to utl_file and add your meta tags etc and you're there.
I've assumed there is only one distinct unld_table per process component. If there are more you'll need a loop to work through each one.
For a slightly more generic approach, you could build a cursor-to-csv generator which could encapsulate the datatype handling, and then you'd only need to build the SQL as select [columns] from [table]. You might then write a generic cursor to file processor, where you pass in the filename and a cursor and it does the lot.
Edit: I've updated my cursor-to-csv generator to provide file output, so you just need to pass it a cursor and the file details.

Dynamic SQL - ORACLE

I have the following procedure, which does not compile correctly, because it refers to non existing objects (table does not exist)
Here is only a section of the code (i used generic names for tables and columns):
DECLARE
C INTEGER := 0;
BEGIN
SELECT COUNT(1) INTO C FROM USER_TABLES WHERE TABLE_NAME = 'MY_TABLE';
IF C > 0 THEN
DECLARE
CURSOR c_maps IS SELECT COLUM_NAME1, COLUM_NAME2 FROM MY_TABLE WHERE ACTIVE = 1;
BEGIN
FOR prec IN c_maps LOOP
some code...;
END LOOP;
EXECUTE IMMEDIATE 'some code..';
END;
END IF;
END;
/
I don't know how to write this statement dynamically, since the table "MY_TABLE" does not exist:
CURSOR c_maps IS SELECT COLUM_NAME1, COLUM_NAME2 FROM MY_TABLE WHERE ACTIVE =1;
I also tried to write it like:
CURSOR c_maps IS SELECT COLUM_NAME1, COLUM_NAME2 FROM (Select 'MY_TABLE' from dual) WHERE ACTIVE = 1;
However, than it refers to the column "ACTIVE" which also does not exist at compile time...It is possible to write the whole procedure inside "execute immediate" - block? I have tried different variants, however without success
You may need to open the cursor in a different way, so that the non existing table is only referred in dynamic SQL; for example:
declare
c integer := 0;
curs sys_refcursor;
v1 number;
v2 number;
begin
select count(1)
into c
from user_tables
where table_name = 'MY_TABLE';
if c > 0
then
open curs for 'select column_name1, column_name2 from my_table where active = 1';
loop
fetch curs into v1, v2;
exit when curs%NOTFOUND;
dbms_output.put_line(v1 || ' - ' || v2);
end loop;
else
dbms_output.put_line('The table does not exist');
end if;
end;
/

Oracle - iterate over tables and check for values in attribute

For all tables in X, while X is
select table_name from all_tab_cols
where column_name = 'MY_COLUMN'
and owner='ADMIN'
I need to check, if the column MY_COLUMN has other values than 'Y' or 'N' and if it does, print out the table name.
Pseudo code:
for table in X:
if MY_COLUMN !='Y' or MY_COLUMN !='N':
print table
How to implement that in PL/SQL, with cursors I guess?
Following should work:
DECLARE
counter NUMBER;
cursor c1 is
select table_name from all_tab_cols
where column_name = 'MY_COLUMN'
and owner='ADMIN';
BEGIN
FOR rec IN c1 LOOP
DBMS_OUTPUT.PUT_LINE(rec.table_name);
EXECUTE IMMEDIATE 'select count(*) into :counter from '|| rec.table_name ||' where MY_COLUMN!= ''Y'' and MY_COLUMN!= ''N'' ';
if counter > 0 then
DBMS_OUTPUT.PUT_LINE(rec.table_name);
end if;
END LOOP;
END;
Basically we open a cursor with all tables containing that column, do a count for rows that have different values than Y or N, and if that count > 0, print the table.
The version of Wouter does not work for me.
Had to remove the semicolon (Oracle Database version 11.2.0.4.0 )
DECLARE
counter NUMBER;
BEGIN
select count(*) into counter from LASTID;
dbms_output.put_line(counter);
END;
/

How to put string of values with IN clause using dynamic PL/SQL?

Using Oracle PL/SQL, how can I populate the bind variable :b3 with more than one value for the IN clause? (This code is for demo purposes only -- it may not compile but it does clarify the question if any is needed)
declare
type work_rec is record (
work_status varchar2(50),
work_cd varchar2(50));
type work_tab is table of work_rec index by pls_integer;
t_work_tab work_tab;
sql_stmt varchar2(400);
begin
select case
when status_desc like '%Employed%' then 'Employed'
else 'Unknown'
end as work_status
,case
when status_cd between '1' and '9' then '1,2,3,4'
else '0'
end as work_cd
bulk collect into t_work_tab
from employee_table;
for i in t_work_tab.first..t_work_tab.last
loop
sql_stmt := 'insert into employee_hist
select name,
employer
from tax_table
where employment_cd in (:b3)'; --< how to populate this with '1','2','3','4'
execute immediate sql_stmt using t_work_tab(i).work_cd;
commit;
end loop;
end;
/
When you loop through the values, keep appending the string with ' and , as required to make up the part of in. Then you can use that string as part of your sql statement.
Example
temp = "'"
Loop through values a,b,c,d as str
temp = temp + str + "'",
End Loop
temp = substr(temp,0,length(temp)) // this is to trim the last , character
Hope it helps!
You need to prepare the in list inside the loop using another variable and then the statement outside it. Something like this may help:
declare
type work_rec is record (
work_status varchar2(50),
work_cd varchar2(50));
type work_tab is table of work_rec index by pls_integer;
t_work_tab work_tab;
sql_stmt VARCHAR2(400);
v_in_str varchar2(100);_
begin
select case
when status_desc like '%Employed%' then 'Employed'
else 'Unknown'
end as work_status
,case
when status_cd between '1' and '9' then '1,2,3,4'
else '0'
end as work_cd
bulk collect into t_work_tab
from employee_table;
for i in t_work_tab.first..t_work_tab.last
loop
v_in_str := v_in_str || t_work_tab(i).work_cd || ',';
END loop;
v_in_str :=rtrim(v_in_str, ',');
sql_stmt := 'insert into employee_hist
select name,
employer
from tax_table
where employment_cd in ('||v_in_str||')';
execute immediate sql_stmt;
commit;
end;
you could use a plsql collection as bind variable, this seems the nicer solution to me:
declare
type t_nbr_tbl is table of number;
type work_rec is record (
work_status varchar2(50),
work_cd t_nbr_tbl;
type work_tab is table of work_rec index by pls_integer;
t_work_tab work_tab;
sql_stmt varchar2(400);
begin
select case
when status_desc like '%Employed%' then 'Employed'
else 'Unknown'
end as work_status
,case
when status_cd between '1' and '9' then t_nbr_tbl(1,2,3,4)
else t_nbr_tbl(0)
end as work_cd
bulk collect into t_work_tab
from employee_table;
for i in t_work_tab.first..t_work_tab.last
loop
sql_stmt := 'insert into employee_hist
select name,
employer
from tax_table
where employment_cd in (select column_value from table(:b3))'; --< how to populate this with '1','2','3','4'
execute immediate sql_stmt using t_work_tab(i).work_cd;
commit;
end loop;
end;
/
Better solution (at least to my issue) - don't use a bind variable but instead concatenate the string of values (code segment shown only):
for i in t_work_tab.first..t_work_tab.last
loop
sql_stmt := 'insert into employee_hist
select name,
employer
from tax_table
where employment_cd in (' || t_work_tab(i).work_cd || ')';
execute immediate sql_stmt;
...
You get the idea. Thanks for all your input.