I'm trying to compare a global temporary table to another table and want to insert into a log table but can not seem to find the best/most efficient way to accomplish this.
Log Table
CREATE TABLE LogTable
(
Date_Time DATETIME,
Name VARCHAR2(10 CHAR),
old VARCHAR2(20 CHAR),
new VARCHAR2(20 CHAR),
)
Object Type
CREATE OR REPLACE type dbo.P_REC AS OBJECT
(
ATTR1 VARCHAR2(10 CHAR),
ATTR2 VARCHAR2(20 CHAR),
ATTR3 VARCHAR2(20 CHAR),
ATTR4 VARCHAR2(20 CHAR)
);
Collection Type
CREATE OR REPLACE type dbo.P_REC_LIST IS TABLE OF P_REC;
Stored Procedure
PROCEDURE PASSPEOPLETOORACLE(tmpCollection IN P_REC_LIST , resultCursor out sys_refcursor)
IS
BEGIN
IF tmpCollection .count > 0 THEN
INSERT INTO tmpPTbl SELECT * FROM table1; <--tmpPTbl is a copy of table1 before the merge statement.
MERGE INTO table1 MKTP
USING (
WITH tmpTBL AS
(
SELECT ADCOLL.ATTR1,
ADCOLL.ATTR2,
MV.ATTR3,
MV.ATTR4
FROM TABLE(tmpCollection) ADCOLL
LEFT JOIN materializedView MV
ON ADCOLL.ATTR1 = MV.ATTR1
)
SELECT DISTINCT COALESCE(tmpTBL.ATTR1,MKtmpTBL.ATTR1) AS ATTR1,
tmpTBL.ATTR2,
tmpTBL.ATTR3,
tmpTBL.ATTR4,
CASE WHEN tmpTBL.ATTR1 IS NULL
THEN 'Y' ELSE 'N' END
match_flag FROM tmpTBL
FULL JOIN table1 MKtmpTBL
ON MKtmpTBL.ATTR1 = tmpTBL.ATTR1
) usingTBL
ON (MKTP.ATTR1 = usingTBL.ATTR1)
WHEN MATCHED THEN
UPDATE SET MKTP.ATTR2 = usingTBL.ATTR2,
MKTP.ATTR3 = usingTBL.ATTR3,
MKTP.ATTR4 = usingTBL.ATTR4,
DELETE WHERE match_flag = 'Y'
WHEN NOT MATCHED THEN
INSERT (ATTR1)
VALUES (usingTBL.ATTR1);
END IF;
END;
Id like a way to compare the newly update records in table1 to the prior records in tmpPTbl and where the old and new values differ, insert a new row into the log table.
2019-02-14 23:59:59,jdoe,abcd,efgh would be an example of a record inserted into the log table.
tmpPTbl & table1 both have 50 columns in them & about 16k rows on average.
The best solution for you would be to create a Trigger on table Table1. So that any operation occurs on Table1 it can be logged to Logtable. See below demo:
CREATE TABLE table1
(col1 VARCHAR2(10),
col2 VARCHAR2(10),
col3 VARCHAR2(10) );
/
--Trigger
CREATE OR REPLACE TRIGGER Log_Entry before
INSERT OR
UPDATE ON table1 FOR EACH row
BEGIN
IF INSERTING THEN
INSERT INTO LogTable VALUES
(sysdate, :new.col1, :new.col2, :new.col3
);
ELSIF UPDATING THEN
INSERT INTO LogTable VALUES
(sysdate, :old.col1, :old.col2, :old.col3
);
END IF;
END;
/
Execution:
SQL> Insert into table1 values ('A','B','C');
SQL>Update table1
set col1 ='Z'
where col1 = 'A';
SQL> Merge INTO table1 tb1 USING
(SELECT 'Z' col1 , 'D' col2, 'K' col3 FROM dual
) tb2 ON (tb1.col1 = tb2.col1)
WHEN matched THEN
UPDATE SET tb1.col2=tb2.col2 WHEN NOT matched THEN
INSERT VALUES
(tb2.col1,tb2.col2,tb2.col3
);
SQL>Commit;
SQL> Select * from logtable;
DATE_TIME NAME OLD NEW
--------- ---------- -------------------- --------------------
15-FEB-19 A B C
15-FEB-19 Z B C
15-FEB-19 Z B C
Note there is no need to copy data to tmpPTbl table as well.
Related
I have below stored procedure:
create or replace PROCEDURE CALCULATE_RECOVERY_HISTORY(p_month IN VARCHAR2) AS
l_id NUMBER;
BEGIN
ADD_LOG_INFO('CALCULATE_RECOVERY_HISTORY', 'Procedure Started');
l_id := SQ_AP_RECOVERY_HISTORY.NEXTVAL;
INSERT INTO t_ap_recovery_history (ID, RECOVERY_TARGET_MONTH, TARGET_INSTANCE, RECOVERY_PROGRESS, RECOVERY_TARGET, FAILED_TO_RECOVERY, FOCUS_AREA, IDENTIFIER_CLASS, CREATED_ON)
SELECT l_id,
a_recovery_target_month,
a_target_instance,
COUNT(CASE WHEN A_IS_RECOVERED = 'Y' THEN 1 END),
COUNT(CASE WHEN A_IS_RECOVERED IN ('Y', 'N') THEN 1 END),
COUNT(CASE WHEN A_IS_RECOVERED = 'N' THEN 1 END),
f.focus_area,
r.identifier_class,
SYSDATE
from t_ap_recovery_target t, t_ap_recovery_focusarea f, range r
where t.a_focus_area_id = f.id and t.a_range_id = r.id
and t.a_recovery_target_month = p_month
group by a_target_instance, a_recovery_target_month, f.focus_area, r.identifier_class;
COMMIT;
END CALCULATE_RECOVERY_HISTORY;
When I run the procedure, I get the error
ORA-00001: unique constraint violated.
I've also tried another way which is
SELECT SQ_AP_RECOVERY_HISTORY.NEXTVAL, a_recovery_target_month ...
But this also return another error which is
Sequence number not allowed here
What should I change in the code to solve this constraint issue?
Below is the table definition for T_AP_RECOVERY_HISTORY
CREATE TABLE "DIMSPST"."T_AP_RECOVERY_HISTORY"
( "ID" NUMBER(38,0),
"RECOVERY_TARGET_MONTH" VARCHAR2(6 BYTE) DEFAULT TO_CHAR(SYSTIMESTAMP, 'YYYYMM'),
"TARGET_INSTANCE" VARCHAR2(20 BYTE),
"RECOVERY_PROGRESS" NUMBER(38,0),
"RECOVERY_TARGET" NUMBER(38,0),
"FAILED_TO_RECOVERY" NUMBER(38,0),
"FOCUS_AREA" VARCHAR2(20 BYTE),
"IDENTIFIER_CLASS" VARCHAR2(42 BYTE),
"CREATED_ON" TIMESTAMP (6),
PRIMARY KEY ("ID")
Perform the aggregation in a sub-query and then apply the sequence value in an outer-query:
CREATE PROCEDURE CALCULATE_RECOVERY_HISTORY(
p_month IN VARCHAR2
)
AS
BEGIN
ADD_LOG_INFO('CALCULATE_RECOVERY_HISTORY', 'Procedure Started');
INSERT INTO t_ap_recovery_history (
ID,
RECOVERY_TARGET_MONTH,
TARGET_INSTANCE,
RECOVERY_PROGRESS,
RECOVERY_TARGET,
FAILED_TO_RECOVERY,
FOCUS_AREA,
IDENTIFIER_CLASS,
CREATED_ON
)
SELECT SQ_AP_RECOVERY_HISTORY.NEXTVAL,
a_recovery_target_month,
a_target_instance,
RECOVERY_PROGRESS,
RECOVERY_TARGET,
FAILED_TO_RECOVERY,
focus_area,
identifier_class,
SYSDATE
FROM (
SELECT a_recovery_target_month,
a_target_instance,
COUNT(CASE WHEN A_IS_RECOVERED = 'Y' THEN 1 END) AS RECOVERY_PROGRESS,
COUNT(CASE WHEN A_IS_RECOVERED IN ('Y', 'N') THEN 1 END) AS RECOVERY_TARGET,
COUNT(CASE WHEN A_IS_RECOVERED = 'N' THEN 1 END) AS FAILED_TO_RECOVERY,
f.focus_area,
r.identifier_class
FROM t_ap_recovery_target t
INNER JOIN t_ap_recovery_focusarea f
ON (t.a_focus_area_id = f.id)
INNER JOIN range r
ON (t.a_range_id = r.id)
WHERE t.a_recovery_target_month = p_month
GROUP BY
a_target_instance,
a_recovery_target_month,
f.focus_area,
r.identifier_class
);
END CALCULATE_RECOVERY_HISTORY;
/
Note: If you COMMIT in stored procedures then you cannot chain multiple procedures together and if one fails then ROLLBACK then all. Instead, you should COMMIT in the block that you call the procedures from.
fiddle
One option is to let Oracle create ID. You didn't specify database version you use, so trigger certainly is what would work:
create or replace trigger trg_bi_rec_hist
before insert on t_ap_recovery_history
for each row
begin
:new.id := SQ_AP_RECOVERY_HISTORY.NEXTVAL;
end;
/
Procedure then wouldn't contain insert into the ID column, i.e.
INSERT INTO t_ap_recovery_history (RECOVERY_TARGET_MONTH, ...)
SELECT a_recovery_target_month, ...
Another option (if your database version supports it) is to create ID as identity column instead of a trigger, e.g.
SQL> create table test
2 (id number generated always as identity);
Table created.
Or, if you would not create the trigger like in the previous answer, the procedure should look like below:
create or replace PROCEDURE CALCULATE_RECOVERY_HISTORY(p_month IN VARCHAR2) AS
l_id NUMBER;
BEGIN
ADD_LOG_INFO('CALCULATE_RECOVERY_HISTORY', 'Procedure Started');
INSERT INTO t_ap_recovery_history (ID, RECOVERY_TARGET_MONTH,
TARGET_INSTANCE, RECOVERY_PROGRESS, RECOVERY_TARGET,
FAILED_TO_RECOVERY, FOCUS_AREA, IDENTIFIER_CLASS, CREATED_ON)
with tb as (
SELECT a_recovery_target_month,
a_target_instance,
COUNT(CASE WHEN A_IS_RECOVERED = 'Y' THEN 1 END) c1,
COUNT(CASE WHEN A_IS_RECOVERED IN ('Y', 'N') THEN 1 END) c2,
COUNT(CASE WHEN A_IS_RECOVERED = 'N' THEN 1 END) c3,
f.focus_area,
r.identifier_class
from t_ap_recovery_target t, t_ap_recovery_focusarea f, range r
where t.a_focus_area_id = f.id and t.a_range_id = r.id
and t.a_recovery_target_month = p_month
group by a_target_instance, a_recovery_target_month,
f.focus_area, r.identifier_class
)
select SQ_AP_RECOVERY_HISTORY.NEXTVAL,
a_recovery_target_month,
a_target_instance,
c1,
c2,
c3,
focus_area,
identifier_class,
sysdate
from tb;
COMMIT;
END CALCULATE_RECOVERY_HISTORY;
In general triggers are detrimental to performance in case you have insert select inserting large numbers of rows in one go, or massive updates or massive deletes or merge.
If you have only DML affecting a small number of rows, triggers may save complexity, although I'd rather do more in stored procedures and less in triggers.
Let´s say I create this table:
CREATE TABLE MYTABLE (
id INT NOT NULL AUTO_INCREMENT,
Field1 VARCHAR(30),
Field2 NUMBER(10),
);
Then I will insert this values:
INSERT INTO MYTABLE VALUES(null, 'Value', 10);
What I want is to be able to do both of these inserts:
INSERT INTO MYTABLE VALUES(null, 'Value', 5);
/* This works as there isn´t a row with both Field1='Value' and Field2=5 at the same time */
INSERT INTO MYTABLE VALUES(null, 'Something', 10);
/* This works as there isn´t a row with both Field1='Something' and Field2=10 at the same time */
But I don´t want to be able to do this (repeat both the Field1 and Field2 values together):
INSERT INTO MYTABLE VALUES(null, 'Value', 10);
/* This doesn´t work as there is a row with both Field1='Value' and Field2=10 at the same time */
How can I achieve this behaviour in Oracle? I thought about using ASSERTIONS but they are not yet implemented in Oracle.
I don´t want to be able to do this (repeat both the Field1 and Field2 values together)
You can use a COMPOUND trigger:
CREATE OR REPLACE TRIGGER mytable__not_repeat_f1_and_f2
FOR UPDATE OR INSERT ON MyTable
COMPOUND TRIGGER
TYPE MyTable_Fields_Type IS RECORD(
rid ROWID,
field1 MyTable.Field1%TYPE,
field2 MyTable.Field2%TYPE
);
TYPE MyTable_Fields_Table_Type IS TABLE OF MyTable_Fields_Type;
fields MyTable_Fields_Table_Type := MyTable_Fields_Table_Type();
AFTER EACH ROW IS
BEGIN
fields.EXTEND;
fields(fields.COUNT) := MyTable_Fields_Type(
:NEW.ROWID,
:NEW.Field1,
:NEW.Field2
);
END AFTER EACH ROW;
AFTER STATEMENT IS
num_field1 PLS_INTEGER;
num_field2 PLS_INTEGER;
BEGIN
FOR i IN 1 .. fields.COUNT LOOP
SELECT COUNT( CASE WHEN Field1 = fields(i).Field1 THEN 1 END ),
COUNT( CASE WHEN Field2 = fields(i).Field2 THEN 1 END )
INTO num_field1,
num_field2
FROM MyTable
WHERE ROWID != fields(i).RID;
IF num_field1 > 0 AND num_field2 > 0 THEN
RAISE_APPLICATION_ERROR( -20000, 'Cannot have duplicate Field1 and Field2' );
END IF;
END LOOP;
END AFTER STATEMENT;
END;
/
Then, for the table:
CREATE TABLE MYTABLE (
id INT
GENERATED ALWAYS AS IDENTITY
PRIMARY KEY,
Field1 VARCHAR2(30),
Field2 NUMBER(10)
);
If you do:
INSERT INTO MyTable ( Field1, Field2 )
SELECT 'a', 1 FROM DUAL UNION ALL
SELECT 'b', 2 FROM DUAL UNION ALL
SELECT 'c', 3 FROM DUAL;
That works but then trying to do:
INSERT INTO MyTable ( Field1, Field2 ) VALUES ( 'b', 3 );
Would raise the exception:
ORA-20000: Cannot have duplicate Field1 and Field2
ORA-06512: at "SCHEMA_NAME.MYTABLE__NOT_REPEAT_F1_AND_F2", line 33
ORA-04088: error during execution of trigger 'SCHEMA_NAME.MYTABLE__NOT_REPEAT_F1_AND_F2'
But:
INSERT INTO MyTable ( Field1, Field2 ) VALUES ( 'b', 4 );
Would work since this doesn't repeat a Field1 and a Field2 value together.
db<>fiddle here
If you want each column to be unique, you can just use unique constraints:
CREATE TABLE MYTABLE (
id INT NOT NULL AUTO_INCREMENT,
Field1 VARCHAR(30) UNIQUE,
Field2 NUMBER(10) UNIQUE
);
Create a compound unique index containing both columns so that the combination can't be inserted more than once, whether it be at the same time or at different times.
I don't think there's any way to do this reliably. If I understand correctly, you want to reject an inserted row if Field1 and Field2 both already exist in the table, but not necessarily in the same row.
So if your table looks like:
Field1 Field2
------ ------
Value 5
Something 10
I should be able to insert ('Apple', 7), right? Then when i try to insert ('Value', 7), it fails because there are rows with both those values already.
But I could do them in the reverse order: insert ('Value', 7) and then ('Apple', 7)
So what happens if I do this?
WITH mydata AS (
SELECT 'Apple' AS field1, 7 AS field2 FROM dual UNION ALL
SELECT 'Value', 7 FROM dual
INSERT INTO mytable
SELECT null, field1, field2 FROM mydata
Does that succeed or fail? There's no way to know. You are not guaranteed that the database actions happen in the order in which you think they're going to happen.
You'll run into the same problem if two sessions try to insert these two rows, then commit.
I have two tables and I try to insert rows from SECOND_TABLE to FIRST_TABLE.
I create two tables:
CREATE TABLE FIRST_TABLE(
F_ID NUMBER(10) not null,
F_NAME VARCHAR(8 BYTE) not null,
F_DESCRIPTION NVARCHAR2(1000) not null
);
CREATE TABLE SECOND_TABLE(
S_ID NUMBER(10) not null,
S_NAME VARCHAR(8 BYTE) not null,
S_DESCRIPTION NVARCHAR2(1000) not null
);
I found differences between SECOND_TABLE and FIRST_TABLE:
select S_NAME,S_DESCRIPTION from SECOND_TABLE minus select F_NAME,F_DESCRIPTION from FIRST_TABLE;
I create a statement:
insert into FIRST_TABLE F_ID,F_NAME,F_DESCRIPTION (select * from SECOND_TABLE where not exists (select * from FIRST_TABLE where SECOND_TABLE.S_NAME = FIRST_TABLE.F_NAME));
but this statement copy F_ID, I don't have idea how to change this statement to not copy F_ID, but generate it from sequence CSS_F. It should looks like:
insert into FIRST_TABLE (F_ID) values (CSS_F.nextval);
Could you give me an advice how to add inserting genereted id to this statement?
You can select the name and description columns from the second_table and use the sequence for inserting the F_ID column.
insert into FIRST_TABLE (F_ID,F_NAME,F_DESCRIPTION)
select CSS_F.nextval, S_NAME, S_DESCRIPTION
from SECOND_TABLE S
where not exists (select * from FIRST_TABLE where S.S_NAME = F_NAME)
I want to connect query to connect 1st table 1st row and 2nd table first two column
means,
Table A,
ID Date Username Password
1 19/2/2016 XYZ ******
2 19/2/2016 ABC ******
Table B,
ID Date Username City
1 19/2/2016 XYZ NYC
2 19/2/2016 ABC LA
that when I insert some data in table A's 1st row then i want to check that data is available at table B's ID,DATE
Do you mean you want to enforce referential integrity between this two tables?
In this case you need a foreign key constraint
ALTER TABLE table_a
ADD CONSTRAINT reference_table_b_fk
FOREIGN KEY (id, date)
REFERENCES table_b (id, date);
If you want to just check before performing Insert option try this:
IF EXISTS (SELECT ID FROM TableB WHERE ID=1 AND Date='19/2/2016')
// Your either insert or not query
ELSE
// Your else logic will be here
so if you insert an entry into table1 only if an equivalent entry exists in table2, here's a little script to do that:
create table table1 (id number(2), date_t1 date, username varchar2(5), password varchar2(8));
create table table2 (id number(2), date_t1 date, username varchar2(5), city varchar2(8));
insert into table1 values (1, to_date('16.02.2016', 'dd.mm.yyyy'), 'XYZ', 'ABC123');
insert into table1 values (2, to_date('16.02.2016', 'dd.mm.yyyy'), 'ABC', 'XYZ123');
insert into table2 values (1, to_date('16.02.2016', 'dd.mm.yyyy'), 'XYZ', 'NYC');
insert into table2 values (2, to_date('16.02.2016', 'dd.mm.yyyy'), 'ABC', 'LA');
declare
n_id number(2);
d_date date;
v_username varchar2(5);
v_password varchar2(8);
n_check number(1);
begin
n_check := 0;
-- fill the variables with data which you want to insert:
n_id := 2;
d_date := to_date('16.02.2016', 'dd.mm.yyyy');
v_username := 'ABC';
v_password := 'CCCCC';
-- check whether an entry exists in table2:
begin
select count(1)
into n_check
from table2 t2
where t2.id = n_id
and trunc(t2.date_t1) = trunc(d_date);
exception when no_data_found then n_check := 0;
end;
-- if an entry exists in table2, then insert into table1:
if n_check <> 0 then
insert into table1 (id, date_t1, username, password)
values (n_id, d_date, v_username, v_password);
end if;
end;
/
select * from table1;
select * from table2;
delete from table1;
delete from table2;
I have a cursor where I need to get a whole bunch of information for an interface, but one of the tables that I need to get information from has one row for each information that I need in columns.
I looked into PIVOT and at first it seemed like it would be a mess (especially considering that I don't need an aggregate for this) but I managed to get it to work nicely; but I'm a serial learner so I still want to find out if it's possible like this:
I thought of doing this by fetching a table as a column: "type table of (object)" so I'd have an array of an array in my PL/SQL code... and it worked fine! When my properties table had only one row, but I got ORA-01427 when it had more than one.
Here's a short example code (I'm using just the IDs and addresses tables for simplicity, this is an actual little PL I created just to test this functionality):
CREATE OR REPLACE TYPE CAIB_FIELDS AS OBJECT (
ID_QUALIFIER VARCHAR2(3),
ID_NUMBER VARCHAR2(20)
)
/
CREATE TYPE CAIB_TBL AS TABLE OF CAIB_FIELDS
/
DECLARE
CURSOR MYCUR(CID IN VARCHAR2) IS
SELECT CUST_ID,CUST_ADDR,UPD_DATE
(SELECT CAIB_TBL(CAIB_FIELDS(ID_QUALIFIER,ID_NUMBER)) FROM CUSTOMER_IDS B
WHERE B.CUST_ID = A.CUST_ID
AND B.CUST_ADDR = A.CUST_ADDR) CAIB
FROM CUSTOMER_ADDR A
WHERE A.CUST_ID = CID
;
TYPE MYCUR_TYPE IS TABLE OF MYCUR%ROWTYPE;
REC_MYCUR MYCUR_TYPE;
BEGIN
OPEN MYCUR('918888'); --This customer has only one ID row -> OK! IT WORKS!
--For customer ID '002632', he has several ID ROWS -> ERROR ORA-01427
LOOP
FETCH MYCUR BULK COLLECT INTO REC_MYCUR LIMIT 100;
FOR I IN 1..REC_MYCUR.COUNT
LOOP
DBMS_OUTPUT.PUT_LINE(REC_MYCUR(I).CAIB(1).ID_QUALIFIER);
--DBMS_OUTPUT.PUT_LINE(REC_MYCUR(I).CAIB(2).ID_QUALIFIER); --this would be OK if Oracle would allow me to fetch multiple rows on my CAIB_TBL type, of course I'll just loop here if it works, but for the sake of this test, I just used fixed values..
END LOOP;
EXIT WHEN MYCUR%NOTFOUND;
END LOOP;
END;
Thanks in advance!!
--- EDIT, tbone's answer is exactly what I was looking for, but it doesn't reflect the exact scenario as it only deals with a single column table; for multiple columns the solution changes just slightly, here's my final test:
create table testA
(
col1 number,
col2 varchar2(50)
);
create table testB
(
col1 number,
col2 varchar2(50),
col3 varchar2(50)
);
insert into testA values (1,'A');
insert into testA values (2,'B');
insert into testA values (3,'C');
insert into testB values (1,'X','x');
insert into testB values (1,'Y','y');
insert into testB values (1,'Z','z');
insert into testB values (2,'BA','ba');
insert into testB values (2,'BB','bb');
commit;
CREATE OR REPLACE TYPE t_test_rec AS object
(col2 varchar2(50),
col3 varchar2(50)
)
/
create or replace type t_vchar_tab as table of t_test_rec;
DECLARE
CURSOR MYCUR IS
SELECT A.COL1,
CAST(MULTISET(SELECT B.COL2,B.COL3 FROM TESTB B WHERE B.COL1 = A.COL1 ORDER BY B.COL2) AS T_VCHAR_TAB) AS TESTB_VALS
FROM TESTA A
;
TYPE MYCUR_TYPE IS TABLE OF MYCUR%ROWTYPE;
REC_MYCUR MYCUR_TYPE;
BEGIN
OPEN MYCUR;
LOOP
FETCH MYCUR BULK COLLECT INTO REC_MYCUR LIMIT 100;
FOR I IN 1..REC_MYCUR.COUNT
LOOP
IF REC_MYCUR(I).TESTB_VALS.COUNT = 0 THEN
DBMS_OUTPUT.PUT_LINE(REC_MYCUR(I).COL1 || '->(NULL)');
ELSE
FOR J IN 1..REC_MYCUR(I).TESTB_VALS.COUNT
LOOP
DBMS_OUTPUT.PUT_LINE(REC_MYCUR(I).COL1 || '->' || REC_MYCUR(I).TESTB_VALS(J).COL2 || ',' || REC_MYCUR(I).TESTB_VALS(J).COL3);
NULL;
END LOOP;
END IF;
END LOOP;
EXIT WHEN MYCUR%NOTFOUND;
END LOOP;
END;
/
Its a bit unclear what you're trying to achieve, but based on the title (grouping multiple rows into a collection from a cursor), you can do something like this:
set echo on;
set display on;
set linesize 200;
create table testA
(
col1 number,
col2 varchar2(50)
);
create table testB
(
col1 number,
col2 varchar2(50)
);
create or replace type t_vchar_tab as table of varchar2(50);
insert into testA values (1,'A');
insert into testA values (2,'B');
insert into testB values (1,'X');
insert into testB values (1,'Y');
insert into testB values (1,'Z');
commit;
-- select all related testB.col2 values in a nested table for each testA.col1 value
select a.col1,
cast(multiset(select b.col2 from testB b where b.col1 = a.col1 order by b.col2) as t_vchar_tab) as testB_vals
from testA a;
So the output will be only the 2 rows from tableA, but have a nested table column containing all the matching rows from tableB