Eliminating duplicates in collection - oracle

I have a declared collection of object type:
CREATE TYPE category_type AS OBJECT (
col1 VARCHAR2(6),
col2 VARCHAR2(10),
col3 NUMBER);
/
CREATE TYPE category_tab AS TABLE OF category_type;
/
And in the PL/SQL code:
v_category_data category_Tab := category_tab();
After I populate v_category_data, I find there are duplicates. I need to remove them. So, I have tried to work with MULTISET UNION DISTINCT by this:
v_new_data := v_category_data;
v_new_data := v_new_data MULTISET UNION DISTINCT v_category_data;
However, I get this error:
804/17 PLS-00306: wrong number or types of arguments in call to
'MULTISET_UNION_DISTINCT'
I tried using the MAP functionality, but did not understand how exactly to use it, or if it would help.
Does anyone know of a way to remove the duplicates in an object collection?

multiset union distinct requires the elements of the collection to be comparable. In your case the elements are PL/SQL records that are unfortunately not comparable data structures (i.e. PL/SQL provides no build-in mechanism to compare PL/SQL records).
multiset union works because it doesn't need to compare the elements.
If for some reason you do not want to use distinct when you're populating your v_category_data collection then I advise to do as I have written below. Selecting from the collection and using distinct on the columns.
This is the part that interests you:
SELECT Category_type(col1, col2, col3)
bulk collect INTO v_new_data
FROM (SELECT DISTINCT col1,
col2,
col3
FROM TABLE(v_category_data));
And this is the entire code with which I checked.
DECLARE
v_category_data CATEGORY_TAB := Category_tab();
v_new_data CATEGORY_TAB := Category_tab();
BEGIN
--populate the collection with 2 types of duplicates and print them
FOR i IN 1 .. 10 LOOP
v_category_data.Extend();
IF i < 4 THEN
V_category_data(v_category_data.last) :=
Category_type('Test', 'Test', 1);
ELSE
V_category_data(v_category_data.last) :=
Category_type('Test2', 'Test2', 2);
END IF;
END LOOP;
FOR i IN v_category_data.first..v_category_data.last LOOP
dbms_output.Put_line(V_category_data(i).col1
||' '
||V_category_data(i).col2
||' '
||V_category_data(i).col3);
END LOOP;
dbms_output.Put_line('After processing'
||Chr(10));
-- populate your collection using distinct and print the content
SELECT Category_type(col1, col2, col3)
bulk collect INTO v_new_data
FROM (SELECT DISTINCT col1,
col2,
col3
FROM TABLE(v_category_data));
FOR i IN v_new_data.first..v_new_data.last LOOP
dbms_output.Put_line(V_new_data(i).col1
||' '
||V_new_data(i).col2
||' '
||V_new_data(i).col3);
END LOOP;
END;

See below how we can achieve this :
CREATE TYPE category_type AS OBJECT (
col1 VARCHAR2(6),
col2 VARCHAR2(10),
col3 NUMBER);
/
CREATE OR REPLACE TYPE category_tab AS TABLE OF category_type ;
/
declare
v_category_data category_Tab := category_tab();
v_new_data category_Tab := category_tab();
begin
--Defining collection length
v_category_data.extend(3);
--Populating the collection
v_category_data(1):= category_type('A','B',1);
v_category_data(2):= category_type('A','B',1);
v_category_data(3):= category_type('B','C',2);
dbms_output.put_line('*********First Collection Elements**************');
for i in 1..v_category_data.count
loop
dbms_output.put_line(v_category_data(i).col1 || v_category_data(i).col2 || v_category_data(i).col3 );
end loop;
--Way to remove duplicate
select
v_category_data MULTISET UNION DISTINCT category_Tab(category_type('A','B',1))
into
v_new_data
from
dual;
--Displaying elements of second collection
dbms_output.put_line('*********Second Collection Elements**************');
for i in 1..v_new_data.count
loop
dbms_output.put_line(v_new_data(i).col1 || v_new_data(i).col2 || v_new_data(i).col3 );
end loop;
end;
Output
SQL> /
*********First Collection Elements**************
AB1
AB1
BC2
*********Second Collection Elements**************
AB1
BC2
PL/SQL procedure successfully completed.

Related

Insert using Union/Union All fails to insert all rows

I am trying to insert the result of a UNION into a table .While I cannot reproduce the original query here, due to it being in a restricted environment, it's structure looks similar to this:
WITH temp(X,Y,Z) AS(
SELECT....
)
SELECT X,Y,Z from TEMP --PART A
UNION
SELECT 'A','B','C' FROM DUAL;--PART B
Part A of the query returns about 1000 records, while Part B is just a single record.
When I wrap this whole query inside a procedure, such that the result of the select statements are inserted into a table, I see that only Part B, a single record gets inserted into the target table.Here is what it loos like when I wrap it inside the procedure.
CREATE OR REPLACE PROCEDURE PROC_INSERT
AS
BEGIN
INSERT INTO TABLENAME(COLUMN1,COLUMN2....)
WITH temp(X,Y,Z) AS(
SELECT....
)
SELECT X,Y,Z from TEMP
UNION
SELECT 'A','B','C' FROM DUAL;
COMMIT;
END;
I tried using both UNION and UNION ALL, but somehow I don't see to get the Part A of the result set inserted into the table.
What could be the possible reasons for such a scenario to occur? I tried to replicate the same ,but failed.
You can do something like this
DECLARE
CURSOR CUR IS SELECT X, Y, Z FROM OG_TABLE UNION SELECT 'A', 'B', 'C' FROM DUAL;
BEGIN
FOR REC IN CUR
LOOP
INSERT INTO NEWTABLE (COL1, COL2, COL3) VALUES (REC.X, REC.Y, REC.Z);
END LOOP;
END;
Alternative Optimized code
DECLARE
LIM PLS_INTEGER := 100;
CURSOR CUR IS SELECT X, Y, Z FROM OG_TABLE UNION SELECT 'A', 'B', 'C'
FROM DUAL;
TYPE OG_REC IS RECORD( X OG_TABLE.X%TYPE, Y OG_TABLE.Y%TYPE, Z
OG_TABLE%TYPE);
TYPE OGT IS TABLE OF OG_REC;
OG_TAB OGT;
BEGIN
OPEN CUR;
LOOP
FETCH CUR BULK COLLECT INTO OG_TAB LIMIT LIM;
BEGIN
FORALL i IN 1..OG_TAB.COUNT SAVE EXCEPTIONS
INSERT INTO NEWTABLE (COL1, COL2, COL3) VALUES (OG_TAB(i).X,
OG_TAB(i).Y, OG_TAB(i).Z);
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
EXIT WHEN CUR%NOTFOUND;
END LOOP;
COMMIT;
CLOSE CUR;
END;

ORA-00928: missing SELECT keyword

In MYTABLE there are courses and their predecessor courses.
What I am trying to is to find the courses to be taken after the specified course. I am getting missing SELECT keyword error. Why I am getting this error although I have SELECT statement in FOR statement ? Where am I doing wrong ?
DECLARE
coursename varchar2(200) := 'COURSE_101';
str varchar2(200);
BEGIN
WITH DATA AS
(select (select course_name
from MYTABLE
WHERE predecessors like ('''%' || coursename||'%''')
) str
from dual
)
FOR cursor1 IN (SELECT str FROM DATA)
LOOP
DBMS_OUTPUT.PUT_LINE(cursor1);
END LOOP;
end;
Unless I'm wrong, WITH factoring clause can't be used that way; you'll have to use it as an inline view, such as this:
declare
coursename varchar2(200) := 'COURSE_101';
str varchar2(200);
begin
for cursor1 in (select str
from (select (select course_name
from mytable
where predecessors like '''%' || coursename||'%'''
) str
from dual
)
)
loop
dbms_output.put_line(cursor1.str);
end loop;
end;
/
Apart from the fact that it doesn't work (wrong LIKE condition), you OVERcomplicated it. This is how it, actually, does something:
SQL> create table mytable(course_name varchar2(20),
2 predecessors varchar2(20));
Table created.
SQL> insert into mytable values ('COURSE_101', 'COURSE_101');
1 row created.
SQL>
SQL> declare
2 coursename varchar2(20) := 'COURSE_101';
3 begin
4 for cursor1 in (select course_name str
5 from mytable
6 where predecessors like '%' || coursename || '%'
7 )
8 loop
9 dbms_output.put_line(cursor1.str);
10 end loop;
11 end;
12 /
COURSE_101
PL/SQL procedure successfully completed.
SQL>
Also, is that WHERE clause correct? PREDECESSORS LIKE COURSENAME? I'm not saying that it is wrong, just looks somewhat strange.
To extend #Littlefoot's answer a bit: you can use a common table expression (WITH clause) in your cursor, but the WITH must be part of the cursor SELECT statement, not separate from it:
DECLARE
coursename varchar2(200) := 'COURSE_101';
BEGIN
FOR aRow IN (WITH DATA AS (select course_name AS str
from MYTABLE
WHERE predecessors like '''%' || coursename||'%''')
SELECT str FROM DATA)
LOOP
DBMS_OUTPUT.PUT_LINE(aRow.str);
END LOOP;
END;
Also note that the iteration variable in a cursor FOR-loop represents a row returned by the cursor's SELECT statement, so if you want to display whatever was returned by the cursor you must use dotted-variable notation (e.g. aRow.str) to extract fields from the row.
Best of luck.
CREATE TABLE product
(
PRODUCT_ID int Primary key,
NAME VARCHAR (20) not null,
Batchno int not null,
Rate int not null,
Tax int not null,
Expiredate date not null
);
INSERT INTO PRODUCT VALUSES(1 , 'vasocare', 32 , 15 , 2 , 01-JAN-2021);

Execute Immediate Loop PL/SQL

Can I use "execute immediate" with for cycle?
I need of generate a combinations of values in pl/sql.
About this I'm thinking of call a series of nested dynamics for cycle.
Example:
column 1 have this values: A,B
column 2 have this values: C,D,E
I would like to generate this combination:
AC / AD/ AE/ BC / BD /BE
I'm thinking to obtain this like:
for i in 1..count.column1
for j in 1..count.column2
dbms_output.put_line(column1.value(i)||'-'||column1.value(j));
end loop;
end loop;
Due to I don't know the number of column (variable),
Can I use a execute immediate?
declare
sql_stmt varchar2(200);
begin
sql_stmt := 'for i in 1..count.column1 for j in 1..count.column2 dbms_output.put_line(column1.value(i)||'-'||column1.value(j)); end loop; end loop';
execute immediate sql_stmt;
end;
But I have error ORA-06512.
How can i make this? :)
Thank you in advance for your suggestion!
If you really want to do this slowly in PL/SQL using nested loops, you don't need anything to be dynamic. You'd just want
for i in (select distinct column1
from your_table)
loop
for j in (select distinct column2
from your_table)
loop
dbms_output.put_line( i.column1 || j.column2 );
end loop;
end loop;
In the vast majority of cases, though, you'd be better off doing this in SQL
with col1 as (
select distinct col1 val
from your_table
),
col2 as (
select distinct col2 val
from your_table
)
select col1.val || col2.val
from col1
cross join col2
I think you can easily get the combinations using this select statement.
SELECT DISTINCT t1.col1, t2.col2
FROM table_name t1, table_name t2;

Select List of Column Names / Aliases from Custom Sub-Query

In Oracle, is there a way to select all the columns that are returned from a custom query with aliases? As an example, let's say we have a query as the following:
SELECT FIRST_NAME AS COL1, LAST_NAME AS COL2, ADDRESS AS COL3
FROM PEOPLE
I would like to know if an encapsulating query can be made that would return:
COL1
COL2
COL3
Here is how to do it in PL/SQL. Don't know if it is possible with straight oracle SQL only. You could always encapulate it in some kind of function if needed.
DECLARE
TYPE RefCursor_Type IS REF CURSOR;
D_RefCur RefCursor_Type;
D_DescriptionTable DBMS_SQL.DESC_TAB2;
D_ColumnCount INTEGER;
D_CursorHandle INTEGER;
BEGIN
OPEN D_RefCur
FOR 'SELECT FIRST_NAME AS COL1, LAST_NAME AS COL2, ADDRESS AS COL3 FROM PEOPLE';
D_CursorHandle := DBMS_SQL.to_cursor_number (D_RefCur);
DBMS_SQL.DESCRIBE_COLUMNS2 (D_CursorHandle,
D_ColumnCount,
D_DescriptionTable);
FOR idx IN 1 .. D_ColumnCount
LOOP
DBMS_OUTPUT.put_line (D_DescriptionTable (idx).col_name);
END LOOP;
END;

Bulk Collect Twice over same nested table

Is there any way that after the second bulk collect, data does not get override of the first bulk collect. I don't want to iterate in loop.
DECLARE
TYPE abc IS RECORD (p_id part.p_id%TYPE);
TYPE abc_nt
IS
TABLE OF abc
INDEX BY BINARY_INTEGER;
v_abc_nt abc_nt;
BEGIN
SELECT p_id
BULK COLLECT
INTO v_abc_nt
FROM part
WHERE p_id IN ('E1', 'E2');
SELECT p_id
BULK COLLECT
INTO v_abc_nt
FROM part
WHERE p_id IN ('E3', 'E4');
FOR i IN v_abc_nt.FIRST .. v_abc_nt.LAST
LOOP
DBMS_OUTPUT.put_line (
'p_id is ' || v_abc_nt (i).p_id
);
END LOOP;
END;
OUTPUT:
p_id is E3
p_id is E4
Note: E1 and E2 is present in part table.
You can't simply add the data to the collection, no.
You can, however, do a BULK COLLECT into a separate collection and then combine the collections assuming that you really just need/ want a nested table rather than an associative array...
DECLARE
TYPE abc IS RECORD (p_id part.p_id%TYPE);
TYPE abc_nt
IS
TABLE OF abc;
v_abc_nt abc_nt;
v_abc_nt2 abc_nt;
BEGIN
SELECT p_id
BULK COLLECT
INTO v_abc_nt
FROM part
WHERE p_id IN ('E1', 'E2');
SELECT p_id
BULK COLLECT
INTO v_abc_nt2
FROM part
WHERE p_id IN ('E3', 'E4');
v_abc_nt := v_abc_nt MULTISET UNION v_abc_nt2;
FOR i IN v_abc_nt.FIRST .. v_abc_nt.LAST
LOOP
DBMS_OUTPUT.put_line (
'p_id is ' || v_abc_nt (i).p_id
);
END LOOP;
END;
If you really want to use an associative array, you would need to write some code because there is no way for Oracle to know automatically how to remap the associations of one array when you combine it with another associative array that has some of the same keys.
You can write it like this
bad example:
declare
type t_numb is record(
numb number);
type t_numb_list is table of t_numb;
v_numb_list t_numb_list;
begin
with q as
(select 1 a from dual union select 2 from dual union select 3 from dual)
select q.a bulk collect into v_numb_list from q;
with w as
(select 4 a from dual union select 5 from dual union select 6 from dual)
select w.a bulk collect into v_numb_list from w;
for r in 1 .. v_numb_list.count loop
dbms_output.put_line(v_numb_list(r).numb);
end loop;
end;
and this works good:
declare
type t_numb is record(
numb number);
type t_numb_list is table of t_numb;
v_numb_list t_numb_list := t_numb_list();
v_numb t_numb;
begin
for q in (select 1 a
from dual
union
select 2
from dual
union
select 3
from dual) loop
v_numb.numb := q.a;
v_numb_list.extend;
v_numb_list(v_numb_list.count) := v_numb;
end loop;
for w in (select 4 a
from dual
union
select 5
from dual
union
select 6
from dual) loop
v_numb.numb := w.a;
v_numb_list.extend;
v_numb_list(v_numb_list.count) := v_numb;
end loop;
for r in 1 .. v_numb_list.count loop
dbms_output.put_line(v_numb_list(r).numb);
end loop;
end;

Resources