Updating database records in a loop? - sql

declare
begin
for i in (select * from emp)
loop
if i.sal=1300 then
update emp
set sal=13000;
end if;
end loop;
end;
This code is updating all the records with salary 13000. Instead i want to update records having salary 1300 to the value 13000. Can you tell where I made a mistake?
I am accesing records using implicit cursor..
for every record i am checking the sal value of that record..
if salary value in a particular record is 1500 i want to update it to 15000..

delete that code and just use:
update emp set sal = 13000 where sal = 1300

Whenever you can do the update with one single statement, you should do that instead of using a loop. You'll get a very huge performance gain that way; or, the other way round, updates in a loop cost you a lot of performance.
If you really really have to use a loop, of course you need a where condition to make sure you are only updating the record you really want to update. A possible way that always works (even if there is no unique key available) is to use the rowid pseudocolumn:
begin
for i in (select rowid, emp.* from emp)
loop
if i.sal=1300 then
update emp
set sal=13000
where rowid=i.rowid;
end if;
end loop;
end;
Another possibility would be using an explicit cursor and the "update ... where current of cursorname" syntax.

You need to put a constraint on your update statement.
What you have at the moment will loop through the results rows, and if it finds a row with salary equal to 1300, if then executest he following SQL:
update emp
set sal=13000;
Without the contraint this updates every row.

This code is updating all the records with salary 13000.
Instead i want to update records having salary 1300 to the value 13000.
for every record i am checking the sal value of that record..
if salary value in a particular record is 1500 i want to update it to 15000..
So what exactly do you want?
You want to update only 1,500 salary, you issue:
UPDATE emp
SET sal = 15000
WHERE sal = 1500;
You want to increase all salary ten times, you issue:
UPDATE emp
SET sal = sal * 10;

While some of these solutions are workable its not a one size fits all solution. I ran into a scenario where we had to set an xml/nvarchar(max) field to null on a table that has +50 M records. This is an excerpt of the code
begin
declare #rows int,
#y int = 2020,
#m int = 1;
set #rows = 1;
while (#rows > 0)
begin
update top (500) cr
set [xml] = null
from [dbo].[customer] cr with(index(ix_customer_reportdt))
where year([reportdt]) = #y
and month([reportdt]) = #m
and [xml] is not null;
set #rows = ##rowcount;
end
end

here's a quick solution that helps in removing the spaces (trimming) of a column data based on created date:
UPDATE table_Name SET column_name = LTRIM(RTRIM(column_name))
WHERE EXTEND(dateTime_column, YEAR TO DAY)='2020-01-31' ;

Related

Bulk Update for multiple columns based on columns from other table

I am trying to do a bulk update in Oracle. The operation involves updating millions of records in target table based on column values from another table.The scenario goes like this.
I have 2 tables:
T1 (source)
t1_col1 t1_col2 t1_col3 t1_col4 t1_col5
T2 (target)
t2_col1 t2_col2 t2_col3 t2_col4 t2_col5 t2_col6
I need to do an update like this:
update t1
set t2_col1 = t1_col1,
t2_col2 = t1_col2,
t2_col3 = sysdate
where t2_col4 = t1_col4
and t2_col5 = t1_col5
and t2_col6 = null
How can I achieve the above update for multiple columns comprising millions of records. Going through the forum, i understand this should be done utilizing bulk collect, cursor, for all, limit etc. I am unable to come up with a query based on this though. Appreciate your help.
The update of millions of columns is always slow. No matter if you do this using UPDATE or BULK COLLECT. The fastest method is to CREATE AS SELECT.
Example, HR scheme:
Creating a target table, EMP, for example as a copy of employees table:
create table emp as select * from employees; update emp set salary = salary * 2;
Create the THIRD temporary table, with new values your update, here I change SALARY column
create table emp_new as SELECT
emp.employee_id,
emp.first_name,
emp.last_name,
emp.email,
emp.phone_number,
emp.hire_date,
emp.job_id,
e.salary,
emp.commission_pct,
emp.manager_id,
emp.department_id FROM
employees e, emp WHERE emp.employee_id = e.employee_id;
Drop the original target table:
create table emp_arch as select * from emp; -- you can want to archive data drop table emp;
Rename the third table to the target table_name
rename emp_new to emp;
That's all, it should be the fastest way. You have to remember that you should add constraints and indexes the to new table.
I did once comparison of how to load data using different methods so if you interested you can check this. At the and there are scripts.
https://how2ora-en.blogspot.com/2020/03/loading-data-sql-vs-forall-perf-tests.html
If you want to use bulk collect it could look like this:
declare
type tabEmp is table of employees%rowtype;
tEmp tabEmp;
bulk_errors exception;
pragma exception_init(bulk_errors, -24381);
nErrCnt number;
n_errcode number;
v_msg varchar2(4000);
n_idx number;
cursor cur is select * from employees;
begin
open cur;
loop
fetch cur bulk collect into tEmp;
exit when tEmp.count =0;
BEGIN
forall i in 1..tEmp.count
update emp set salary = tEmp(i).salary;
exception when bulk_errors then
nErrCnt:= sql%bulk_exceptions.count;
for i in 1 .. nErrCnt
loop
n_errcode := sql%bulk_exceptions(i).error_code;
v_msg := sqlerrm(-n_errcode);
n_idx := sql%bulk_exceptions(i).error_index;
dbms_output.put_line(n_errcode);
end loop;
end;
end loop;
close cur;
end;

there is few mistake in trigger help me to solve that problem

Create trigger that will
not allow update of salary for employee
who are in sales department.
Emp(no,name,dno,salary,designamtion)
Dept(dno,name,location)
Errors: TRIGGER MYTRIGGER1
Line/Col: 6/11 PLS-00049: bad bind variable 'OLD.EMP'
Line/Col: 6/31 PLS-00049: bad bind variable 'OLD.EMP'
DROP TABLE EMP;
DROP TABLE DEPT;
CREATE TABLE Dept
(
dno NUMBER PRIMARY KEY,
name VARCHAR(15),
location VARCHAR(15) );
CREATE TABLE Emp
(
no NUMBER PRIMARY KEY,
name VARCHAR(15),
dno NUMBER,
salary NUMBER,
designamtion VARCHAR(15),
FOREIGN KEY(dno) REFERENCES Dept(dno) );
insert into DEPT values (1,'SALES','GUJARAT');
insert into DEPT values (2,'MARKETING','UP');
insert into DEPT values (3,'MANUFACTURING','MP');
insert into DEPT values (4,'DEALING','VAPI');
insert into DEPT values (5,'SELL','TAPI');
insert into EMP values (1,'AMAN',2,45400,'MANAGER');
insert into EMP values (2,'BHAMAN',5,20000,'GM');
insert into EMP values (3,'CHAMAN',3,34400,'ADVISOR');
insert into EMP values (4,'DAMAN',4,75400,'WORKER');
insert into EMP values (5,'KHAMAN',1,42400,'MANAGER');
CREATE OR REPLACE trigger MYTRIGGER1
BEFORE UPDATE OF SALARY ON EMP
for each row
declare
hmmm VARCHAR(15);
begin
select Dept.name into hmmm
from Dept, Emp
where :old.emp.no=no and :old.emp.dno=Dept.dno;
dbms_output.put_line(hmmm);
end;
You don't need to alias the table the trigger is on. This will compile fine:
CREATE OR REPLACE trigger MYTRIGGER1
BEFORE UPDATE OF SALARY ON EMP
for each row
declare
hmmm VARCHAR(15);
begin
select Dept.name into hmmm
from Dept, Emp
where :old.no=no and :old.dno=Dept.dno;
dbms_output.put_line(hmmm);
end;
However, this trigger will fail when you update the table. You cannot select from the table itself in the trigger body. This will raise a mutating table error. Since it is an assignment I'll leave the research up to you.
As Koen said, your code will suffer from mutating table error.
Lucky you - you don't have to select from the table you're just updating (the emp table, right?) - you have everything you need in :new.dno. So:
SQL> create or replace trigger mytrigger1
2 before update of salary on emp
3 for each row
4 declare
5 hmmm varchar2(15);
6 begin
7 select d.name
8 into hmmm
9 from dept d
10 where d.dno = :new.dno;
11
12 dbms_output.put_line('Department name = ' || hmmm);
13 end;
14 /
Trigger created.
Testing:
SQL> set serveroutput on
SQL>
SQL> update emp set salary = 1000 where no = 1;
Department name = MARKETING
1 row updated.
SQL> update emp set salary = 1000;
Department name = MARKETING
Department name = SELL
Department name = MANUFACTURING
Department name = DEALING
Department name = SALES
5 rows updated.
SQL>
Your request is a trigger that disallows certain action for employees in Sales. Your trigger would not do so even it compiled. Two problems:
dbms_output does not prevent anything it merely 'prints' a message.
it prevents any update to salary, except when emp.dno or emp.no is also updated.
First (getting up on soapbox). You are enforcing a business rule. This should not be done in a trigger. It should be done in either the business rules engine of your application or in a database constraint or if you want to be super cautions both. Now a trigger will do the enforcement but business rule violations should glaringly obvious and easy to find, in a trigger becomes a side effect of an action, hidden away and difficult to find. (OK enough of that getting off soapbox).
The prevention method your looking for is raise_application_error. And you want a little code as possible in a trigger. In this case you have everything needed from the EMP table through the pesudo rows :old and :new so there is no reason to join your tables. However you do need to look at the specific department. It is too bad Oracle constrains the Exists predicate to introduction of a sub-select, this would be a perfect place for the structure:
If EXISTS (selects ...) then ...
But that is not available. The following uses a technique of reversing the requirement. That is it selects what is NOT wanted then if found it raise the exception, and if not found it suppress the Oracle raised error. So:
create or replace trigger mytrigger1
before update of salary on emp
for each row
declare
hmmm varchar2(01);
begin
select null
into hmmm
from dept d
where d.dno = :old.dno
and d.name = 'SALES';
raise_application_error(-20199, 'Can not update salary for Employee in Sales.');
exception
when no_data_found then null;
end mytrigger1;
This is sometimes referred to as "Programming the Exception" and is not a generally recommended procedure but at times it is useful. See fiddle here.
There is an outstanding question not addressed here. Can an employee in Sales have a salary change if they are also transferring departments. I.e. should the following succeed or fail?
update emp e
set dno = 2
, salary = salary + 5000
where e.no = 5;

PLSQL block to increase salary of departments

I have to Increase the Salary of employees working in deptno 10 by 15%, Deptno 20 by 15% and others by 5% and display the corresponding the employees working in that Dept. I am able to increase the salary of departmnet 10 and 20 but I am not able to increase the salary of other departments by 5%. I have tried with for loop too.This is a practice question.
My question is this:(Display the all records from the Dept table. Increase the Salary of employees working in deptno 10 by 15%, Deptno 20 by 15% and others by 5% Also display the corresponding the employees working in that Dept. Use a parameter Cursor and Cursor with Update clause.)
MY code:
declare
cursor sal_increase(v_dno number) is select empno,sal,ename,deptno from emp where deptno=v_dno ;
emp_record sal_increase%rowtype;
begin
OPEN sal_increase(10);
LOOP
FETCH sal_increase INTO emp_record;
EXIT WHEN sal_increase%NOTFOUND;
update emp set sal=sal+sal*0.15 where empno=emp_record.empno;
DBMS_OUTPUT.PUT_LINE(emp_record.ename||' '||emp_record.deptno);
END LOOP;
CLOSE sal_increase;
OPEN sal_increase(20);
LOOP
FETCH sal_increase INTO emp_record;
EXIT WHEN sal_increase%NOTFOUND;
update emp set sal=sal+sal*0.15 where empno=emp_record.empno;
DBMS_OUTPUT.PUT_LINE(emp_record.ename||' '||emp_record.deptno);
END LOOP;
CLOSE sal_increase;
end;
You don't need any procedural code at all to do this. That would just unnecessarily and severely slow it down, especially the use of cursors.
Instead just use a simple UPDATE with a CASE expression returning the new salary depending on the department.
UPDATE emp
SET sal = CASE
WHEN deptno IN (10, 20) THEN
sal * 0.15
ELSE
sal * 0.05
END;
For the output just use a SELECT.
SELECT ename || ' ' || deptno
FROM emp;
Don't forget the solution from #sticky bit for future.
Going back to your requirement , I believe you stick to do it with one anonymous block and with parameterized cursor.(correct me if I am wrong)
if yes, I would change the following and it should do the work for you.
Change your cursor to below, For others you pass null as parameter and in the where clause we can handle the same. (when value pass as 10 or 20 it will do a comparison and when null it will update for all other than 10 and 20)
CURSOR sal_increase(v_dno NUMBER) IS
SELECT empno
,sal
,ename
,deptno
FROM emp
WHERE ( v_dno IS NOT NULL AND deptno = v_dno
OR v_dno IS NULL AND deptno NOT IN (10,20)
);
Then call again the cusrosr as you did for 10 and 20 once more like below,
OPEN sal_increase(v_dno => NULL);
LOOP
FETCH sal_increase
INTO emp_record;
EXIT WHEN sal_increase%NOTFOUND;
UPDATE emp SET sal = sal * 0.05 WHERE empno = emp_record.empno;
dbms_output.put_line(emp_record.ename || ' ' || emp_record.deptno);
END LOOP;
CLOSE sal_increase;

How to fetch the deleted records?

How to fetch the deleted records. I am using Oracle 10g.
Do you have a backup before the deletion? ...or you could try using flashback (SELECT AS OF)
http://docs.oracle.com/cd/E11882_01/appdev.112/e41502/adfns_flashback.htm#ADFNS01003
Hope I've understood your question correctly.
Do some research into the RETURNING clause: https://docs.oracle.com/cd/B19306_01/appdev.102/b14261/returninginto_clause.htm
https://docs.oracle.com/cd/B19306_01/appdev.102/b14261/collections.htm#BABHDGIG
The link has a fully-functional example with an UPDATE statement. I'm sure if you spent some time on it you can figure out how to make it work with a DELETE statement as well.
DECLARE
TYPE EmpRec IS RECORD (last_name employees.last_name%TYPE,
salary employees.salary%TYPE);
emp_info EmpRec;
emp_id NUMBER := 100;
BEGIN
UPDATE employees SET salary = salary * 1.1 WHERE employee_id = emp_id
RETURNING last_name, salary INTO emp_info;
DBMS_OUTPUT.PUT_LINE('Just gave a raise to ' || emp_info.last_name ||
', who now makes ' || emp_info.salary);
ROLLBACK;
END;
/

Validating whether an emp is a manager in oracle

I am trying to build a trigger that would not allow users to give a salary higher than 10,000 to employees but if the employee is a manager, I don't want the user to be able to give less than 15000 of a salary.
What I reached so far is the following
create or replace trigger sal_check
before update or insert on emp
for each row
begin
if :new.sal > 10000
then
update emp set sal = 10000;
end if;
end;
Now can I please take a hint of how to do a check whether the employee is a manager or not?
Note : I am using Oracle, and this is running on Scott schema, the one that actually comes with Oracle by default.
If emp table has mgr column and you set up foreign key constraint, it's enough just to check whether mgr field is not null. Also, update emp set sal = 10000; in your code will cause ORA-04091 (table is mutating), you need to change it to :new.sal := 1000. So you trigger will look like
...
if :new.mgr IS NULL THEN
if :new.sal > 10000
then
:new.sal := 10000;
end if;
ELSE
if :new.sal < 15000
then
:new.sal := 15000;
end if;
END IF;
I think you should create a table (not temporary) with all the managers.
Afterwords, it would be quite easy to detect if a employee is a manager or not and by making a simple select on that table you could see what should be the salary.
To determine what employees should be on the table, it would be the ones in the mgr column.