Using Merge statement inside a cursor - sql

We have a requirement to populate a master table which consists of columns from a set of 20 different tables.
I have written a stored procedure to join some of the tables that return me max number of columns and have them in a cursor.
Now. I am using for loop to iterate through the cursor records so I can insert them into the master table.
How I can use a merge statement inside the cursor for loop so I can check if I need to update existing row or insert a new row depending if the records already exists or not.
Any ideas if we can use merge statement inside a cursor for loop? Any examples?

You can do a MERGE by selecting the cursor's data from DUAL. For example
Create a source and destination table with some data
SQL> create table src ( col1 number, col2 varchar2(10) );
Table created.
SQL> create table dest( col1 number, col2 varchar2(10) );
Table created.
SQL> insert into src values( 1, 'A' );
1 row created.
SQL> insert into src values( 2, 'B' );
1 row created.
SQL> insert into dest values( 1, 'C' );
1 row created.
SQL> commit;
Commit complete.
Run the merge
SQL> ed
Wrote file afiedt.buf
1 begin
2 for x in (select * from src)
3 loop
4 merge into dest
5 using( select x.col1 col1, x.col2 col2
6 from dual ) src
7 on( src.col1 = dest.col1 )
8 when matched then
9 update set col2 = src.col2
10 when not matched then
11 insert( col1, col2 )
12 values( src.col1, src.col2 );
13 end loop;
14* end;
SQL> /
PL/SQL procedure successfully completed.
And verify that the merge did what we wanted. Row 1 was updated and row 2 was inserted.
SQL> select * from dest;
COL1 COL2
---------- ----------
1 A
2 B
However, it generally wouldn't make too much sense to structure the code this way. You'd generally be better off putting the query that you'd use to open the cursor into the MERGE statement directly so that rather than selecting one row of data from DUAL, you're selecting all the data you want to merge from all the tables you're trying to merge the data from. Of course, it may make sense to create a view for this query that the MERGE statement can query in order to keep the MERGE statement readable.

Related

Fetching a column value for current row in before insert Oracle trigger

I am creating a trigger in oracle which gets triggered before any insert statement. I want to insert a value in column2 using that trigger, but the problem is, I have to first get the the value of another column which is column 1 , of the new row being inserted and based on that value ,I will write business logic to insert the value in column2.
I am unable to access the value of column1 for the new row which will be inserted. How can I access the values of the new row being inserted.
Scenario is like this:
Value for column2 has to be inserted using trigger.
But, to popluate value in column2 , first value for column1 from the row which is going to be inserted, is required.
and Based on the value of column1, value for column2 will be calculated and inserted.
Please help me with syntax and proper resolution.
Use Case is:
Suppose column1 value is TS-1
then column2 value has to be TS-1-1, for the next time column2 value will be
TS-1-2 and so on. SO there could be multiple values for TS-1 in the table, and for that every time vales for column2 will increase as TS-1-,TS-1-2, TS-1-3 and so on, last digit gets incremented for column2.
So I will have the fetch the maximum value of last number getting incremented and increase it by 1 for each insert.
Please help
Once you create such a trigger as below :
SQL> create or replace trigger trg_tab_bi
before insert on tab
for each row
declare
nr int;
begin
select count(1)+1
into nr
from tab
where col1 = :new.col1;
:new.col2 := :new.col1||'-'||nr;
end;
you may manage your desired duty like in a below way:
SQL> insert into tab(col1) values('TS-1');
1 row inserted
SQL> select t.*
2 from tab t
3 order by col1, col2;
COL1 COL2
------ ------
TS-1 TS-1-1
SQL> insert into tab(col1) values('TS-1');
1 row inserted
SQL> select t.*
2 from tab t
3 order by col1, col2;
COL1 COL2
------ ------
TS-1 TS-1-1
TS-1 TS-1-2
SQL> insert into tab(col1) values('TS-1');
1 row inserted
SQL> select t.*
2 from tab t
3 order by col1, col2;
COL1 COL2
------ ------
TS-1 TS-1-1
TS-1 TS-1-2
TS-1 TS-1-3
SQL> insert into tab(col1) values('TS-7');
1 row inserted
SQL> select t.*
2 from tab t
3 order by col1, col2;
COL1 COL2
------ ------
TS-1 TS-1-1
TS-1 TS-1-2
TS-1 TS-1-3
TS-7 TS-7-1
SQL> insert into tab(col1) values('TS-7');
1 row inserted
SQL> select t.*
2 from tab t
3 order by col1, col2;
COL1 COL2
------ ------
TS-1 TS-1-1
TS-1 TS-1-2
TS-1 TS-1-3
TS-7 TS-7-1
TS-7 TS-7-2
You can use these two terms in a trigger :old to reference the old value and :new to reference the new value.
Here is an example from the Oracle documentation linked to above
CREATE OR REPLACE TRIGGER Print_salary_changes
BEFORE DELETE OR INSERT OR UPDATE ON Emp_tab
FOR EACH ROW WHEN (new.Empno > 0)
DECLARE
sal_diff number;
BEGIN
sal_diff := :new.sal - :old.sal;
dbms_output.put('Old salary: ' || :old.sal);
dbms_output.put(' New salary: ' || :new.sal);
dbms_output.put_line(' Difference ' || sal_diff);
END;
In this example the trigger fires BEFORE DELETE OR INSERT OR UPDATE :old.sal will contain the salary prior to the trigger firing and :new.sal will contain the new value.

How to validate data after update in oracle

I have two tables A and B which are not related.
SQL> select * from A;
OLD_ID R_ID
---------- ----------
TA-BC 1
TB-BC 2
TC-BC 3
TD-BC 4
TE-BC 5
TF-BC 6
TG-BC 7
8
SQL> select * from B;
NEW_ID OLD_ID S_CD
---------- ---------- -----
1 TA-BC A
2 TB-BC B
3 TC-BC C
4 TD-BC A
5 TE-BC B
6 TF-BC F
7 TG-BC C
8 TH-BC B
I need to update column "old_id" in table A with corresponding "new_id" values from table B where A.OLD_ID = B.OLD_ID.
I have written something like below. The data in table A and B has around 1 million records the one i gave above here is sample data. Since the data volume is high am updating for every 25k records and commiting it in a loop.
DECLARE
v_cnt number := 1;
BEGIN
WHILE v_cnt > 0 LOOP
UPDATE /*+ parallel(A 10) */ A a
SET a.old_id =
(SELECT DISTINCT new_id
FROM B b
WHERE b.old_id = a.old_id)
WHERE EXISTS
(SELECT 1
FROM B b1
WHERE b1.old_id = a.old_id and ROWNUM < 25000;
v_cnt := SQL%ROWCOUNT;
COMMIT;
END LOOP;
END;
/
I would like to know how can i print how many records got updated and how can i validate whether all the records in table A which has the matching record in table B with old_id has got updated correctly or not. What is the query i can write before/after the update statement to validate if table A "old_id" column has been updated correctly with values from table B "new_id" columns
Below is the table creation script.
create table A(old_id varchar2(10),r_id number);
insert into A values ('TA-BC',1);
insert into A values ('TB-BC',2);
insert into A values ('TC-BC',3);
insert into A values ('TD-BC',4);
insert into A values ('TE-BC',5);
insert into A values ('TF-BC',6);
insert into A values ('TG-BC',7);
insert into A(r_id) values(8);
commit;
create table B(new_id number,old_id varchar2(10),s_cd varchar2(5));
insert into B values (1,'TA-BC','A');
insert into B values (2,'TB-BC','B');
insert into B values (3,'TC-BC','C');
insert into B values (4,'TD-BC','A');
insert into B values (5,'TE-BC','B');
insert into B values (6,'TF-BC','F');
insert into B values (7,'TG-BC','C');
insert into B values (8,'TH-BC','B');
commit;
I don't see why you are replacing OLD_ID with NEW_ID when they are different data types: OLD_ID is char and NEW_ID is an integer.
It would be better to add a new field (column) to the table to store NEW_ID and update that.
You can then check the mapping of old to new has been performed correctly and take advantage of the fact the new id is the correct data type for joins to other tables using NEW_ID

Update with after insert trigger on same table

I have a to write a insert trigger on a tableA. which will perform update with same table but different column. I am getting error while doing this. My trigger is
create or replace trigger trigger_A
after insert on table_A
begin
update table_A set col1=1 where col1 is null;
end;
I have an application will perform col2 alone will be inserted and col1 will be kept null. so my trigger will give value for col1 once the row is inserted. But i am getting error saying "Trigger is failed and invalid" when a row is inserted.
How to do this. TIA.
If you want to assign a simple default value, the easiest way is to declare it on the table, using the DEFAULT clause.
SQL> create table t42
2 ( col1 number default 1 not null
3 , col2 date)
4 /
Table created.
SQL> insert into t42 (col2) values (sysdate)
2 /
1 row created.
SQL> select * from t42
2 /
COL1 COL2
---------- ---------
1 03-AUG-11
SQL>
This works with literals or pseudocolumns such as SYSDATE or USER. If you want to derive a more complicated value with a user-defined function or a sequence, you will need to use
a trigger.
Here is a new version of the table...
SQL> create table t42
2 ( col1 number default 1 not null
3 , col2 date default sysdate
4 , col3 varchar2(30) default user
5 , col4 number )
6 /
Table created.
SQL>
... with a trigger:
SQL> create or replace trigger t42_trg
2 before insert or update
3 on t42
4 for each row
5 begin
6 if :new.col4 is null
7 then
8 :new.col4 := my_seq.nextval;
9 end if;
10 end;
11 /
Trigger created.
SQL> insert into t42 (col1, col2, col3)
2 values (99, sysdate, 'MR KNOX')
3 /
1 row created.
SQL> select * from t42
2 /
COL1 COL2 COL3 COL4
---------- --------- ------------------------------ ----------
99 03-AUG-11 MR KNOX 161
SQL>
Note that although every column on the table is defaultable, I have to populate at least one column to make the SQL valid:
SQL> insert into t42 values ()
2 /
insert into t42 values ()
*
ERROR at line 1:
ORA-00936: missing expression
SQL>
But I can pass in NULL to COL4 to get a completely defaulted record:
SQL> insert into t42 (col4) values (null)
2 /
1 row created.
SQL> select * from t42
2 /
COL1 COL2 COL3 COL4
---------- --------- ------------------------------ ----------
99 03-AUG-11 MR KNOX 161
1 03-AUG-11 APC 162
SQL>
Caveat lector: my trigger uses the new 11g syntax. In previous versions we have to assign the sequence value using a SELECT statement:
select my_seq.nextval
into :new.col4
from dual;
You cannot update a table where the trigger is invoked:
Within a stored function or trigger, it is not permitted to modify a
table that is already being used (for reading or writing) by the
statement that invoked the function or trigger.
Doing so will generate Error 1442:
Error Code: 1442
Can't update table 'MyTable' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.
In short, we are not allowed to update the table in use - but your case is simple, only want to update the field if it's NULL, for this choose BEFORE INSERT ON trigger, this way you can update all the fields of the new/current entry/row (as it has not been entered yet):
DELIMITER //
DROP TRIGGER IF EXISTS trigger_A//
CREATE TRIGGER trigger_A BEFORE INSERT ON table_A
FOR EACH ROW BEGIN
IF NEW.col1 IS NULL THEN
set NEW.col1 = <some-value>;
ENF IF;
END;
//
DELIMITER ;

Query to output values which are not present in table

Need help in Oracle query
Requirement:
I have 5 rows in a table lets say ID = 1, 2, 3, 4, 5
Requirement is as such that user may pass any value as input and if that value is not there in table then query should return me the value which is not present.
Ex:
1. If user passes 9 then Oracle query should return the output as 9
2. If user passes 1,2,10 then Oracle query should return the output as 10
as 9 and 10 in above example are not in table.
I am using following query but not getting result.
SELECT ID
FROM TABLE_NAME WHERE ID NOT IN
(SELECT ID
FROM TABLE_NAME where ID NOT in (1,2,10))
create table z (id number);
Table created.
SQL> insert into z values (1);
1 row created.
SQL> insert into z values (2);
1 row created.
SQL> insert into z values (3);
1 row created.
SQL> insert into z values (4);
1 row created.
SQL> insert into z values (5);
1 row created.
SQL> select 10 id from dual
2 minus
3 select id from z;
ID
----------
10
You could use a nested table as input:
SQL> CREATE TABLE table_name (ID NUMBER NOT NULL);
Table created
SQL> INSERT INTO table_name (SELECT ROWNUM FROM dual CONNECT BY LEVEL <= 5);
5 rows inserted
SQL> CREATE TYPE tab_number AS TABLE OF NUMBER;
2 /
Type created
SQL> SELECT *
2 FROM TABLE(tab_number(1,2,10)) x
3 WHERE x.column_value NOT IN (SELECT ID FROM table_name);
COLUMN_VALUE
------------
10

oracle sql precision,scale ,insert calculate and drop

table = mytable
temp col = tempcol
col = mycol
currently contains 5000 rows various values from 99999.99999 to 0.00001
I need to keep the data create a script to create a temp column,round the values to 7,3 update mycol to a null value, modify my column from 10,5 to 7,3 return the data to mycol, drop the temp column. Job done.
so far
SELECT mycol
INTO tempcol
FROM mytable
update mytable set mycol = null
alter table mytable modify mycol number (7,3)
SELECT tempcol
INTO mycol
FROM mytable
drop tempcol
can you please fill in the missing gaps are direct me to a solution.
Well first of all a NUMBER(10,5) can store results from -99999 to 99999 while NUMBER(7,3) interval is only [-9999,9999] so you will potentially encounter conversion errors. You probably want to change the column into a NUMBER(8,3).
Now your plan seems sound: you can not reduce the precision or the scale of a column while there is data in that column, so you will store data into a temporary column. I would do it like this:
SQL> CREATE TABLE mytable (mycol NUMBER(10,5));
Table created
SQL> /* populate table */
2 INSERT INTO mytable
3 (SELECT dbms_random.value(0, 1e10)/1e5
4 FROM dual CONNECT BY LEVEL <= 1e3);
1000 rows inserted
SQL> /* new temp column */
2 ALTER TABLE mytable ADD (tempcol NUMBER(8,3));
Table altered
SQL> /* copy data to temp */
2 UPDATE mytable
3 SET tempcol = mycol,
4 mycol = NULL;
1000 rows updated
SQL> ALTER TABLE mytable MODIFY (mycol NUMBER(8,3));
Table altered
SQL> UPDATE mytable
2 SET mycol = tempcol;
1000 rows updated
SQL> /* cleaning */
2 ALTER TABLE mytable DROP COLUMN tempcol;
Table altered