how to insert multiple rows from a source table into a single row in target table using cursor in oracle [duplicate] - oracle

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
How can multiple rows be concatenated into one in Oracle without creating a stored procedure?
create table pr_info(
pr_ref varchar2(10),
pr_text varchar2(3),
pr_key varchar2(12)
)
This table contains the data in the following format
pr_ref pr_text pr_key
a1 abc qwertyui01
a1 def qwertyui02
b1 aaa zxcvbnmj01
b1 bbb zxcvbnmj02
b1 ccc zxcvbnmj03
That is if the pr_text is more than 3 characters long then the record is split and placed in a new record with same pr_ref but different pr_key(in this case the first 8 characters will remain the same but the last two character will signify the sequence of the record)
So now i need to put the data of this table into a new table which has the following sprecification
create table pv_cus(pv_ref vrachar2(10),pv_text varchar2(100))
So basically i need to concatenate the rows belonging to same person from the source table and put it in one row in target table.
pv_ref pv_text
a1 abc,def
b1 aaa,bbb,ccc

Procedural approach
DECLARE
type pv_ref_t is TABLE of pv_cus.pv_ref%type;
type pv_text_t is TABLE of pv_cus.pv_text%type;
v_pv_ref_tab pv_ref_t;
v_pv_text_tab pv_text_t;
v_last_pr_ref pr_info.pr_ref%type;
BEGIN
v_pv_ref_tab := pv_ref_t();
v_pv_text_tab := pv_text_t();
FOR rec in (SELECT pr_ref, pr_text FROM pr_info order by pr_ref, pr_key)
LOOP
IF v_last_pr_ref IS NULL
OR v_last_pr_ref != rec.pr_ref
THEN
v_last_pr_ref := rec.pr_ref;
v_pv_ref_tab.extend(1);
v_pv_text_tab.extend(1);
v_pv_ref_tab(v_pv_ref_tab.last) := rec.pr_ref;
v_pv_text_tab(v_pv_text_tab.last) := rec.pr_text;
ELSE
-- tbd: check length of v_pv_text_tab(v_pv_text_tab.last)
v_pv_text_tab(v_pv_text_tab.last) := v_pv_text_tab(v_pv_text_tab.last) || ',' || rec.pr_text;
END IF;
END LOOP;
FORALL i in 1..v_pv_ref_tab.last
INSERT INTO pv_cus (pv_ref, pv_text) VALUES(v_pv_ref_tab(i), v_pv_text_tab(i))
;
END;
/

Related

Using EXECUTE IMMEDIATE based on entries of table

I (using Oracle 12c, PL/SQL) need to update an existing table TABLE1 based on information stored in a table MAP. In a simplified version, MAP looks like this:
COLUMN_NAME
MODIFY
COLUMN1
N
COLUMN2
Y
COLUMN3
N
...
...
COLUMNn
Y
COLUMN1 to COLUMNn are column names in TABLE1 (but there are more columns, not just these). Now I need to update a column in TABLE1 if MODIFY in table MAP contains a 'Y' for that columns' name. There are other row conditions, so what I would need would be UPDATE statements of the form
UPDATE TABLE1
SET COLUMNi = value_i
WHERE OTHER_COLUMN = 'xyz_i';
where COLUMNi runs through all the columns of TABLE1 which are marked with MODIFY = 'Y' in MAP. value_i and xyz_i also depend on information stored in MAP (not displayed in the example).
The table MAP is not static but changes, so I do not know in advance which columns to update. What I did so far is to generate the UPDATE-statements I need in a query from MAP, i.e.
SELECT <Text of UPDATE-STATEMENT using row information from MAP> AS SQL_STMT
FROM MAP
WHERE MODIFY = 'Y';
Now I would like to execute these statements (possibly hundreds of rows). Of course I could just copy the contents of the query into code and execute, but is there a way to do this automatically, e.g. using EXECUTE IMMEDIATE? It could be something like
BEGIN
EXECUTE IMMEDIATE SQL_STMT USING 'xyz_i';
END;
only that SQL_STMT should run through all the rows of the previous query (and 'xyz_i' varies with the row as well). Any hints how to achieve this or how one should approach the task in general?
EDIT: As response to the comments, a bit more background how this problem emerges. I receive an empty n x m Matrix (empty except row and column names, think of them as first row and first column) quarterly and need to populate the empty fields from another process.
The structure of the initial matrix changes, i.e. there may be new/deleted columns/rows and existing columns/rows may change their position in the matrix. What I need to do is to take the old version of the matrix, where I already have filled the empty spaces, and translate this into the new version. Then, the populating process merely looks if entries have changed and if so, alters them.
The situation from the question arises after I have translated the old version into the new one, before doing the delta. The new matrix, populated with the old information, is TABLE1. The delta process, over which I have no control, gives me column names and information to be entered into the cells of the matrix (this is table MAP). So I need to find the column in the matrix labeled by the delta process and then to change values in rows (which ones is specified via other information provided by the delta process)
Dynamic SQL it is; here's an example, see if it helps.
This is a table whose contents should be modified:
SQL> select * from test order by id;
ID NAME SALARY
---------- ---------- ----------
1 Little 100
2 200
3 Foot 0
4 0
This is the map table:
SQL> select * from map;
COLUMN CB_MODIFY VALUE WHERE_CLAUSE
------ ---------- ----- -------------
NAME Y Scott where id <= 3
SALARY N 1000 where 1 = 1
Procedure loops through all columns that are set to be modified, composes the dynamic update statement and executes it:
SQL> declare
2 l_str varchar2(1000);
3 begin
4 for cur_r in (select m.column_name, m.value, m.where_clause
5 from map m
6 where m.cb_modify = 'Y'
7 )
8 loop
9 l_str := 'update test set ' ||
10 cur_r.column_name || ' = ' || chr(39) || cur_r.value || chr(39) || ' ' ||
11 cur_r.where_clause;
12 execute immediate l_str;
13 end loop;
14 end;
15 /
PL/SQL procedure successfully completed.
Result:
SQL> select * from test order by id;
ID NAME SALARY
---------- ---------- ----------
1 Scott 100
2 Scott 200
3 Scott 0
4 0
SQL>

OUT parameter with multiples values

create or replace PROCEDURE Show_R(A IN VARCHAR2, B OUT VARCHAR2)
IS
BEGIN
select func_w(day),TO_CHAR(hour, 'HH24:MI')INTO B
from task t
inner join mat m
on t.id_p = m.id_a
where m.cod_mod = A;
END;
I have a issue with this code, this select gets two types of columns data that are not the same type of data, i don't know how to add into B two types of data in only one "out parameter"
You can't put 2 values into 1 OUT parameter. So, use 2 OUT parameters.
Firstly don't store day and hour in separate columns. Just use a single DATE column as, in Oracle, the DATE data type has year, month, day, hour, minute and second components and so can store both the date and time.
Secondly, don't use A, B, show_R or func_w identifiers; use meaningful names as it will be far easier to debug your code in 6-months if you can tell what it is intended to do.
Third, your SELECT ... INTO statement will fail as you have two columns but only one variable to select into; you need 2 variables in INTO clause and this means (unless you are going to concatenate the two values) that you need 2 OUT parameters.
CREATE PROCEDURE Show_w_day_and_hour(
i_cod_mod IN mat.cod_mod%TYPE,
o_w_day OUT VARCHAR2,
o_hour OUT VARCHAR2
)
IS
BEGIN
SELECT func_w(day),
TO_CHAR(hour, 'HH24:MI')
INTO o_w_day,
o_hour
FROM task t
INNER JOIN mat m
ON ( t.id_p = m.id_a )
WHERE m.cod_mod = i_cod_mod;
END;
/
db<>fiddle

Dynamically generate DDL from text file input fields for table output

Consider the following three files:
1.csv [contains 3 fields: a, b, c]
2.csv [contains 4 fields: d, e, f, g]
3.csv [contains 2 fields: h, i]
My assignment is to load all three files to their respective table output. So
File "*.csv" ->loads-> Table "*_csv"
I know I can process multiple files with the "Get File Names" step but how do I generate a DDL statement that creates the target table for each file? I am looking at the metadata injection step but I am not sure this fits my needs.
Any advice?
Pentaho Data Integration 7.0
Postgres RDS
You can below procedure to create the table dynamically.The only prob in this procedure is it is creating table with the same name. play with this code. I created this code in mysql. Prerequisite is you have to pass field names in concatenated form like 'col1,col2,col3'.
delimiter $$
create procedure dynamic_table (col_concat varchar(2000),out query1 varchar(1000))
begin
declare i integer ;
declare v_count int;
declare v_col varchar(100);
set i =1;
select LENGTH(col_concat) - LENGTH(REPLACE(col_concat, ',', ''))+1 into v_count;
set query1 =(select concat('create table table',convert(i,signed),' ( '));
while (i<= v_count)
do
begin
select replace(substring_index(col_concat,',',i),',','_') into v_col;
set query1 = (select concat(query1, v_col,' varchar(1000), '));
set i=i+1;
end;
end while;
select query1;
set query1= replace(query1,substring(query1,length(query1)-1,1),' )');
select query1;
end;$$
sample run : call dynamic_table ('col1,col2,col3',#query1)

Fastest way of doing field comparisons in the same table with large amounts of data in oracle

I am recieving information from a csv file from one department to compare with the same inforation in a different department to check for discrepencies (About 3/4 of a million rows of data with 44 columns in each row). After I have the data in a table, I have a program that will take the data and send reports based on a HQ. I feel like the way I am going about this is not the most efficient. I am using oracle for this comparison.
Here is what I have:
I have a vb.net program that parses the data and inserts it into an extract table
I run a procedure to do a full outer join on the two tables into a new table with the fields in one department prefixed with '_c'
I run another procedure to compare the old/new data and update 2 different tables with detail and summary information. Here is code from inside the procedure:
DECLARE
CURSOR Cur_Comp IS SELECT * FROM T.AEC_CIS_COMP;
BEGIN
FOR compRow in Cur_Comp LOOP
--If service pipe exists in CIS but not in FM and the service pipe has status of retired in CIS, ignore the variance
If(compRow.pipe_num = '' AND cis_status_c = 'R')
continue
END IF
--If there is not a summary record for this HQ in the table for this run, create one
INSERT INTO t.AEC_CIS_SUM (HQ, RUN_DATE)
SELECT compRow.HQ, to_date(sysdate, 'DD/MM/YYYY') from dual WHERE NOT EXISTS
(SELECT null FROM t.AEC_CIS_SUM WHERE HQ = compRow.HQ AND RUN_DATE = to_date(sysdate, 'DD/MM/YYYY'))
-- Check fields and update the tables accordingly
If (compRow.cis_loop <> compRow.cis_loop_c) Then
--Insert information into the details table
INSERT INTO T.AEC_CIS_DET( Fac_id, Pipe_Num, Hq, Address, AutoUpdatedFl,
DateTime, Changed_Field, CIS_Value, FM_Value)
VALUES(compRow.Fac_ID, compRow.Pipe_Num, compRow.Hq, compRow.Street_Num || ' ' || compRow.Street_Name,
'Y', sysdate, 'Cis_Loop', compRow.cis_loop, compRow.cis_loop_c);
-- Update information into the summary table
UPDATE AEC_CIS_SUM
SET cis_loop = cis_loop + 1
WHERE Hq = compRow.Hq
AND Run_Date = to_date(sysdate, 'DD/MM/YYYY')
End If;
END LOOP;
END;
Any suggestions of an easier way of doing this rather than an if statement for all 44 columns of the table? (This is run once a week if it matters)
Update: Just to clarify, there are 88 columns of data (44 of duplicates to compare with one suffixed with _c). One table lists each field in a row that is different so one row can mean 30+ records written in that table. The other table keeps tally of the number of discrepencies for each week.
First of all I believe that your task can be implemented (and should be actually) with staight SQL. No fancy cursors, no loops, just selects, inserts and updates. I would start with unpivotting your source data (it is not clear if you have primary key to join two sets, I guess you do):
Col0_PK Col1 Col2 Col3 Col4
----------------------------------------
Row1_val A B C D
Row2_val E F G H
Above is your source data. Using UNPIVOT clause we convert it to:
Col0_PK Col_Name Col_Value
------------------------------
Row1_val Col1 A
Row1_val Col2 B
Row1_val Col3 C
Row1_val Col4 D
Row2_val Col1 E
Row2_val Col2 F
Row2_val Col3 G
Row2_val Col4 H
I think you get the idea. Say we have table1 with one set of data and the same structured table2 with the second set of data. It is good idea to use index-organized tables.
Next step is comparing rows to each other and storing difference details. Something like:
insert into diff_details(some_service_info_columns_here)
select some_service_info_columns_here_along_with_data_difference
from table1 t1 inner join table2 t2
on t1.Col0_PK = t2.Col0_PK
and t1.Col_name = t2.Col_name
and nvl(t1.Col_value, 'Dummy1') <> nvl(t2.Col_value, 'Dummy2');
And on the last step we update difference summary table:
insert into diff_summary(summary_columns_here)
select diff_row_id, count(*) as diff_count
from diff_details
group by diff_row_id;
It's just rough draft to show my approach, I'm sure there is much more details should be taken into account. To summarize I suggest two things:
UNPIVOT data
Use SQL statements instead of cursors
You have several issues in your code:
If(compRow.pipe_num = '' AND cis_status_c = 'R')
continue
END IF
"cis_status_c" is not declared. Is it a variable or a column in AEC_CIS_COMP?
In case it is a column, just put the condition into the cursor, i.e. SELECT * FROM T.AEC_CIS_COMP WHERE not (compRow.pipe_num = '' AND cis_status_c = 'R')
to_date(sysdate, 'DD/MM/YYYY')
That's nonsense, you convert a date into a date, simply use TRUNC(SYSDATE)
Anyway, I think you can use three single statements instead of a cursor:
INSERT INTO t.AEC_CIS_SUM (HQ, RUN_DATE)
SELECT comp.HQ, trunc(sysdate)
from AEC_CIS_COMP comp
WHERE NOT EXISTS
(SELECT null FROM t.AEC_CIS_SUM WHERE HQ = comp.HQ AND RUN_DATE = trunc(sysdate));
INSERT INTO T.AEC_CIS_DET( Fac_id, Pipe_Num, Hq, Address, AutoUpdatedFl, DateTime, Changed_Field, CIS_Value, FM_Value)
select comp.Fac_ID, comp.Pipe_Num, comp.Hq, comp.Street_Num || ' ' || comp.Street_Name, 'Y', sysdate, 'Cis_Loop', comp.cis_loop, comp.cis_loop_c
from T.AEC_CIS_COMP comp
where comp.cis_loop <> comp.cis_loop_c;
UPDATE AEC_CIS_SUM
SET cis_loop = cis_loop + 1
WHERE Hq IN (Select Hq from T.AEC_CIS_COMP)
AND trunc(Run_Date) = trunc(sysdate);
They are not tested but they should give you a hint how to do it.

Delete duplicate records where column value may null

I have table which has two columns(CAGE,HCC) nulls allowed. I want to display duplicate records in my procedure, i wrote like this.
FOR REC IN (SELECT LOCATION, NIIN, INVL_DATE, CAGE,
HCC,SUM(CA_QTY) AS SUM_CA,SUM(COST_QTY) AS SUM_COST,COUNT(*) FROM INVENTORY
GROUP BY LOCATION, NIIN, INVL_DATE,
CAGE, HCC
HAVING COUNT(*)>1)
LOOP
VAR_LOC_NAME := REC.LOCATION;
VAR_NIIN := REC.NIIN;
VAR_DATE := REC.INVL_DATE;
VAR_CAGE := REC.CAGE;
VAR_HCC := REC.HCC;
VAR_CA_QTY := REC.SUM_CA;
VAR_COST_QTY := REC.SUM_COST;
FOR REC1 IN (SELECT SNO FROM INVENTORY WHERE LOCATION=VAR_LOC_NAME AND
NIIN=VAR_NIIN AND TUNC(INVL_DATE)=TRUNC(TO_DATE(VAR_DATE,'DD-MM-YY')) AND
CAGE=VAR_CAGE AND HCC=VAR_HCC)
LOOP
DBMS_OUTPUT.PUT_LINE('GET NUMBER '||REC1.SNO);
END LOOP;
end loop;
but for null values of CAGE and HCC it is not working.
FYI: I am using oracle 11g
You need to find some value, that is not used in CAGE and HCC columns, and replace NULL values with this unique value using Oracle's NVL function. So:
FOR REC IN (SELECT LOCATION, NIIN, INVL_DATE, NVL(CAGE,-1) as CAGE,
NVL(HCC,-1) as HCC,SUM(CA_QTY) AS SUM_CA,SUM(COST_QTY) AS SUM_COST,COUNT(*) FROM INVENTORY
GROUP BY LOCATION, NIIN, INVL_DATE,
NVL(CAGE,-1), NVL(HCC,-1)
HAVING COUNT(*)>1)
Of course -1 is just a sample here.
As for the second loop:
FOR REC1 IN (SELECT SNO FROM INVENTORY WHERE LOCATION=VAR_LOC_NAME AND
NIIN=VAR_NIIN AND TUNC(INVL_DATE)=TRUNC(TO_DATE(VAR_DATE,'DD-MM-YY')) AND
NVL(CAGE,-1)=VAR_CAGE AND NVL(HCC,-1)=VAR_HCC)
On printout you can replace -1 back with NULL.

Resources