PL/SQL: How to delete records in a specific manner, for example if records of specific type X exist, delete all but one record - plsql

I'm trying to create a PL/SQL procedure where by I delete records that are grouped and selected by cursor but I only want one record remaining. I want to delete first by Xcomment, if there are multiple entries with id_number, activity_code, start_dt, activity_participation_code exist, then delete all but ONE entry with blank/null xcomment. If there are multiple entries with blank xcomment, then delete all but one with blank table_nmb. If multiple entries with blank table_nmb then delete highest sequence until only one is left. Essentially, I only want one record per all these fields. I'm having trouble thinking of how to do this so any help would be appreciated.
Here is my code so far:
Create Or Replace Function Y_Cleanup_Cursor
Return Sys_Refcursor
As
My_Cursor Sys_Refcursor;
Begin
Open My_Cursor For
Select Q.Id_Number, Q.Activity_Code, Q.Start_Dt, Q.Activity_Participation_Code, Q.Rec_Count, A.Xcomment, A.Table_Nmb, A.Xsequence
From (Select Id_Number, Activity_Code, Start_Dt, Activity_Participation_Code, Count(0) As Rec_Count
From Activity A
Group By Id_Number, Activity_Code, Start_Dt, Activity_Participation_Code
Having Count(0) > 1) Q,
Activity A
Where
Q.Id_Number = A.Id_Number And
Q.Activity_Code = A.Activity_Code And
Q.Start_Dt = A.Start_Dt And
Q.Activity_Participation_Code = A.Activity_Participation_Code;
Return My_Cursor;
End Y_Cleanup_Cursor;
Create Or Replace Procedure Help_Me_Please(Code In Varchar2)
Is
-- Declare Variables
-- I Stands For Internal Variable
L_Cursor Sys_Refcursor;
I_Id_Number Varchar2(10 Byte);
I_Xsequence Number (6);
I_Activity_Code Varchar2(05 Byte);
I_Start_Dt Varchar2(08 Byte);
I_Activity_Participation_Code Varchar2(02 Byte);
I_Table_Nmb Varchar2(15 Byte);
I_Xcomment Varchar2(255 Byte);
I_Rec_Count Number (6);
L_Counter Integer;
Begin
L_Cursor := Y_Cleanup_Cursor;
Loop
Fetch L_Cursor Into
I_Id_Number, I_Activity_Code, I_Start_Dt, I_Activity_Participation_Code, I_Rec_Count, I_Xcomment, I_Table_Nmb, I_Xsequence;
Select Count (Id_Number)
Into L_Counter
From Activity Where
Id_Number = I_Id_Number
And Activity_Code = I_Activity_Code
And Start_Dt = I_Start_Dt
And Activity_Participation_Code = I_Activity_Participation_Code
And Trim(Xcomment) Is Null;
If L_Counter <> I_Rec_Count Then
Begin
Delete From Activity
Where
Id_Number = I_Id_Number
And Activity_Code = I_Activity_Code
And Start_Dt = I_Start_Dt
And Activity_Participation_Code = I_Activity_Participation_Code
And Trim(Xcomment) Is Null;
end;
End If;
Exit When L_Cursor%Notfound;
End Loop;
Close L_Cursor;
End Help_Me_Please;

From what I gather you want to delete all rows except 1 where there are repeating columns
first make sure to backup your table:
create table [backup_table] as select * from [table];
Try This:
DELETE FROM backup_table
WHERE rowid not in
(SELECT MIN(rowid)
FROM backup_table
GROUP BY [col1], [col2]);
Col1 and col2, etc are the columns that should be identical

Related

PL/SQL: I get expression 'I' cannot be used as an assignment target

My code:
create table info(str varchar2(30));
declare
cursor c(job emp_ast.job_id%type, dep emp_ast.department_id%type) is select employee_id
from emp_ast
where job_id=job and department_id=dep;
type t_job is table of emp_ast.job_id%type;
t t_job:=t_job();
emp emp_ast.employee_id%type;
i number(3);
begin
select job_id
bulk collect into t
from emp_ast;
for i in 10..270 loop
for j in 1..t.count loop
open c(i, t(j));
loop
fetch c into emp;
insert into info
values (i||' '||t(j)||' '||emp);
exit when c%notfound;
end loop;
i:=i+10;
end loop;
end loop;
end;
/
I get "expression 'I' cannot be used as an assignment target", reffering to the line where I increment i by 10. I am trying to save the department_id, employee_id and job_id as a string in a table for each department and each job.
At the point where you get that message, i refers to the loop control variable i defined in the line for i in 10..270 loop, not the int(3) variable defined earlier. In PL/SQL a loop definition defines a variable which is only accessible inside the loop, and which you cannot alter. I suggest you change the name of one or the other to make them unique.
EDIT
PL/SQL doesn't provide a way to step by more than 1 in a computed FOR loop. Instead, you will need to compute the desired department number value within the loop:
DECLARE
CURSOR c(job EMP_AST.JOB_ID%TYPE,
dep EMP_AST.DEPARTMENT_ID%TYPE)
IS SELECT EMPLOYEE_ID
FROM EMP_AST
WHERE JOB_ID = job AND
DEPARTMENT_ID = dep;
TYPE t_job IS TABLE OF EMP_AST.JOB_ID%TYPE;
t t_job := t_job();
emp EMP_AST.EMPLOYEE_ID%TYPE;
nDepartment NUMBER;
BEGIN
SELECT job_id
BULK COLLECT INTO t
FROM EMP_AST;
FOR i IN 1..27 LOOP
nDepartment := i * 10;
FOR j IN 1..t.COUNT LOOP
OPEN c(t(j), nDepartment);
LOOP
FETCH c INTO emp;
INSERT INTO info
VALUES (nDepartment || ' ' || t(j) || ' ' || emp);
EXIT WHEN c%notfound;
END LOOP; -- cursor c
CLOSE c;
END LOOP; -- j
END LOOP; -- i
END;
/
Note that in the code above the nDepartment value is computed within the i loop, which now increments from 1 to 27 instead of going from 10 to 270.

Return deleted rows in a cursor in PL/SQL

I have a requirement where I want to return the deleted records in a sys_refcursor. I can retrieve the data in cursor before the delete statement but is there any way to retrieve them after a delete statement? I mean process will be like at first delete and then open sys_refcursor for fetching deleted records.
There are probably a few options, but one option would be to use RETURNING and BULK COLLECT. Here is a simple example.
CREATE TABLE t (
a NUMBER,
b VARCHAR2(10),
c DATE
);
INSERT INTO t VALUES (1, 'a', SYSDATE);
INSERT INTO t VALUES (2, 'b', SYSDATE);
INSERT INTO t VALUES (3, 'c', SYSDATE);
INSERT INTO t VALUES (4, 'd', SYSDATE);
INSERT INTO t VALUES (5, 'e', SYSDATE);
CREATE OR REPLACE TYPE tt AS OBJECT (
a NUMBER,
b VARCHAR2(10),
c DATE
);
CREATE OR REPLACE TYPE tt_tab AS TABLE OF tt;
DECLARE
v_tt_tab tt_tab;
v_tt tt;
v_cur SYS_REFCURSOR;
BEGIN
DELETE FROM t
WHERE a < 4
RETURNING tt(a, b, c) BULK COLLECT INTO v_tt_tab;
OPEN v_cur FOR
SELECT tt(a,
b,
c)
FROM TABLE(v_tt_tab);
LOOP
FETCH v_cur
INTO v_tt;
EXIT WHEN v_cur%NOTFOUND;
dbms_output.put_line(v_tt.a || ' ' || v_tt.b || ' ' || v_tt.c);
END LOOP;
CLOSE v_cur;
END;
/
/*
1 a 07-OCT-20
2 b 07-OCT-20
3 c 07-OCT-20
*/
By creating an object that matches the data we want to keep and a table of that object we can return that into a collection and then easily turn it back into a cursor using the TABLE function.
You need to be careful with how many rows you delete as you don't want to run out of memory. I would be cautious with this approach if you delete more than a few hundred rows.
Another suggestion might be to use a GTT, INSERT the rows you plan to delete and then delete from the first table using the GTT as your "key".
CREATE GLOBAL TEMPORARY TABLE t_gtt (
a NUMBER,
b VARCHAR2(10),
c DATE
);
DECLARE
v_tt_tab tt_tab;
v_tt tt;
v_cur SYS_REFCURSOR;
BEGIN
INSERT INTO t_gtt
SELECT *
FROM t
WHERE a < 4;
DELETE FROM t
WHERE EXISTS (SELECT NULL
FROM t_gtt
WHERE t_gtt.a = t.a);
OPEN v_cur FOR
SELECT tt(a,
b,
c)
FROM t_gtt;
LOOP
FETCH v_cur
INTO v_tt;
EXIT WHEN v_cur%NOTFOUND;
dbms_output.put_line(v_tt.a || ' ' || v_tt.b || ' ' || v_tt.c);
END LOOP;
CLOSE v_cur;
END;
/
This option is maybe better if you are planning to delete a large amount of rows. I used my tt object again, but you really don't need it. It just made looping to dump the contents of the SYS_REFCURSOR easier.

PLSQL STORED PROCEDURE does not give result

select count(*)
INTO countExceed
from uid_emp_master k
where k.unique_id in (select k.reviewer_uid
from uid_rm_hierarchy k
where k.unique_id in ('||p_ID_list||'))
and k.band IN( 'A','B','C','D');
if (countExceed > 0) then
quer :='UPDATE UID_RM_HIERARCHY I
SET I.REVIEWER_UID in (SELECT L.REVIEWER_UID
FROM UID_RM_HIERARCHY L
WHERE L.UNIQUE_ID in ('||p_ID_list||') )
WHERE I.REVIEWER_UID in('||p_ID_list||')
and isdeleted=0';
EXECUTE IMMEDIATE quer ;
END IF;
the above stored procedure does not show any result the variable countExceed declared as a number please help me to correct the query.
The issue is in
where k.unique_id in ('||p_ID_list||'))
Here you are saying to look for records
where unique_id = '||p_ID_list||'
exactly as its typed, but what you need is to handle that variable as a list of values.
Say you have a table like this
create table tabTest(id) as (
select 'id1' from dual union all
select 'id2' from dual union all
select 'id3' from dual union all
select 'id4' from dual
)
and your input string is 'id1,id3,1d8';
I see two ways to do what you need; one is with dynamic SQL, for example:
declare
vResult number;
vList varchar2(199) := 'id1,id3,1d8';
vSQL varchar2(100);
begin
vSQL :=
'select count(*)
from tabTest
where id in (''' || replace (vList, ',', ''', ''') || ''')';
--
execute immediate vSQL into vResult;
--
dbms_output.put_line('Result: ' || vResult);
end;
Another way could be by splitting the string into a list of values and then simply using the resulting list in the IN.
For that, there are many answers about how to split a comma separated list string in Oracle.

Inserting new id from sequence into table column id and insert that same id to another table without dual table

I have a table:
create table osoba (osoba_id number,
ime_osobe varchar2(200),
prezime_osobe varchar2(200),
kartica_id number)
create table kartica (kartica_id number,
dozvoljen_ulaz_id number)
I need to make procedure that
Inserts data into table osoba.
Checks if there is data in table kartica column kartica_id.
If there is, add that id to column kartica_id in table osoba.
If there isn't, then add kartica_id in kartica table by sequence I have created and then add that newly created kartica.kartica_id to kartica_id in table osoba.
Kartica_id in kartica and kartica_id in osoba must be unique and only one kartica_id for one record in osoba for that exact record (person added).
If there is already a kartica_id added to osoba.kartica_id of the same value then throw error message 'Kartica_id already exists. No same values allowed.' and insert next new kartica_id value to kartica table and pass that value to kartica_id in table osoba.
I'm new to pl/sql so this is where I got so far:
create or replace procedure insertOsoba
( o_osoba_id in osoba.osoba_id%type default generate_id.nextval,
o_ime_osobe in osoba.ime_osobe%type,
o_prezime_osobe in osoba.prezime_osobe%type,
o_kartica_id in kartica.kartica_id%type default null --must be optional
)
is
begin
insert into osoba (osoba_id,ime_osobe,prezime_osobe,kartica_id)
values (o_osoba_id,o_ime_osobe,o_prezime_osobe,o_kartica_id);
end insertosoba;
I'm posting solution where I use kartica_seq as a sequence for kartica_id. Please replace it with your sequence name or create sequence if it not exist.
create sequence kartica_seq start with 1 increment by 1;
create or replace procedure insertOsoba
( o_osoba_id in osoba.osoba_id%type default generate_id.nextval,
o_ime_osobe in osoba.ime_osobe%type,
o_prezime_osobe in osoba.prezime_osobe%type,
o_kartica_id in kartica.kartica_id%type default null --must be optional
)
is
cnt number;
cnt2 number;
begin
select count(*) into cnt from osoba where kartica_id = o_kartica_id;
select count(*) into cnt2 from kartica where kartica_id = o_kartica_id;
if(cnt = 0) then
--there is no person with such kartica and user passed existing kartica_id
if(o_kartica_id is not null and cnt2 > 0) then
insert into osoba (osoba_id,ime_osobe,prezime_osobe,kartica_id)
values (o_osoba_id,o_ime_osobe,o_prezime_osobe,o_kartica_id);
--there is no person with such kartica but we need to create one entry using sequence
else
insert into kartica (kartica_id) values (kartica_seq.nextval);
insert into osoba (osoba_id,ime_osobe,prezime_osobe,kartica_id)
values (o_osoba_id,o_ime_osobe,o_prezime_osobe,kartica_seq.currval);
end if;
end if;
--there is person with such kartica
if(cnt > 0) then
dbms_output.put_line('Kartica_id already exists. No same values allowed.'); --or raise an exception here
end if;
commit;
end insertosoba;

how to retrieve data from multiple colums from bulk collect

DECLARE
TYPE two_cols IS RECORD
(
family_id family_members.family_id %TYPE,
city family_members.city%TYPE
);
TYPE family_members_t IS TABLE OF two_cols;
l_family_members family_members_t;
BEGIN
SELECT family_id,city
BULK COLLECT INTO l_family_members
FROM (SELECT x.family_id, x.City, x.Member_count,row_number()
OVER (PARTITION BY x.family_id ORDER BY x.Member_count DESC) rn
FROM (SELECT family_id, City, COUNT(*) Member_count
FROM FAMILY_MEMBERS
GROUP BY family_id, City) x) y
WHERE y.rn = 1;
for rec in 1..l_family_members.count
loop
dbms_output.put_line('majority mem of family id'
|| l_family_members.family_id(rec)
|| 'stay in '||l_family_members.city(rec));
end loop;
END;
Error:
ORA-06550: line 23, column 69: PLS-00302: component 'FAMILY_ID' must
be declared ORA-06550: line 23, column 1: PL/SQL: Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
I am confused at the output line.. I am not getting how to retrieve data from bulk collect as there are two columns in it..how to distinguish them and retrieve them?
you are trying to select 2 columns into 1 record which doesn't work.
depending on your database version, you may be able to select records which then get bulk collected into a table as follows
DECLARE
TYPE two_cols IS RECORD
(
family_id family_members.family_id %TYPE,
city family_members.city%TYPE
);
TYPE family_members_t IS TABLE OF two_cols;
l_family_members family_members_t;
BEGIN
SELECT two_cols(family_id,city )
BULK COLLECT INTO l_family_members
FROM (SELECT x.family_id, x.City, x.Member_count,row_number()
OVER (PARTITION BY x.family_id ORDER BY x.Member_count DESC) rn
FROM (SELECT family_id, City, COUNT(*) Member_count
FROM FAMILY_MEMBERS
GROUP BY family_id, City) x) y
WHERE y.rn = 1;
for rec in 1..l_family_members.count
loop
dbms_output.put_line('majority mem of family id'
|| l_family_members(rec).family_id
|| 'stay in '||l_family_members(rec).city);
end loop;
END;
NB: I also fixed the reference in the output loop to put the (rec) after the table and before column

Resources