Delete duplicated records using procedure in Oracle/PLSQL - oracle

As the title, I wanna create a procedure in Oracle/PLSQL to delete rows which share same values in some columns. I know how to implement it using Query, but how to do it using procedure? Do I have to use any loop? I am very new to PLSQL
Please help, thank you a lot!

If you want a simple procedure to delete from a particular table you can use the below piece of code:
CREATE OR REPLACE PROCEDURE DELETE_DUPLICATE AS
BEGIN
FOR I IN (SELECT TAB.A, TAB.B, MIN(ROWID) RID
FROM DUPLICATE_TABLE TAB
GROUP BY TAB.A, TAB.B
HAVING COUNT(*) > 1) LOOP
DELETE FROM DUPLICATE_TABLE TAB
WHERE I.RID <> TAB.ROWID
AND TAB.A = I.A
AND TAB.B = I.B;
COMMIT;
END LOOP;
END;
Here DUPLICATE_TABLE is the table having duplicate values. We are deleting rows having same values in columns A and B.

Hey. As per your question, although it is not advicable to create
procedure for this simpler task which can be easily done via Pure SQL.
But if its really imp to make it as a stored procedure then i would
suggest to use PURE SQL logic than using any kind of loop as there
will be Context Switching which will have a toll on the database.
Below is a snippet which i think will be useful also incorporated
Analytical function to suffice your issue. Let me know if it helps.
CREATE OR REPLACE PROCEDURE Dup_DELETE
AS
BEGIN
DELETE
FROM EMP
WHERE EMP.ROWID IN
-- Assuming that i am trying to segregate the duplicate values on Empno and ename
(SELECT A.ROWID
FROM
(SELECT ROW_NUMBER() OVER(PARTITION BY EMPNO,ENAME ORDER BY JOB DESC) RNK,
empno,
ename,
rowid
FROM EMP
)A
WHERE A.RNK <> 1
);
END;

Just put your SQL statement in a procedure. There's no rule that says you have to change the approach because it's PL/SQL. For example,
create or replace procedure dedupe_sometable
as
begin
delete sometable
where rowid in
( select lag(rowid) over (partition by id order by null)
from sometable );
end dedupe_sometable;
Add logging etc as needed.
(Ideally this would be within a package and not a standalone procedure.)

If you know how to do it in SQL, better to do it in sql. PL/SQL should be used only when you cannot write specific task in SQL statement or if there is performance issues in the query and can improve by writing the logic in PL/SQL (second scenario is very rare).
If you want to write PL/SQL procedure to parameterize so that any table can be passed to delete the duplicates from it, then it makes sense. You need to dynamically generate delete statement in the procedure and execute using execute immediate.
If your intention is to learn PL/SQL, then it is programming language and you need to spend some time as if you are learning new programming language.

It is not recommended to use plsql for something that can be done using plain sql.
Whenever you have a combination of sql and plsql, you are switching between sql and plsql engine. So it does not make sense to incur this overhead without proper requirement.
If for some reason there is still a need for doing this, you can atleast implement bulk delete to reduce some overhead. Please refer to the code below to find out how to do that -
DECLARE
TYPE t_del IS TABLE OF VARCHAR2(100);
l_del t_del;
CURSOR c IS
SELECT MIN(ROWID) RID
FROM test_tbl TAB
GROUP BY TAB.age, TAB.gender
HAVING COUNT(*) > 1;
BEGIN
OPEN c;
LOOP
FETCH c BULK COLLECT INTO l_del;
EXIT WHEN l_del.COUNT = 0;
FORALL i IN l_del.FIRST..l_del.last
DELETE FROM test_tbl WHERE ROWID = l_del(i);
END LOOP;
END;

Related

Simple query with variables as parameters in SQL Developer

Ok I have seen a bunch of questions attempt to answer this but all the answers seem horribly complicated or don't actually answer the question.
Is there a way to simply do something like this:
DECLARE
v_some_variable VARCHAR2(10) := 'Find Me';
BEGIN
select column1, column2, column3
from someTable st
where st.columnTarget = v_some_variable
END;
And have this displayed in a grid?
This is so simple on SQL server and is maddeningly NOT simple in Oracle.
Every permutation or combination I try from various answers either does not work and gives an error, requires defining every column and datatype from the output prior to running the query or does not output to the grid and you have to roll your own text output. All of these are very poor solutions in my opinion. What am I missing? Oracle can't be this bad can it?
It is arguably possible to print query output from PL/SQL by returning a refcursor or printing it or something. Although like you, I don't find any of those solutions very easy or intuitive.
I do a lot of SQL development in Oracle, and generally - if you want to get a result grid back, don't use a PL/SQL block. Just use plain SQL statements.
select column1, column2, column3
from someTable st
where st.columnTarget = :v_some_variable; -- when you run this, it'll prompt for the value
If you don't like the prompt popup, you can also declare bind variables in SQL*Plus (the backend behind SQL Developer) like this:
var v_some_variable VARCHAR2(10)
exec :v_some_variable := 'Find Me';
select column1, column2, column3
from someTable st
where st.columnTarget = :v_some_variable;
If you run that as a script (F5), it won't prompt you for a value. Note that this still isn't using a PL/SQL block.
If you just want to use SQLPlus to write a query and execute it to see the results in a grid, then the simplest way to go is the following:
Start by defining the parameter:
define p = 1000;
Then you can use it:
select *
from table1 t1
join etc on etc.id = t1.etc_id
where etc.p = &p
You need to learn to do things the Oracle way, which is not the same as the SQL Server way. It's also incredibly simple in Oracle - if you know the way Oracle does it.
First we create the database table and add some data.
create table some_table (first varchar2(10), second varchar2(10));
insert into some_table values ('First', 'Find me');
Now for the PL/SQL.
declare
v_var varchar2(10);
v_var2 varchar2(10);
begin
v_var := 'Find me';
select first into v_var2 from some_table where second = v_var;
end;
/
Compare my PL/SQL with yours. Hint: Note that mine contains into
Refer to this db fiddle

How to find the column used in the dynamic query without executing whole query

Problem Statement
I have a dynamic SQL which i need to store in a table ,but before
storing the sql i need to validate the sql with the list of columns
stored in another table.
Without executing the query , is it possible to find name of columns in the select ?
Approach1
Only option i can think of is ,try to use explain plan of the query and read the meta data in the data dictionaries table .But unfortunately i am not able to find any table with such data.Please let me know if you know such views?
Approach2
Use DBMS_SQL.DESCRIBE_COLUMNS package to find the column name ,but i believe this will execute the whole query.
You don't need to execute the query to get the column names, you just need to parse it; e.g. as a simple example:
set serveroutput on
declare
l_statement varchar2(4000) := 'select * from employees';
l_c pls_integer;
l_col_cnt pls_integer;
l_desc_t dbms_sql.desc_tab;
begin
l_c := dbms_sql.open_cursor;
dbms_sql.parse(c=>l_c, statement=>l_statement, language_flag=>dbms_sql.native);
dbms_sql.describe_columns(c=>l_c, col_cnt=>l_col_cnt, desc_t=>l_desc_t);
for i in 1..l_col_cnt loop
dbms_output.put_line(l_desc_t(i).col_name);
end loop;
dbms_sql.close_cursor(l_c);
exception
when others then
if (dbms_sql.is_open(l_c)) then
dbms_sql.close_cursor(l_c);
end if;
raise;
end;
/
which outputs:
EMPLOYEE_ID
FIRST_NAME
LAST_NAME
EMAIL
PHONE_NUMBER
HIRE_DATE
JOB_ID
SALARY
COMMISSION_PCT
MANAGER_ID
DEPARTMENT_ID
PL/SQL procedure successfully completed.
You can do whatever validation you need on the column names inside the loop.
Bear in mind that you'll only see (and validate) the column names or aliases for column expressions, which won't necessarily reflect the data that is actually being retrieved. Someone could craft a query that pulls any data from anywhere it has permission to access, but then gives the columns/expression aliases that are considered valid.
If you're trying to restrict access to specific data then look into other mechanisms like views, virtual private database, etc.
DBMS_SQL.PARSE will not execute a SELECT statement but it will execute a DDL statement. If the string 'select * from employees' is replaced by 'drop table employees' the code will fail but the table will still get dropped.
If you're only worried about the performance of retrieving the metadata then Alex Poole's answer will work fine.
If you're worried about running the wrong statement types then you'll want to make some adjustments to Alex Poole's answer.
It is surprisingly difficult to tell if a statement is a SELECT instead of something else. A simple condition checking that the string begins with select will work 99% of the time but getting from 99% to 100% is a huge amount of work. Simple regular expressions cannot keep up with all the different keywords, comments, alternative quoting format, spaces, etc.
/*comment in front -- */ select * from dual
select * from dual
with asdf as (select * from dual) select * from asdf;
((((((select * from dual))))));
If you need 100% accuracy I recommend you use my open source PLSQL_LEXER. Once installed you can reliably test the command types like this:
select
statement_classifier.get_command_name(' /*comment*/ ((select * from dual))') test1,
statement_classifier.get_command_name('alter table asdf move compress') test2
from dual;
TEST1 TEST2
----- -----
SELECT ALTER TABLE

how to select table name from a query in oracle

select SRI_PACK_INDEX_VAL
from (select TABLE_NAME
from all_tables
where table_name like '%SE_RAO_INDEX_04_12_2015%');
Hi,
The above query is not working in oracle.Can anyone help me to solve this
You can't dynamically select from tables like that as Oracle's parser can't figure it out before fetch to know what tables it needs to read. If you want to do a dynamic fetch you need to do dynamic sql. Something like (forgive any minor syntax errors-I'm away from my database at the moment
declare
index_val number; -- i am assuming it is a number, change as appropriate
begin
-- I'm doing this in a loop if there are multiple tables that meet your name mask.
-- If there will only be one, then just select it into a variable and use it that way instead
for tab_name in (SELECT TABLE_NAME
from all_tables
where table_name like '%SE_RAO_INDEX_04_12_2015%')
loop
execute immediate 'select SRI_PACK_INDEX_VAL from '||tab_name.table_name
into index_val;
-- do your stuff with it
end loop;
end;
now, that works if the select only brings back one row. if you are bringing back multiple rows, then you have to handle it differently. In that case you will either want to EXECUTE IMMEDIATE a pl/sql block and embed your processing of the results there, or execute immediate bulk collect into an array, an example on how to do that is here.
select
extractvalue(
xmltype(
dbms_xmlgen.getxml(
'select sri_pack_index_val a from '||owner||'.'||table_name
)
), '/ROWSET/ROW/A'
) sri_pack_index_val
from all_tables
where table_name like '%SE_RAO_INDEX_04_12_2015%';
This query is based on a post from Laurent Schneider. Here's a SQLFiddle demonstration.
This is a neat trick to create dynamic SQL in SQL, but it has a few potential issues. It's probably not as fast as the typical dynamic SQL approach as shown by Michael Broughton. And I've ran into some weird bugs when trying to use this for large production queries.
I recommend only using this approach for ad hoc queries.

Insert data from pl/sql table type to another pl/sql table type

I am just wondering whether it is possible to insert data from one pl/sql table to another using bulkcollect?
I am trying it out but looks like it is not possible conceptually and the only way is to loop through the Pl/sql table .
Any insights would be really helpful.
Thanks
below is the simplified version of what i am trying. i think i am making some conceptual mistake here . hence it is not working:
DECLARE
TYPE ROWTBL IS TABLE OF BW_COLUMN.NAME%TYPE ;
PL_TBL_ROW ROWTBL;
TYPE COLNAME_TBL IS TABLE OF BW_COLUMN.NAME%TYPE ;
PL_TBL_COLNAME COLNAME_TBL;
BEGIN
SELECT NAME
BULK COLLECT INTO PL_TBL_ROW
FROM TBL_COL WHERE TBL_ID = 2000081;
SELECT NAME
BULK COLLECT INTO PL_TBL_COLNAME
FROM PL_TBL_ROW;
END;
BULK COLLECT is a mechanism for efficiently reading data into PL/SQL data structures so they can be processed by PL/SQL code. You can certainly use this approach for copying data from one table to another but I suspect it will probably take more time than the simpler approach of using an INSERT statement such as
BEGIN
INSERT INTO SOME_TABLE (COL_1, COL_2, COL_N)
SELECT COL_1, COL_2, COL_N
FROM SOME_OTHER_TABLE
WHERE SOME_FIELD = SOME_OTHER_VALUE;
END;
Best of luck.

How to create a table and insert in the same statement

I'm new to Oracle and I'm struggling with this:
DECLARE
cnt NUMBER;
BEGIN
SELECT COUNT(*) INTO cnt FROM all_tables WHERE table_name like 'Newtable';
IF(cnt=0) THEN
EXECUTE IMMEDIATE 'CREATE TABLE Newtable ....etc';
END IF;
COMMIT;
SELECT COUNT(*) INTO cnt FROM Newtable where id='something'
IF (cnt=0) THEN
EXECUTE IMMEDIATE 'INSERT INTO Newtable ....etc';
END IF;
END;
This keeps crashing and gives me the "PL/SQL: ORA-00942:table or view does not exist" on the insert-line. How can I avoid this? Or what am I doing wrong? I want these two statements (in reality it's a lot more of course) in a single transaction.
It isn't the insert that is the problem, it's the select two lines before. You have three statements within the block, not two. You're selecting from the same new table that doesn't exist yet. You've avoided that in the insert by making that dynamic, but you need to do the same for the select:
EXECUTE IMMEDIATE q'[SELECT COUNT(*) FROM Newtable where id='something']'
INTO cnt;
SQL Fiddle.
Creating a table at runtime seems wrong though. You said 'for safety issues the table can only exist if it's filled with the correct dataset', which doesn't entirely make sense to me - even if this block is creating and populating it in one go, anything that relies on it will fail or be invalidated until this runs. If this is part of the schema creation then making it dynamic doesn't seem to add much. You also said you wanted both to happen in one transaction, but the DDL will do an implicit commit, you can't roll back DDL, and your manual commit will start a new transaction for the insert(s) anyway. Perhaps you mean the inserts shouldn't happen if the table creation fails - but they would fail anyway, whether they're in the same block or not. It seems a bit odd, anyway.
Also, using all_tables for the check could still cause this to behave oddly. If that table exists in another schema, you create will be skipped, but you select and insert might still fail as they might not be able to see, or won't look for, the other schema version. Using user_tables or adding an owner check might be a bit safer.
Try the following approach, i.e. create and insert are in two different blocks
DECLARE
cnt NUMBER;
BEGIN
SELECT COUNT (*)
INTO cnt
FROM all_tables
WHERE table_name LIKE 'Newtable';
IF (cnt = 0)
THEN
EXECUTE IMMEDIATE 'CREATE TABLE Newtable(c1 varchar2(256))';
END IF;
END;
DECLARE
cnt2 NUMBER;
BEGIN
SELECT COUNT (*)
INTO cnt2
FROM newtable
WHERE c1 = 'jack';
IF (cnt2 = 0)
THEN
EXECUTE IMMEDIATE 'INSERT INTO Newtable values(''jill'')';
END IF;
END;
Oracle handles the execution of a block in two steps:
First it parses the block and compiles it in an internal representation (so called "P code")
It then runs the P code (it may be interpreted or compiled to machine code, depending on your architecture and Oracle version)
For compiling the code, Oracle must know the names (and the schema!) of the referenced tables. Your table doesn't exist yet, hence there is no schema and the code does not compile.
To your intention to create the tables in one big transaction: This will not work. Oracle always implicitly commits the current transaction before and after a DDL statement (create table, alter table, truncate table(!) etc.). So after each create table, Oracle will commit the current transaction and starts a new one.

Resources