fetch multiple rows, store in 1 variable and insert into table - sql

I have to fetch bulk record and insert into table using loop
I have little confusion how to fetch and insert record using loop. Below I have shared what I have done so far .
declare
stud_Id varchar;
begin
stud_Id := select student_id from student_backup where is_active_flg ='Y';
for i in 1 ..stud_Id.count
loop
insert into users(student_id,password,status) values(stud_Id(i),'password','status')
where not exists (select student_id from users where student_id=stud_Id(i))
end loop;
commit;
end;
/

You can use the following :
declare
stud_Id student_backup.student_id%type;
begin
select nvl(max(student_id),0) into stud_Id
from student_backup
where is_active_flg ='Y';
if stud_Id >0 then
for i in 1 ..stud_Id
loop
insert into users(student_id,password,status)
select b.student_id,'password','status'
from student_backup b
left join users u on b.student_id = u.student_id
where is_active_flg ='Y'
and b.student_id = i;
end loop;
end if;
commit;
end;
/
Demo
P.S. If I understood you want to perform, you don't need to use for loop(including if statement) and the select statement in the beginning, but directly apply the insert statement by removing the part and b.student_id = i.
So, convert your block to the one as below :
declare
stud_Id student_backup.student_id%type;
begin
insert into users(student_id,password,status)
select b.student_id,'password','status'
from student_backup b
left join users u on b.student_id = u.student_id
where is_active_flg ='Y' ;
commit;
end;
/

Abdul,
I think you are searching for the following:
BEGIN
INSERT INTO USERS
SELECT STUDENT_ID, PASSWORD , STATUS
FROM student_backup
WHERE STUDENT_ID NOT IN (SELECT STUDENT_ID FROM USERS)
AND is_active_flg = 'Y';
END;
/
Hope, this will be useful.
Demo

Related

BULK COLLECT in a trigger

I need to create a trigger that updates a column in a table based on a transaction that happens in another table. Is there a way to use BULK COLLECT on joining two tables?
I need to collect data multiple times, is it possible to use BULK COLLECT inside another BULK COLLECT?
HERE is my existing trigger
create or replace trigger trans_hist_trg
AFTER INSERT OR UPDATE OF reason ON transaction_history
FOR EACH ROW
DECLARE
v_exists VARCHAR2(1);
v_valid code.valid_code%TYPE;
v_person_id person.id%TYPE;
TYPE Anumber_Type is TABLE of person.registration_number%TYPE INDEX BY binary_INTEGER;
v_NumberList Anumber_Type;
v_primaryAnumber person.primary_number%TYPE;
v_secondaryAnumber consolidated_numbers.secondary_number%TYPE;
v_anumber person.registration_number%TYPE;
BEGIN
IF(INSERTING) THEN
v_person_id := :NEW.person_id;
ELSE
v_person_id := :OLD.person_id;
END IF;
BEGIN
SELECT p.registration_number, p.primary_number, c.secondary_number INTO v_anumber, v_primaryAnumber, v_secondaryAnumber
FROM person p
LEFT JOIN consolidated_numbers c ON p.id = c.person_id WHERE p.id = v_person_id;
END;
BEGIN
SELECT women_act INTO v_exists
FROM person
WHERE id = v_person_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
v_exists := NULL;
END;
IF v_exists IS NULL AND :NEW.type_id IN (10,20,30,40,50) THEN
IF :NEW.reason NOT IN ('A1','B1') OR (:NEW.reason IN ('A1','B1') AND :NEW.action_date >= '01-JAN-00') THEN
BEGIN
SELECT valid_code INTO v_valid
FROM code
WHERE valid_code = :NEW.reason;
EXCEPTION
WHEN NO_DATA_FOUND THEN
v_exists := null;
END;
IF v_valid IS NOT NULL THEN
SELECT CASE
WHEN EXISTS (SELECT 1 FROM code WHERE valid_code = v_valid)
THEN 'Y' ELSE 'N' END INTO v_exists FROM dual;
END IF;
IF v_exists = 'Y' THEN
select registration_number BULK COLLECT into v_NumberList
FROM person where (registration_number=v_primaryAnumber OR
registration_number=v_anumber OR
registration_number=v_secondaryAnumber OR
primary_number= v_secondaryAnumber OR
primary_number=v_anumber OR
primary_number=v_primaryAnumber ) and (primary_number IS NOT NULL or primary_number <>'000000');
ELSE
select registration_number BULK COLLECT into v_NumberList
FROM person where (registration_number=v_primaryAnumber OR
registration_number=v_anumber OR
registration_number=v_secondaryAnumber OR
primary_number=v_anumber OR
primary_number=v_secondaryAnumber OR
primary_number=v_primaryAnumber OR
primary_number IS NULL);
END IF;
FOR indx IN 1 .. v_NumberList.COUNT
LOOP
update person set women_act = 'X'
where registration_number=v_NumberList(indx) and (women_act<>'X' or women_act IS NULL);
END LOOP;
update person set women_act = 'X'
where registration_number=v_anumber and (women_act<>'X' or women_act IS NULL);
END IF;
END IF;
mmm
END trans_hist_trg;
I need to make this block of code to be my main outer loop to iterate through all the numbers. But I'm unsure how. Please help.
SELECT p.registration_number, p.primary_number, c.secondary_number INTO v_anumber, v_primaryAnumber, v_secondaryAnumber
FROM person p
LEFT JOIN consolidated_numbers c ON p.id = c.person_id WHERE p.id = v_person_id;
Thank you!

Logic of my Stored Procedure, Loop cursors

Let's see if i can make this clear. Basically what i want to do and i don't know how is this: inside my loop how can i iterate those 2 cursors? After fetching those rows i want to insert in those 2 tables as you can see in the snippet :
CREATE OR REPLACE PROCEDURE add_docs
IS
dom_doc DOM_DOCUMENT.DOMAIN_DOC%TYPE;
type_doc_pk TYPE_DOCS.TYPE_DOC_PK%TYPE;
type_doc TYPE_DOCS.TYPE_DOCUMENT%TYPE;
user_code TYPE_DOCS.USERCODE%TYPE;
result_code ECM_TIPO_DOCS.CODIGO_RESULTADO%TYPE;
LS_LOCAL INTEGER;
l_id INTEGER;
cursor get_local
is
SELECT ls_local_pk FROM rt_local_ls
rc_loc c_loc%ROWTYPE;
cursor get_docs
is
SELECT DOM_DOCUMENT.DOMAIN_DOC INTO dom_doc,
TYPE_DOCS.TYPE_DOC_PK INTO type_doc_pk,
TYPE_DOCS.TYPE_DOCUMENT INTO type_doc,
TYPE_DOCS.USERCODE INTO user_code,
TYPE_DOCS.CODE_RESULT INTO result_code
FROM TYPE_DOCS
JOIN DOM_TDOC_SIS
ON TYPE_DOCS.TYPE_DOC_PK = DOM_TDOC_SIS.TYPE_DOC_PK
JOIN DOM_DOCUMENT
ON DOM_TDOC_SIS.DOMAIN_DOC_PK = DOM_DOCUMENT.DOMAIN_DOC_PK
WHERE DOM_DOCUMENT.DOMAIN_DOC_PK IN (2, 10)
AND NOT EXISTS
(
SELECT 1
FROM TP_DOC_MAP
WHERE TP_DOC_MAP.LS_LOCAL_PK = LS_LOCAL ----this is the variable that i have to iterate it's what i am getting from the other cursor
AND TP_DOC_MAP.LS_SYSTEM_PK = 3
AND TP_DOC_MAP.ACTIVE = 1
AND TP_DOC_MAP.CODE = TYPE_DOCS.TYPE_DOC_PK
);
BEGIN
OPEN get_local;
FETCH get_local INTO rc_loc
IF get_local%FOUND
THEN
for md_local in get_local
LOOP
OPEN get_docs;
FETCH get_docs INTO....
---now this is where i don't know how to do inside this loop i want to repeat the cursor get_docs for each row in the cursor get_local
--and then insert with the values fetched for each iteration
INSERT INTO TP_DOC VALUES (
type_doc_pk,
type_doc,
1,
SYSDATE,
NULL)
RETURNING id INTO l_id;
INSERT INTO TP_DOC_MAP VALUES (
l_id,
LS_LOCAL,
3,
type_doc_pk,
1,
sysdate,
NULL
);
END LOOP
END IF;
END add_docs;
how can i do this? for each LS_LOCAL that exists it will have to run the sele
for each LS_LOCAL that exists, it will have to run the select in the cursor get_docs with the LS_LOCAL variable.
Embedded cursor FOR loops are one option. Here's how (I removed irrelevant parts of code to make it as simple as possible):
begin
for cur_l in (select ls_local_pk from rt_local_ls)
loop
for cur_d in (select domain_doc,
type_doc_pk, ...
from type_docs join dom_tdoc_sis ...
)
loop
insert into tp_doc ...
insert into tp_doc_map ...
end loop;
end loop;
end;

how update result of big query in oracle?

Can I update the result of query easily?
Assume I have big query which returns salary column and I need update salaries based on this query results.
ID- is primary key for my table
Now I,m doing it like this:
STEP 1
select id from mytable ...... where something
STEP 2
update mytable set salary=1000 where id in (select id from mytable ...... where something)
Is there exists alternative to do that easily?
Try for update and current of. You said that you are looking for something like "updating data on grid"
create table my_table( id number, a varchar2(10), b varchar2(10));
insert into my_table select level, 'a', 'b' from dual connect by level <=10;
select * from my_table;
declare
rec my_table%rowtype;
cursor c_cursor is select * from my_table for update;
begin
open c_cursor;
loop
fetch c_cursor into rec;
exit when c_cursor%notfound;
if rec.id in (1,3,5) then
rec.a := rec.a||'x';
rec.b := rec.b||'+';
update my_table set row = rec where current of c_cursor;
else
delete from my_table where current of c_cursor;
end if;
end loop;
commit;
end;
select * from my_table;
Yes , you can directly update the result easily.
Here is example :
update
(
select salary from mytable ...... where something
) set salary=1000

Trigger avoiding mutating table and updating :new.values

I have a small table looking like this
table People
( name VARCHAR(20) PRIMARY KEY
,group NUMBER(4)
);
And i need to create trigger (or triggers) thats will allow below rules to work:
- 1 if there are more then 10 names in group i need to raise an error if someone tries to INSERT next people for this group.
- 2 if INSERT comes with NULL value for group field i need to assign it to group which count is less then 10.
- 3 if there are 10 names in all groups i need to generate next group number.
- 4 I need to avoid mutating table error.
This is what i've done till now :
CREATE OR REPLACE TRIGGER people_bis
BEFORE INSERT ON people
FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
g_count NUMBER(4);
g_num NUMBER(4);
g_l NUMBER(4);
g_r NUMBER(4);
BEGIN
Select count(*) into g_count from people where group = :new.group;
If g_count > 9 Then
raise_application_error (-20003,'Group reached it limit, please choose other');
End if;
If :NEW.group = '' or :NEW.group is null Then
select count (*) into g_l from (select count(imie),group from people group by group having count(name) = 10);
select count (distinct group) into g_r from people;
if g_l = g_r then
select max(group)+1 into g_num from people;
else
select group into g_num from(select group, count(name) from people having count(name) < 10 group by group order by count(group) desc) where rownum < 2;
End if;
:New.group := g_num;
End if;
End people_bis;
Code above works, but when i INSERT as select from a mirror table e.g.
INSERT INTO people(name) select concat(name,'_next') from people_mirror;
The result is that it exceed the given limit (10) for a group.
Also i know that using PRAGMA AUTONOMOUS_TRANSACTION is not a best way to avoid mutating table error, and i know i could reach this functionality if i would split row trigger into statement triggers but i'm just out off idea how to get it.
So anyone? ;)
Thanks in advance.
-------------------EDIT------------------------
Those are the triggers that works but still i have doubts about them as both of them are BEFORE and row type.
CREATE OR REPLACE TRIGGER people_bir1
BEFORE INSERT on people
FOR EACH ROW
DECLARE
V_count NUMBER(2);
BEGIN
If :NEW.group = '' or :NEW.group is null then
return;
end if;
insert into groups values(:New.group,1);
exception when dup_val_on_index then
Select count into v_count from groups where group = :New.group;
UPDATE groups set count = v_count+1 where group = :New.group;
END people_bir1;
CREATE OR REPLACE TRIGGER people_bir2
BEFORE INSERT on people
FOR EACH ROW
DECLARE
g_count NUMBER(2);
g_num NUMBER(2);
begin
if :NEW.group = '' or :NEW.group is null Then
select min(count) into g_count from groups;
if g_count = 10 Then
select max(group) into g_num from groups;
g_num := g_num+1;
Else
select min(group) into g_num from group where count = g_count;
End if;
:New.group := g_num;
Else
select count into g_count from groups where group=:New.group;
if g_count > 9 then
raise_application_error (-20003,'More then 10 people in group please select another');
end if;
end if;
end people_bir2;
as it is too long i couldn't paste it as a comment to #TonyAndrews answer.
You are right that adding PRAGMA AUTONOMOUS_TRANSACTION is no solution. One way to do this is to maintain a count of people per group in the GROUPS table (if you don't have a GROUPS table then you could add one) using triggers on PEOPLE:
After INSERT on PEOPLE: update GROUPS, add 1 to count of group they are in
After DELETE on PEOPLE: update GROUPS, subtract 1 from count of group they are in
After UPDATE on PEOPLE: update GROUPS, add 1 to new group, subtract 1 from old group
Then your BEFORE INSERT trigger doesn't need to look at the PEOPLE table, it can look at GROUPS:
Select people_count into g_count from groups where group = :new.group
for update;
Note the for update clause to lock the GROUPS row until your transaction completes.
You can use a compund trigger. It looks like this:
CREATE OR REPLACE TRIGGER people_bis
FOR INSERT ON people
COMPOUND TRIGGER
g_count NUMBER(4);
g_num NUMBER(4);
g_l NUMBER(4);
g_r NUMBER(4);
BEFORE STATEMENT IS
BEGIN
Select count(*) into g_count from people where group = :new.group;
If g_count > 9 Then
raise_application_error (-20003,'Group reached it limit, please choose other');
End if;
select count (*) into g_l from (select count(imie),group from people group by group having count(name) = 10);
select count (distinct group) into g_r from people;
if g_l = g_r then
select max(group)+1 into g_num from people;
else
select group into g_num from(select group, count(name) from people having count(name) < 10 group by group order by count(group) desc) where rownum < 2;
End if;
END BEFORE STATEMENT;
BEFORE EACH ROW IS
BEGIN
If :NEW.group = '' or :NEW.group is null Then
:New.group := g_num;
End if;
END BEFORE EACH ROW;
End people_bis;
Please note, most probably this code does not work as you wanted but it should give you a general impression how to work with a Compound Trigger.

INSERT and UPDATE a record using cursors in oracle

I have 2 tables- student and studLoad both having 2 fields studID and studName. I want to load data from student table into stuLoad table.
If the data already exists in the studLoad table, then it should be updated else it should be inserted. following is my code to do so:
create or replace procedure studentLoad is
v_id student.studID%type;
v_name student.studName%type;
v_sn studLoad.studName%type;
cursor cur_load is
select * from student;
begin
open cur_load;
loop
fetch cur_load into v_id,v_name;
exit when cur_load%notfound;
select studName into v_sn from studLoad where studID = v_id;
if(v_sn!= v_name) then
update studLoad set studName= v_name where studID= v_id;
else
insert into studLoad values(v_id,v_name);
dbms_output.put_line(v_id || ' ' || v_name);
end if;
end loop;
close cur_load;
end;
It's not working. the rows in studLoad table are noT updated. How do I solve this? In SQL server we use IF EXISTS(select...from stuLoad..) to check if the record exists in the table, is there a way to do the same in Oracle? if yes then please let me know the same.
This is a highly inefficient way of doing it. You can use the merge statement and then there's no need for cursors, looping or (if you can do without) PL/SQL.
MERGE INTO studLoad l
USING ( SELECT studId, studName FROM student ) s
ON (l.studId = s.studId)
WHEN MATCHED THEN
UPDATE SET l.studName = s.studName
WHERE l.studName != s.studName
WHEN NOT MATCHED THEN
INSERT (l.studID, l.studName)
VALUES (s.studId, s.studName)
Make sure you commit, once completed, in order to be able to see this in the database.
To actually answer your question I would do it something like as follows. This has the benefit of doing most of the work in SQL and only updating based on the rowid, a unique address in the table.
It declares a type, which you place the data within in bulk, 10,000 rows at a time. Then processes these rows individually.
However, as I say this will not be as efficient as merge.
declare
cursor c_data is
select b.rowid as rid, a.studId, a.studName
from student a
left outer join studLoad b
on a.studId = b.studId
and a.studName <> b.studName
;
type t__data is table of c_data%rowtype index by binary_integer;
t_data t__data;
begin
open c_data;
loop
fetch c_data bulk collect into t_data limit 10000;
exit when t_data.count = 0;
for idx in t_data.first .. t_data.last loop
if t_data(idx).rid is null then
insert into studLoad (studId, studName)
values (t_data(idx).studId, t_data(idx).studName);
else
update studLoad
set studName = t_data(idx).studName
where rowid = t_data(idx).rid
;
end if;
end loop;
end loop;
close c_data;
end;
/
If you would like to use your procedure, consider to change some lines:
create or replace procedure studentLoad is
v_id student.studID%type;
v_name student.studName%type;
v_sn studLoad.studName%type;
cursor cur_load is
select * from student;
begin
open cur_load;
loop
fetch cur_load into v_id,v_name;
exit when cur_load%notfound;
begin
select studName into v_sn from studLoad where studID = v_id;
if(v_sn!= v_name) then
update studLoad set studName= v_name where studID= v_id;
end if;
exception
when no_data_found then
insert into studLoad values(v_id,v_name);
end;
dbms_output.put_line(v_id || ' ' || v_name);
end loop;
close cur_load;
end;
I think it should work, didn't test it.