PL SQL loop through list of ids - sql

I have a list of names.
john, sam, peter, jack
I want to query the same sql with each of above as the filter. Each query will give me a unique employee id, which I want to use to delete some other records.
select emp_id from employee where emp_name like '%john%';
Let's say for the first query, I get the id as 1001. So the delete queries would be like following.
delete from account_details where emp_id = 1001;
delete from hr_details where emp_id = 1001;
delete from pay_role_details where emp_id = 1001;
I have to repeat this for a list of employees. Pseudocode would be like following.
var emp_list = ['john', 'jack', 'kate', 'peter', 'sam',...]
for each :employee_name in emp_list
select emp_id as :var_emp_id from employee where emp_name like '%:employee_name%';
delete from account_details where emp_id = :var_emp_id;
delete from hr_details where emp_id = :var_emp_id;
delete from pay_role_details where emp_id = :var_emp_id;
end loop
I want a PL-SQL query to do this. Please help. Thanks.
What I tried is something like the following.
set serveroutput on;
begin
loop x in ('john','jack', 'kate') loop as :name
select emp_id as var_emp_id from employee where emp_name like '%:name%';
// delete queries
end loop;
end;
P.S. Although accoring to the question, like query may result in multiple records, in actual scenario, it is guaranteed to be only one record. Why I use like is that in actual scenario, it is a list of reference numbers instead of names. The reference number has some other pre texts and post texts and my comma seperated list has only the numbers.

Perhaps the following will help:
BEGIN
FOR aName IN (SELECT 'john' AS EMP_NAME FROM DUAL
UNION ALL
SELECT 'sam' AS EMP_NAME FROM DUAL
UNION ALL
SELECT 'peter' AS EMP_NAME FROM DUAL
UNION ALL
SELECT 'jack' AS EMP_NAME FROM DUAL)
LOOP
FOR emp IN (SELECT * FROM EMPLOYEE WHERE EMP_NAME LIKE '%' || aName.EMP_NAME || '%')
LOOP
DELETE FROM ACCOUNT_DETAILS a WHERE a.EMP_ID = emp.EMP_ID;
DELETE FROM HR_DETAILS h WHERE h.EMP_ID = emp.EMP_ID;
DELETE FROM PAY_ROLE_DETAILS p WHERE p.EMP_ID = emp.EMP_ID;
DBMS_OUTPUT.PUT_LINE('Deleted data for employee with EMP_ID=' || emp.EMP_ID);
END LOOP; -- emp
END LOOP; -- aName
END;
Study this until you understand how and why it works.
Share and enjoy.

Do you really need a cursor to do so? Try to skip cursor if possible to avoid poor performance/memory usage on huge data.
delete from account_details inner join employee on account_details.emp_id = employee.emp_id where WHERE CONTAINS(employee.emp_name, '"John" OR "Sam" OR "Max"', 1) >0;
delete from hr_details inner join employee on hr_details.emp_id = employee.emp_id where WHERE CONTAINS(employee.emp_name, '"John" OR "Sam" OR "Max"', 1) >0;
delete from pay_role_details inner join employee on pay_role_details.emp_id = employee.emp_id where WHERE CONTAINS(employee.emp_name, '"John" OR "Sam" OR "Max"', 1) >0;

Use a PL/SQL cursor to select all the IDs you want to delete and then just loop it and issue the DELETE statements with every pass.
In-depth info on cursors can be found here: http://www.oracle.com/technetwork/issue-archive/2013/13-mar/o23plsql-1906474.html
For dynamic SQL see here: http://docs.oracle.com/cd/E11882_01/appdev.112/e25519/dynamic.htm#LNPLS627
Code example:
PROCEDURE delete_stuff
IS
id AS NUMBER;
CURSOR your_cursor IS
SELECT emp_id FROM employee WHERE CONTAINS(employee.emp_name, '"John" OR "Sam" OR "Max"', 1) > 0;
OPEN your_cursor;
LOOP
FETCH your_cursor INTO id;
EXIT WHEN your_cursor%NOTFOUND;
EXECUTE IMMEDIATE 'DELETE FROM account_details WHERE emp_id = :id' USING id;
EXECUTE IMMEDIATE 'DELETE FROM hr_details WHERE emp_id = :id' USING id;
EXECUTE IMMEDIATE 'DELETE FROM pay_role_details WHERE emp_id = :id' USING id;
CLOSE your_cursor;
END LOOP;
EXCEPTION
WHEN OTHERS THEN NULL;
END delete_stuff;

Related

How to manipulate VARRAYS in sql (oracle)?

Supposing i am using a table person, and persons might have multiple last names, so that attribute should be a varray of 3 elements for example (it's not about where to store last names), here is a simple sql for creating the type last name, the table person and adding an example row in oracle's sql developper (11G XE):
create type lastn as varray(3) of varchar2(10);
CREATE TABLE person
(
ID NUMBER NOT NULL
, last_name lastn
, CONSTRAINT EXEMPLE_PK PRIMARY KEY
(
ID
)
ENABLE
);
insert into person values(1,lastn('dani','bilel'));
I know how to update all last names at once, but i need to preserve existing last names and add other last names, or remove a single last name without affecting the others. In a nutshell, i want my code to be like (i am not familiar with PL/SQL):
insert into table
(select last_name from example where id=1)
values lastn('new');
This is the case where i want to get persons that have a first last name of 'bilel' and second last_name as 'dani'
select * from person where id in (select id from pernom p,table(p.last_name)
where column_value(1)='bilel' and column_value(2)='dani');
I know that it doesn't work like that, but i want to know CRUD(create update delete) statements in that case. and select statement with varray in where statement.
Thanks for your response.
From the docs:
Oracle does not support piecewise updates on VARRAY columns. However, VARRAY columns can be inserted into or updated as an atomic unit.
As shown in the examples there, you can manipulate the collection through PL/SQL instead; incuding adding an element to the array:
declare
l_last_name lastn;
begin
select last_name into l_last_name
from person where id = 1;
l_last_name.extend();
l_last_name(l_last_name.count) := 'third';
update person
set last_name = l_last_name
where id = 1;
end;
/
PL/SQL procedure successfully completed.
select last_name from person where id = 1;
LAST_NAME
--------------------------------------------------
LASTN('dani', 'bilel', 'third')
You can also do this via cast(multiset(...) as ...):
-- rollback; to reverse PL/SQL block actions above
update person p
set last_name = cast(multiset(
select column_value
from table (last_name)
union all
select 'third' from dual
) as lastn)
where id = 1;
1 row updated.
select last_name from person where id = 1;
LAST_NAME
--------------------------------------------------
LASTN('dani', 'bilel', 'third')
That explodes the existing last_name value into multiple rows, union's in a new value, and then converts the combined result back into your varray type.
And you can delete or update elements in a similar way:
update person p
set last_name = cast(multiset(
select column_value
from table (last_name)
where column_value != 'bilel'
) as lastn)
where id = 1;
1 row updated.
select last_name from person where id = 1;
LAST_NAME
--------------------------------------------------
LASTN('dani', 'third')
update person p
set last_name = cast(multiset(
select case column_value when 'third' then 'second' else column_value end
from table (last_name)
) as lastn)
where id = 1;
1 row updated.
select last_name from person where id = 1;
LAST_NAME
--------------------------------------------------
LASTN('dani', 'second')
For the select statement, i've figured out the solution, which goes like this :
select * from person p where id in (select id from table(p.last_name) where
column_value='bilel' intersect select id from table(p.last_name) where
column_value='dani');
or
select * from agent ag where id in (select id from table(ag.prenom)
t1,table(ag.prenom) t2,table(ag.prenom) t3 where t1.column_value='bilel' and
t2.column_value='dani' and t3.column_value='third');

I want to replicate an existing record for a new employee ID

I want to replicate an existing employees record for a new employee.
EG.
If my present employee details are:
EmpID: 100
EmpDept: Accounts
EmpEndDate: 12-12-2018
So when I am adding a new employee I dont want to manually write an insert statement, all I want is a procedure that can replicate all the other values present for the new employee.
One approach is to increment the max EmpID by 1 so as to maintain uniqueness of rows - with a CONNECT BY LOOP, if you want multiple rows.
INSERT INTO Employee (
EmpID
,EmpDept
,EmpEndDate
)
WITH m(max_id) AS (
SELECT MAX(EmpID)
FROM Employee
)
SELECT max_id + LEVEL
,EmpDept
,EmpEndDate
FROM Employee
CROSS JOIN m
WHERE EmpID = p_emp_id CONNECT BY LEVEL <= p_numof_rows;
Put this query in a procedure and pass p_emp_id and p_numof_rows as parameter to replicate as many rows as you want. Hopefully you have one row currently in the table for each p_emp_id you would pass.
If you always want a single row, just use max_id + 1 and remove CONNECT BY LEVEL <= p_numof_rows
Demo
Assuming you are using a sequence named Employees__EmpID__Seq to generate the unique employee IDs then:
INSERT INTO Employees ( EmpID, EmpDept, EmpEndDate )
SELECT Employees__EmpID__Seq.NEXTVAL,
EmpDept,
EmpEndDate
FROM Employees
WHERE EmpID = :id_of_the_employee_you_want_to_copy;
You can then run that statement and it will copy an existing record.
all I want is a procedure that can replicate all the other values present for the new employee.
If you really need to wrap it in a procedure:
CREATE PROCEDURE copy_employee(
i_empid IN Employees.EmpID%TYPE,
o_empid OUT Employees.EmpID%TYPE
)
IS
BEGIN
o_empid := Employees__EmpID__Seq.NEXTVAL;
INSERT INTO Employees ( EmpID, EmpDept, EmpEndDate )
SELECT o_empid,
EmpDept,
EmpEndDate
FROM Employees
WHERE EmpID = i_empid;
IF SQL%ROWCOUNT <> 1 THEN
o_empid := NULL;
END IF;
END;
/
SQLFiddle
you can also do it in PL/SQL,
DECLARE
v_emp_rec employee%ROWTYPE;
BEGIN
SELECT *
INTO v_emp_rec
FROM employee
WHERE emp_id = 'emp_id_to_replicate';
v_emp_rec.emp_id := '101';
v_emp_rec.first_name := 'Alfie';
INSERT INTO employee
VALUES
v_emp_rec;
v_emp_rec.emp_id := '102';
v_emp_rec.first_name := 'Alf';
INSERT INTO employee
VALUES
v_emp_rec;
END;
/
COMMIT;

Create Oracle PL/SQL procedure that outputs distinct names inside a loop

I just started with PL/SQL, so, please be kind.
I have a simple problem, I want to extract distinct names from a table.
When I do that, inside the loop, I am going to do on each of those distinct names some other operations.
I am stuck on how to get the unique names inside a loop. What I do is not working because if I have:
1 MARY
2 MARY
3 JOHN
I am outputting:
MARY
MARY
Instead of:
MARY
JOHN
This is my code:
create or replace PROCEDURE CREATE_TABLE
(
NM OUT VARCHAR2,
tot OUT NUMBER
)
AS
BEGIN
SELECT count(DISTINCT NAME) INTO tot FROM MYTABLE;
FOR r IN 1..tot
LOOP
SELECT NAME INTO NM
FROM (
SELECT DISTINCT NAME,
ROWNUM rnum
FROM MYTABLE
ORDER BY NAME DESC
)
WHERE rnum = r;
dbms_output.put_line (NM);
END LOOP;
END;
I'd use an implicit cursor loop instead, they're very easy to work with.
FOR r in (SELECT DISTINCT NAME
FROM MYTABLE
ORDER BY NAME DESC)
LOOP
NM := r.NAME;
dbms_output.put_line (NM);
END LOOP;

SQL query to return a particular column or a default value in case of row not existing in the db

I have a requirement of getting an employee id from department id (lets consider that one department will only contain 1 employee at most)
I can do something like below:
Select emp_id from emp where dept_id = 101;
Now in case this row was not existing, I won't be getting any value.
However since I am using this query in PL/SQL layer, it will throw a NO_DATA_FOUND exception (when no employee exists in db)
Can you refactor this query to return some emp_id = -1 in case no employee exists in db.
i can do something like below (which results in 2 sql queries, so not efficient)
select count(1) INTO temp_count from emp where dept_id = 101; -- or use an exists clause
if (temp_count != 0)
Select emp_id from emp where dept_id = 101;
Try:
select nvl(min(emp_id),-1)
from emp
where dept_id = 101;
Other than the provided answer ,there are multiple ways you can address this requirement.
You can use DECODE,COALESCE,NULLIF like below :
Select COALESCE(emp_id,<default value>) from emp where dept_id = 101;
Select DECODE(emp_id,NULL,<default value>,emp_id) from emp where dept_id = 101;
Go through this Link NULL-Related Functions for more examples
You can use this code block for your problem :
select t.emp_id
from (select nvl(e.emp_id
,-1) as emp_id
from emp e
where e.dept_id = 101) t
where t.emp_id <> -1
You can try this code block :
select case
when e.emp_id is null then
-1
else
e.emp_id
end as emp_id
from emp e
where e.dept_id = 101
If you're using PL/SQL, presumably a function, why not just handle the no_data_found error, rather than faffing around with the sql statement and possibly making it less efficient?
E.g. something like:
create or replace function get_emp_id (p_dept_id number)
return number
is
v_emp_id number;
begin
select emp_id
into v_emp_id
from emp
where dept_id = p_dept_id;
return v_emp_id;
exception
when no_data_found then
return -1;
end get_emp_id;
/
You can use below code
Select nvl(emp_id,default_value) from emp where dept_id = 101;
Select DECODE(emp_id,NULL,default_value,emp_id) from emp where dept_id = 101;

PL/SQL Oracle Query With IF Statement

I want to implement a query that only returns the logged in user and displays there record only, which I have done as follows and it works:
SELECT * FROM EMPLOYEE
WHERE UPPER(username) = v('APP_USER')
However, I have another column called User_Type, and a user can be type 1, 2 or 3. If I have a user type of 1, I want the query to also return all the tables records too as user type 1 is an admin.
I thought about doing it like this:
BEGIN
SELECT * FROM Employee
WHERE upper(username) = v('APP_USER')
IF User_Type = 1
THEN SELECT * FROM Employee
END IF;
END;
/
But it doesn't work in APEX Oracle PLSQL.
Any suggestions?
From what I understand you need to try this:
DECLARE
emp employee%ROWTYPE; -- Create a record type
tbl_emp IS TABLE OF emp;
-- ^^^ Create a table of that record type
v_user_type employee.user_type%TYPE;
-- ^^^ Variable to store user type
BEGIN
SELECT user_type
INTO v_user_type
FROM Employee
WHERE upper(username) = v('APP_USER');
IF v_user_type = 1 THEN
SELECT *
BULK COLLECT INTO tbl_emp
FROM employee;
-- ^^ Returns the entire table
ELSE
SELECT *
BULK COLLECT INTO tbl_emp
FROM employee;
WHERE upper(username) = v('APP_USER');
-- ^^ Returns the row related to the user.
END IF;
END;
/
The output is stored in the nested table variable tbl_emp.
EDIT:
It can be achieved using pure SQL also, like this:
SELECT *
FROM employee e
WHERE EXISTS (SELECT 1
FROM employees e_in
WHERE e_in.user_type = 1
AND UPPER(e_in.username) = v('APP_USER'))
OR UPPER(e.username) = v('APP_USER')
Choose whichever is best suited for you.
You want all records from users with either UPPER(username) being v('APP_USER') or User_Type being 1? Then just use OR:
SELECT * FROM Employee WHERE upper(username) = v('APP_USER') OR User_Type = 1
If that's not what you mean, then can you explain more clearly?
Try:
select distinct e2.*
from employee e1
join employee e2 on (e1.username = e2.username or e1.User_Type = 1)
where UPPER(e1.username) = v('APP_USER')