Outer Joins with Multiple Conditions in Oracle Join Syntax - sql

I have the tables below
create table xx_base_tbl
(
tbl_id number
, trx_num varchar2(100)
);
create table xx_dtl_tbl
(
dtl_id number
, tbl_id number
, category varchar2(100)
, attribute1 varchar2(100)
);
insert into xx_base_tbl (tbl_id, trx_num) values (1, 'trx 1');
insert into xx_base_tbl (tbl_id, trx_num) values (2, 'trx 2');
insert into xx_base_tbl (tbl_id, trx_num) values (3, 'trx 3');
insert into xx_base_tbl (tbl_id, trx_num) values (4, 'trx 4');
insert into xx_base_tbl (tbl_id, trx_num) values (5, 'trx 5');
insert into xx_dtl_tbl (dtl_id, tbl_id, category, attribute1) values (1, 1, null, 'SAMPLE');
insert into xx_dtl_tbl (dtl_id, tbl_id, category, attribute1) values (2, 1, null, 'hello');
insert into xx_dtl_tbl (dtl_id, tbl_id, category, attribute1) values (3, 2, 'PREPAYMENT', 'this is not a value');
insert into xx_dtl_tbl (dtl_id, tbl_id, category, attribute1) values (4, 2, 'PREPAYMENT', 1);
insert into xx_dtl_tbl (dtl_id, tbl_id, category, attribute1) values (5, 3, 'PREPAYMENT', 2);
insert into xx_dtl_tbl (dtl_id, tbl_id, category, attribute1) values (6, 3, 'PREPAYMENT', 1);
insert into xx_dtl_tbl (dtl_id, tbl_id, category, attribute1) values (7, 3, 'SAMPLE', 15678);
insert into xx_dtl_tbl (dtl_id, tbl_id, category, attribute1) values (8, 4, 'PREPAYMENT', 1);
insert into xx_dtl_tbl (dtl_id, tbl_id, category, attribute1) values (9, 4, 'PREPAYMENT', NULL);
insert into xx_dtl_tbl (dtl_id, tbl_id, category, attribute1) values (10, 5, 'PREPAYMENT', null);
insert into xx_dtl_tbl (dtl_id, tbl_id, category, attribute1) values (11, 5, 'SAMPLE', 'YEY');
I am using the ANSI Syntax below to outer join xx_dtl_tbl to another xx_base_tbl show only CATEGORY with PREPAYMENT and numeric values only. then use LISTAGG() to aggregate the results into a single row
SELECT
xx1.trx_num,
LISTAGG(xx3.trx_num, ',') WITHIN GROUP(
ORDER BY
xx3.trx_num
) prepayment
FROM
xx_base_tbl xx1
INNER JOIN xx_dtl_tbl xx2 ON xx1.tbl_id = xx2.tbl_id
LEFT JOIN xx_base_tbl xx3 ON (to_number(xx2.attribute1) = xx3.tbl_id and length(TRIM(translate(xx2.attribute1, ' +-.0123456789', ' '))) IS NULL)
GROUP BY
xx1.trx_num
The result looks fine:
TRX_NUM PREPAYMENT
------- -------------
trx 1
trx 2 trx 1
trx 3 trx 1,trx 2
trx 4 trx 1
trx 5
However, when i use SQL Developer's tool to change the syntax to Oracle Join Syntax, it get the below result:
SELECT
xx1.trx_num,
LISTAGG(xx3.trx_num, ',') WITHIN GROUP(
ORDER BY
xx3.trx_num
) prepayment
FROM
xx_base_tbl xx1,
xx_dtl_tbl xx2,
xx_base_tbl xx3
WHERE
xx1.tbl_id = xx2.tbl_id
AND to_number(xx2.attribute1) = xx3.tbl_id (+)
AND ( length(TRIM(translate(xx2.attribute1, ' +-.0123456789', ' '))) IS NULL )
GROUP BY
xx1.trx_num
The result changed:
TRX_NUM PREPAYMENT
------- -------------
trx 2 trx 1
trx 3 trx 1,trx 2
trx 4 trx 1
trx 5
the trx 1 row is suddenly missing. How can I write this in Oracle Syntax?

This query is not so easy to transform to old syntax. Something which worked for me is:
select a.trx_num,
listagg(b.trx_num, ',') within group (order by b.trx_num) prepayment
from (
select trx_num, attribute1,
case when trim(translate(xx2.attribute1, ' +-.0123456789', ' ')) is null
then to_number(xx2.attribute1)
end attr_num
from xx_base_tbl xx1, xx_dtl_tbl xx2
where xx1.tbl_id = xx2.tbl_id) a,
xx_base_tbl b
where a.attr_num = b.tbl_id (+)
group by a.trx_num
dbfiddle
Two steps, in first create joining column using case when in subquery, then use it in main query.
Edit:
Above query may be simplified to:
select xx1.trx_num,
listagg(xx3.trx_num, ',') within group (order by xx3.trx_num) prepayment
from xx_base_tbl xx1, xx_dtl_tbl xx2, xx_base_tbl xx3
where xx1.tbl_id = xx2.tbl_id
and case when trim(translate(xx2.attribute1, ' +-.0123456789', ' ')) is null
then to_number(xx2.attribute1)
end = xx3.tbl_id(+)
group by xx1.trx_num
dbfiddle

The syntax that is used in your first query is ANSI standard joins and properly works with oracle.
Second query's syntax is old SQL-92 standard and should be avoided. Also, it is hard to understand and handle.
By the way, your second query will need some extra clauses as follows:
WHERE
xx1.tbl_id = xx2.tbl_id
AND to_number(xx2.attribute1) = xx3.tbl_id (+)
AND (xx3.tbl_id is null -- this extra OR condition is needed with below condition
or ( length(TRIM(translate(xx2.attribute1, ' +-.0123456789', ' '))) IS NULL )
)

Related

SQL Display every class

Can someone assist me with my Oracle SQL joins, something is missing.
The select statement below lists every Student and the Class Date but I want to show every Class for each Student, even if the Student took the Class or not.
Kim, Brandy and Trina, Brandy should have every class name listed with no Class Date since they have not took any class yet.
Green, Robert took 3 Classes and did not take 2 Classes, the 2 Classes should be listed also with no Class Date since he did not take them.
The insert statements and create tables are listed. Let me know if you have any questions. Thanks
SELECT VW.STUDENT_NAME,
VW.EMPLE_NO,
CN.PK_CLASS_NAME_ID,
CN.CLASS_NAME,
DP.CLASS_DATE
FROM EMPLOYEE_NAME VW
LEFT JOIN DEMO_PRODUCT_INFO_NEW DP ON DP.FK_CO_EMPL_ID = VW.EMPLE_NO
LEFT JOIN TBL_CLASS_NAME_NEW CN
ON CN.PK_CLASS_NAME_ID = DP.FK_CLASS_NAME_ID
ORDER BY STUDENT_NAME ASC;
CREATE TABLE TBL_CLASS_NAME_NEW
(
PK_CLASS_NAME_ID INTEGER,
CLASS_NAME VARCHAR2(75 BYTE)
);
Insert into TBL_CLASS_NAME_NEW
(PK_CLASS_NAME_ID, CLASS_NAME)
Values
(1, 'CPR');
Insert into TBL_CLASS_NAME_NEW
(PK_CLASS_NAME_ID, CLASS_NAME)
Values
(3, 'ETHICS');
Insert into TBL_CLASS_NAME_NEW
(PK_CLASS_NAME_ID, CLASS_NAME)
Values
(4, 'HARRASEMENT');
Insert into TBL_CLASS_NAME_NEW
(PK_CLASS_NAME_ID, CLASS_NAME)
Values
(5, 'DEFENSIVE TEST');
Insert into TBL_CLASS_NAME_NEW
(PK_CLASS_NAME_ID, CLASS_NAME)
Values
(2, 'RANGE');
COMMIT;
CREATE TABLE DEMO_PRODUCT_INFO_NEW
(
PRODUCT_ID NUMBER NOT NULL,
FK_CO_EMPL_ID NUMBER,
FK_CLASS_NAME_ID NUMBER,
CLASS_DATE DATE
);
Insert into DEMO_PRODUCT_INFO_NEW
(PRODUCT_ID, FK_CO_EMPL_ID, FK_CLASS_NAME_ID, CLASS_DATE)
Values
(4, 4, 1, TO_DATE('7/18/2021', 'MM/DD/YYYY'));
Insert into DEMO_PRODUCT_INFO_NEW
(PRODUCT_ID, FK_CO_EMPL_ID, FK_CLASS_NAME_ID, CLASS_DATE)
Values
(22, 4, 1, TO_DATE('7/25/2021', 'MM/DD/YYYY'));
Insert into DEMO_PRODUCT_INFO_NEW
(PRODUCT_ID, FK_CO_EMPL_ID, FK_CLASS_NAME_ID, CLASS_DATE)
Values
(18, 4, 4, TO_DATE('7/18/2021', 'MM/DD/YYYY'));
Insert into DEMO_PRODUCT_INFO_NEW
(PRODUCT_ID, FK_CO_EMPL_ID, FK_CLASS_NAME_ID, CLASS_DATE)
Values
(21, 4, 3, TO_DATE('7/4/2021', 'MM/DD/YYYY'));
Insert into DEMO_PRODUCT_INFO_NEW
(PRODUCT_ID, FK_CO_EMPL_ID, FK_CLASS_NAME_ID, CLASS_DATE)
Values
(32, 22, 2, TO_DATE('8/15/2021', 'MM/DD/YYYY'));
Insert into DEMO_PRODUCT_INFO_NEW
(PRODUCT_ID, FK_CO_EMPL_ID, FK_CLASS_NAME_ID, CLASS_DATE)
Values
(34, 22, 1, TO_DATE('8/29/2021', 'MM/DD/YYYY'));
Insert into DEMO_PRODUCT_INFO_NEW
(PRODUCT_ID, FK_CO_EMPL_ID, FK_CLASS_NAME_ID, CLASS_DATE)
Values
(35, 22, 1, TO_DATE('7/4/2021', 'MM/DD/YYYY'));
Insert into DEMO_PRODUCT_INFO_NEW
(PRODUCT_ID, FK_CO_EMPL_ID, FK_CLASS_NAME_ID, CLASS_DATE)
Values
(46, 18, 4, TO_DATE('7/4/2021', 'MM/DD/YYYY'));
Insert into DEMO_PRODUCT_INFO_NEW
(PRODUCT_ID, FK_CO_EMPL_ID, FK_CLASS_NAME_ID, CLASS_DATE)
Values
(43, 18, 5, TO_DATE('7/11/2021', 'MM/DD/YYYY'));
Insert into DEMO_PRODUCT_INFO_NEW
(PRODUCT_ID, FK_CO_EMPL_ID, FK_CLASS_NAME_ID, CLASS_DATE)
Values
(45, 4, 2, TO_DATE('7/4/2021', 'MM/DD/YYYY'));
Insert into DEMO_PRODUCT_INFO_NEW
(PRODUCT_ID, FK_CO_EMPL_ID, FK_CLASS_NAME_ID, CLASS_DATE)
Values
(48, 4, 5, TO_DATE('7/11/2021', 'MM/DD/YYYY'));
Insert into DEMO_PRODUCT_INFO_NEW
(PRODUCT_ID, FK_CO_EMPL_ID, FK_CLASS_NAME_ID, CLASS_DATE)
Values
(51, 22, 3, TO_DATE('7/4/2021', 'MM/DD/YYYY'));
Insert into DEMO_PRODUCT_INFO_NEW
(PRODUCT_ID, FK_CO_EMPL_ID, FK_CLASS_NAME_ID, CLASS_DATE)
Values
(52, 18, 3, TO_DATE('7/18/2021', 'MM/DD/YYYY'));
Insert into DEMO_PRODUCT_INFO_NEW
(PRODUCT_ID, FK_CO_EMPL_ID, FK_CLASS_NAME_ID, CLASS_DATE)
Values
(5, 4, 2, TO_DATE('7/25/2021', 'MM/DD/YYYY'));
COMMIT;
CREATE TABLE EMPLOYEE_NAME
(
EMPLE_NO INTEGER,
STUDENT_NAME VARCHAR2 (100 BYTE),
LAST_NAME VARCHAR2 (40 BYTE),
FIRST_NAME VARCHAR2 (40 BYTE)
);
Insert into EMPLOYEE_NAME
(EMPLE_NO, STUDENT_NAME, LAST_NAME, FIRST_NAME)
Values
(4, 'WENDY, FRANK', 'FRANK', 'WENDY');
Insert into EMPLOYEE_NAME
(EMPLE_NO, STUDENT_NAME, LAST_NAME, FIRST_NAME)
Values
(22, 'JOHN, JAMES', 'JAMES', 'JOHN');
Insert into EMPLOYEE_NAME
(EMPLE_NO, STUDENT_NAME, LAST_NAME, FIRST_NAME)
Values
(18, 'GREEN, ROBERT', 'ROBERT', 'GREEN');
Insert into EMPLOYEE_NAME
(EMPLE_NO, STUDENT_NAME, LAST_NAME, FIRST_NAME)
Values
(21, 'KIM, BRANDY', 'BRANDY', 'KIM');
Insert into EMPLOYEE_NAME
(EMPLE_NO, STUDENT_NAME, LAST_NAME, FIRST_NAME)
Values
(32, 'TRINA, JAMIE', 'JAMIE', 'TRINA');
This is how the following query works
T1: Lists all the courses and students that the student can choose.
T2: Lists all the courses and students that the student has chosen
Then T1 goes to T2 LEFT JOIN with equal number of PK_CLASS_NAME_ID and EMPLE_NO
SELECT
T1.STUDENT_NAME,
T1.EMPLE_NO,
T1.PK_CLASS_NAME_ID,
T1.CLASS_NAME,
T2.CLASS_DATE
FROM
(
SELECT VW.STUDENT_NAME,
VW.EMPLE_NO,
CN.PK_CLASS_NAME_ID,
CN.CLASS_NAME
FROM TBL_CLASS_NAME_NEW CN
JOIN EMPLOYEE_NAME VW
ON 1=1
GROUP BY VW.STUDENT_NAME,VW.EMPLE_NO, CN.PK_CLASS_NAME_ID, CN.CLASS_NAME
) T1
LEFT JOIN
(
SELECT VW.STUDENT_NAME,
VW.EMPLE_NO,
CN.PK_CLASS_NAME_ID,
CN.CLASS_NAME,
DP.CLASS_DATE
FROM TBL_CLASS_NAME_NEW CN
JOIN DEMO_PRODUCT_INFO_NEW DP
ON CN.PK_CLASS_NAME_ID = DP.FK_CLASS_NAME_ID
JOIN EMPLOYEE_NAME VW
ON DP.FK_CO_EMPL_ID = VW.EMPLE_NO
GROUP BY VW.STUDENT_NAME,VW.EMPLE_NO,CN.PK_CLASS_NAME_ID, CN.CLASS_NAME,DP.CLASS_DATE
) T2 ON T1.EMPLE_NO = T2.EMPLE_NO ANd T1.PK_CLASS_NAME_ID = T2.PK_CLASS_NAME_ID
I got the output in SQL, but it doesn't matter, it works in Oracle.

Can I improve this query for use in large tables?

How can I improve this query for use in large tables....?
I use a table ('DataValues') to store a collection of values ('Value') for collections ('Visit_id') ie it records certain values for each visit.
I use a table ('MatchItems') to store dynamic match sets 'MatchSet' of values ('Value'), sets can contain any number of values. The table also has a IsNeg field to indicate if the match should require a value to be not present in the visit collection.
This allows me to dynamically match visits that conform to certain criteria such as
Must contain values A, B and C and NOT D OR C and B AND NOT A.
ie (Value = A and Value = B and Value = C and Value /= D)
or (Value = C and Value = B and Value /= A)
I have a query that delivers a reasonable solution fiddle:
CREATE TABLE DataValues (
id NUMBER(5) CONSTRAINT DataValues_pk PRIMARY KEY,
Visit_id Number(5) ,
Value varchar(5)
);
INSERT INTO DataValues VALUES (1, 1, 'M');
INSERT INTO DataValues VALUES (2, 1, 'I');
INSERT INTO DataValues VALUES (3, 1, 'C');
INSERT INTO DataValues VALUES (4, 1, 'K');
INSERT INTO DataValues VALUES (5, 1, 'E');
INSERT INTO DataValues VALUES (6, 1, 'Y');
INSERT INTO DataValues VALUES (7, 2, 'M');
INSERT INTO DataValues VALUES (8, 2, 'O');
INSERT INTO DataValues VALUES (9, 2, 'U');
INSERT INTO DataValues VALUES (10, 2, 'S');
INSERT INTO DataValues VALUES (11, 2, 'E');
INSERT INTO DataValues VALUES (12, 3, 'C');
INSERT INTO DataValues VALUES (13, 3, 'A');
INSERT INTO DataValues VALUES (14, 3, 'T');
INSERT INTO DataValues VALUES (15, 4, 'S');
INSERT INTO DataValues VALUES (16, 4, 'A');
INSERT INTO DataValues VALUES (17, 4, 'T');
INSERT INTO DataValues VALUES (18, 5, 'M');
INSERT INTO DataValues VALUES (19, 5, 'A');
INSERT INTO DataValues VALUES (20, 5, 'T');
CREATE TABLE MatchItems (
id NUMBER(5) CONSTRAINT MatchItems_pk PRIMARY KEY,
MatchSet Number(5),
Value VARCHAR(5),
IsNeg NUMBER(1) NOT NULL CHECK (IsNeg in (0,1))
);
INSERT INTO MatchItems VALUES (1, 1, 'M', 0);
INSERT INTO MatchItems VALUES (2, 1, 'I', 0);
INSERT INTO MatchItems VALUES (3, 1, 'C', 0);
INSERT INTO MatchItems VALUES (4, 1, 'K', 0);
INSERT INTO MatchItems VALUES (5, 1, 'E', 0);
INSERT INTO MatchItems VALUES (6, 1, 'Y', 0);
INSERT INTO MatchItems VALUES (7, 2, 'C', 0);
INSERT INTO MatchItems VALUES (8, 2, 'A', 0);
INSERT INTO MatchItems VALUES (9, 3, 'A', 0);
INSERT INTO MatchItems VALUES (10, 3, 'T', 0);
INSERT INTO MatchItems VALUES (11, 4, 'S', 1);
INSERT INTO MatchItems VALUES (12, 4, 'A', 0);
INSERT INTO MatchItems VALUES (13, 4, 'K', 1);
INSERT INTO MatchItems VALUES (14, 5, 'A', 0);
INSERT INTO MatchItems VALUES (15, 5, 'T', 0);
SELECT
MatchItems.MatchSet,
DataValues.Visit_id,
GpMatchItems.Count TgtCount,
Count(MatchItems.Id),
sum(MatchItems.IsNeg)
FROM DataValues
LEFT JOIN MatchItems ON MatchItems.Value = DataValues.Value
--AND MatchItems.MatchSet = 4
LEFT JOIN (SELECT
MatchItems.MatchSet,
count(*) Count
FROM MatchItems
WHERE
MatchItems.IsNeg = 0
GROUP BY
MatchItems.MatchSet) GpMatchItems ON GpMatchItems.MatchSet = MatchItems.MatchSet
HAVING
Count(MatchItems.Id) = GpMatchItems.Count
AND sum(MatchItems.IsNeg) = 0
GROUP BY
MatchItems.MatchSet,
DataValues.Visit_id,
GpMatchItems.Count
How can I improve the performance of this query where the DataValues table contains 100m records, and MatchItems may include a collection of 50 sets each of 2 - 20 values?
You can try this version using Analytic functions and see if it performs any better. This query removes the subquery GpMatchItems that you are joining with.
SELECT DISTINCT matchset,
visit_id,
tgtcount,
match_visit_count,
isneg_sum
FROM (SELECT MatchItems.MatchSet,
DataValues.Visit_id,
COUNT (DISTINCT CASE MatchItems.IsNeg WHEN 0 THEN MatchItems.id ELSE NULL END)
OVER (PARTITION BY MatchItems.MatchSet)
AS tgtcount,
COUNT (*) OVER (PARTITION BY MatchItems.MatchSet, DataValues.Visit_id)
AS match_visit_count,
SUM (MatchItems.IsNeg) OVER (PARTITION BY MatchItems.MatchSet, DataValues.Visit_id)
AS isneg_sum
FROM DataValues LEFT JOIN MatchItems ON MatchItems.VALUE = DataValues.VALUE)
WHERE tgtcount = match_visit_count AND isneg_sum = 0;
I have adjusted EJ's suggestion to include a LEFT JOIN to collect the tgtCount to identify the total number of good matches required in each MatchSet:
SELECT DISTINCT matchset,
visit_id,
tgtcount,
match_visit_count,
isneg_sum
GpMatchItems.count tgtCount
FROM
COUNT (*) OVER (PARTITION BY MatchItems.MatchSet, DataValues.Visit_id)
AS match_visit_count,
SUM (MatchItems.IsNeg) OVER (PARTITION BY MatchItems.MatchSet, DataValues.Visit_id)
AS isneg_sum
FROM DataValues
LEFT JOIN MatchItems ON MatchItems.VALUE = DataValues.VALUE)
LEFT JOIN ( SELECT
MatchItems.MatchSet,
count(*) Count
FROM MatchItems
WHERE MatchItems.IsNeg = 0
GROUP BY
MatchItems.MatchSet) GpMatchItems
ON GpMatchItems.MatchSet = MatchItems.MatchSet
)
WHERE
tgtcount = match_visit_count
AND isneg_sum = 0;

SQL - find exact group of records in large table

I have following data:
ID --- GRP_ID --- REC_VAL
1 --- 1 --- A
2 --- 2 --- A
3 --- 2 --- B
4 --- 3 --- A
5 --- 3 --- B
6 --- 3 --- C
7 --- 4 --- A
8 --- 4 --- B
9 --- 4 --- C
10 --- 5 --- A
11 --- 5 --- B
12 --- 5 --- E
Is there a way how to find id of record groups that have same values ? (in this case only grp_id 3 and 4 have same values)
Second question:
Is there effecient way how to find exact grp_id when i had a set of values ? My solution is not very quick because table with groups has over 6mil. records:
-- Large table - up to 6m records
create table tmp_grp (id number, grp_id number, rec_val varchar2(10));
--
insert into tmp_grp(id, grp_id, rec_val) values (1, 1, 'A');
insert into tmp_grp(id, grp_id, rec_val) values (2, 2, 'A');
insert into tmp_grp(id, grp_id, rec_val) values (3, 2, 'B');
insert into tmp_grp(id, grp_id, rec_val) values (4, 3, 'A');
insert into tmp_grp(id, grp_id, rec_val) values (5, 3, 'B');
insert into tmp_grp(id, grp_id, rec_val) values (6, 3, 'C');
insert into tmp_grp(id, grp_id, rec_val) values (7, 4, 'A');
insert into tmp_grp(id, grp_id, rec_val) values (8, 4, 'B');
insert into tmp_grp(id, grp_id, rec_val) values (9, 4, 'C');
insert into tmp_grp(id, grp_id, rec_val) values (10, 5, 'A');
insert into tmp_grp(id, grp_id, rec_val) values (11, 5, 'B');
insert into tmp_grp(id, grp_id, rec_val) values (12, 5, 'E');
commit;
--
-- CTE representing record group for asking
WITH datrec AS
(SELECT 'A' rec FROM dual UNION ALL
SELECT 'B' rec FROM dual)
--
SELECT x.grp_id
FROM (
-- Count of joined records
SELECT COUNT(1) cnt, t.grp_id
FROM tmp_grp t
JOIN datrec d
ON d.rec = t.rec_val
GROUP BY t.grp_id
--
) x
WHERE
-- Count of all data records
x.cnt = (SELECT COUNT(1) FROM datrec)
-- Count of all group records
AND x.cnt = (SELECT COUNT(1) FROM tmp_grp g WHERE x.grp_id = g.grp_id);
--
This question is similar to Find group of records that match multiple values , but this topic only cover exact set of values (number of values and values in column rec of datrec will be provided by another query) and query return groups which contains this set. I need to return only exact match.
UPDATE
- added data in table for better clarification
Also related to How to compare groups of tuples in sql
Here is a way that avoids joining the base table to itself. It will be more efficient especially if there are several (many?) possible values of rec_val for each grp_id. It can be made faster still if the distinct grp_id already exist somewhere in your data; I create them on the fly.
with gid ( grp_id ) as (
select distinct grp_id from tmp_grp
),
prep ( grp_id_1, grp_id_2, rec_val ) as (
select t.grp_id, g.grp_id, t.rec_val
from tmp_grp t join gid g on t.grp_id < g.grp_id
union all
select g.grp_id, t.grp_id, t.rec_val
from gid g join tmp_grp t on g.grp_id < t.grp_id
),
counts ( grp_id_1, grp_id_2, cnt ) as (
select grp_id_1, grp_id_2, count(*)
from prep
group by grp_id_1, grp_id_2, rec_val
)
select grp_id_1, grp_id_2
from counts
group by grp_id_1, grp_id_2
having min(cnt) = 2
;

How to select data from joined table as a table type?

I'd like to build de-normalised view that handles reference data in table type.
create table reftab (id number, name varchar2(40), details varchar2(1000));
/
create table basictab (id number, name varchar2(300), contract varchar2 (20));
/
create or replace type reftype is object (id number, name varchar2(40), details varchar2(1000));
/
create or replace type reftypetab as table of reftype;
/
insert into basictab values (1, 'aaa', 'c1');
insert into basictab values (2, 'aab', 'c1');
insert into basictab values (3, 'aaa', 'c2');
insert into basictab values (4, 'aaa', 'c3');
insert into reftab values (1, 'asd', 'aaa');
insert into reftab values (1, 'asg', 'ass');
insert into reftab values (1, 'ash', 'add');
insert into reftab values (1, 'asf', 'agg');
insert into reftab values (3, 'asd', 'aaa');
insert into reftab values (3, 'ad', 'aa');
insert into reftab values (4, 'asd', 'aaa');
insert into reftab values (4, 'as', 'a');
insert into values (4, 'ad', 'aa');
/
With such data I'd like to have view that contains 4 rows of basictab with additional column that is reftypetab and contains all ref data joined on id.
I know I can obtain it by:
CREATE OR REPLACE FUNCTION pipef (p_id IN NUMBER) RETURN reftypetab PIPELINED AS
BEGIN
FOR x IN (select * from reftab where id = p_id) LOOP
PIPE ROW(reftype(x.id, x.name, x.details));
END LOOP;
RETURN;
END;
/
SELECT id, pipef(id)
FROM reftab
group BY id;
/
but is there any better way without function to get the result?
Your current set-up gets:
SELECT id, pipef(id) as result
FROM reftab
group BY id;
ID RESULT(ID, NAME, DETAILS)
---------- ------------------------------------------------------------------------------------------------------------------------
1 REFTYPETAB(REFTYPE(1, 'asd', 'aaa'), REFTYPE(1, 'asg', 'ass'), REFTYPE(1, 'ash', 'add'), REFTYPE(1, 'asf', 'agg'))
4 REFTYPETAB(REFTYPE(4, 'asd', 'aaa'), REFTYPE(4, 'as', 'a'), REFTYPE(4, 'ad', 'aa'))
3 REFTYPETAB(REFTYPE(3, 'asd', 'aaa'), REFTYPE(3, 'ad', 'aa'))
You could use the collect() function to simplify that:
select id, cast(collect(reftype(id, name, details)) as reftypetab) as result
from reftab
group by id;
ID RESULT(ID, NAME, DETAILS)
---------- ------------------------------------------------------------------------------------------------------------------------
1 REFTYPETAB(REFTYPE(1, 'asd', 'aaa'), REFTYPE(1, 'asf', 'agg'), REFTYPE(1, 'ash', 'add'), REFTYPE(1, 'asg', 'ass'))
3 REFTYPETAB(REFTYPE(3, 'asd', 'aaa'), REFTYPE(3, 'ad', 'aa'))
4 REFTYPETAB(REFTYPE(4, 'asd', 'aaa'), REFTYPE(4, 'ad', 'aa'), REFTYPE(4, 'as', 'a'))
If you want information from basictab as well you can use a multiset operator:
select bt.id, bt.name,
cast(multiset(select reftype(rt.id, rt.name, rt.details)
from reftab rt where rt.id = bt.id) as reftypetab) as result
from basictab bt;
ID NAME RESULT(ID, NAME, DETAILS)
---------- ---------- ------------------------------------------------------------------------------------------------------------------------
1 aaa REFTYPETAB(REFTYPE(1, 'asd', 'aaa'), REFTYPE(1, 'asg', 'ass'), REFTYPE(1, 'ash', 'add'), REFTYPE(1, 'asf', 'agg'))
2 aab REFTYPETAB()
3 aaa REFTYPETAB(REFTYPE(3, 'asd', 'aaa'), REFTYPE(3, 'ad', 'aa'))
4 aaa REFTYPETAB(REFTYPE(4, 'asd', 'aaa'), REFTYPE(4, 'as', 'a'), REFTYPE(4, 'ad', 'aa'))
Further research gave me another way to do that:
SELECT id, CAST(COLLECT(reftype(r.id, r.name, r.details)) AS reftypetab) AS customer_ids
FROM reftab r group by id;
This looks much better but still I would ask if there are any opther ways to do that.
EDIT
Maybe that's not exactly what I was asking about but cursor expresion can give similar result.
SELECT id, cursor(select reftype(r.id, r.name, r.details) from reftab r where r.id = b.id) AS customer_ids
FROM basictab b;

SQL Split String to letters each one in row

I have case to get String from table , Split it to letters , each letter represent "active/inactive" "0/1" , then return description from other table for just all active letter IN ONE CELL ..
NOTE: Letters order as it is in other table ..
this is my case :
CREATE TABLE Strings_tab
(
Str_id NUMBER,
Str_text VARCHAR2 (40)
);
CREATE TABLE Reprsnt_Tab
(
rep_id NUMBER,
rep_text VARCHAR2 (40)
);
INSERT INTO Strings_tab VALUES (1, '1111111111000000000011111111110000000000');
INSERT INTO Strings_tab VALUES (2, '0000011111000001111100000111110000011111');
INSERT INTO Reprsnt_Tab VALUES (1, 'rep1');
INSERT INTO Reprsnt_Tab VALUES (2, 'rep2');
INSERT INTO Reprsnt_Tab VALUES (3, 'rep3');
INSERT INTO Reprsnt_Tab VALUES (4, 'rep4');
INSERT INTO Reprsnt_Tab VALUES (5, 'rep5');
INSERT INTO Reprsnt_Tab VALUES (6, 'rep6');
INSERT INTO Reprsnt_Tab VALUES (7, 'rep7');
INSERT INTO Reprsnt_Tab VALUES (8, 'rep8');
INSERT INTO Reprsnt_Tab VALUES (9, 'rep9');
INSERT INTO Reprsnt_Tab VALUES (10, 'rep10');
INSERT INTO Reprsnt_Tab VALUES (11, 'rep11');
INSERT INTO Reprsnt_Tab VALUES (12, 'rep12');
INSERT INTO Reprsnt_Tab VALUES (13, 'rep13');
INSERT INTO Reprsnt_Tab VALUES (14, 'rep14');
INSERT INTO Reprsnt_Tab VALUES (15, 'rep15');
INSERT INTO Reprsnt_Tab VALUES (16, 'rep16');
INSERT INTO Reprsnt_Tab VALUES (17, 'rep17');
INSERT INTO Reprsnt_Tab VALUES (18, 'rep18');
INSERT INTO Reprsnt_Tab VALUES (19, 'rep19');
INSERT INTO Reprsnt_Tab VALUES (20, 'rep20');
INSERT INTO Reprsnt_Tab VALUES (21, 'rep21');
INSERT INTO Reprsnt_Tab VALUES (22, 'rep22');
INSERT INTO Reprsnt_Tab VALUES (23, 'rep23');
INSERT INTO Reprsnt_Tab VALUES (24, 'rep24');
INSERT INTO Reprsnt_Tab VALUES (25, 'rep25');
INSERT INTO Reprsnt_Tab VALUES (26, 'rep26');
INSERT INTO Reprsnt_Tab VALUES (27, 'rep27');
INSERT INTO Reprsnt_Tab VALUES (28, 'rep28');
INSERT INTO Reprsnt_Tab VALUES (29, 'rep29');
INSERT INTO Reprsnt_Tab VALUES (30, 'rep30');
INSERT INTO Reprsnt_Tab VALUES (31, 'rep31');
INSERT INTO Reprsnt_Tab VALUES (32, 'rep32');
INSERT INTO Reprsnt_Tab VALUES (33, 'rep33');
INSERT INTO Reprsnt_Tab VALUES (34, 'rep34');
INSERT INTO Reprsnt_Tab VALUES (35, 'rep35');
INSERT INTO Reprsnt_Tab VALUES (36, 'rep36');
INSERT INTO Reprsnt_Tab VALUES (37, 'rep37');
INSERT INTO Reprsnt_Tab VALUES (38, 'rep38');
INSERT INTO Reprsnt_Tab VALUES (39, 'rep39');
INSERT INTO Reprsnt_Tab VALUES (40, 'rep40');
COMMIT;
this is my query :
SELECT STR_TEXT,
RTRIM (
XMLAGG (XMLELEMENT (E, DATA.REP_TEXT || ',' || CHR (10))).EXTRACT (
'//text()'),
',')
REPS
FROM ( SELECT LETTER,
STR_ID,
LVL,
STR_TEXT,
REP_TEXT
FROM ( SELECT DISTINCT SUBSTR (A.STR_TEXT, LEVEL, 1) LETTER,
A.STR_ID,
LEVEL LVL,
A.STR_TEXT
FROM STRINGS_TAB A
CONNECT BY LEVEL <= LENGTH (A.STR_TEXT) ---- HERE IS MY PROBLEM
) TXT,
( SELECT ROWNUM RN, REPRSNT_TAB.*
FROM REPRSNT_TAB
ORDER BY REP_ID) B
WHERE B.RN = TXT.LVL AND LETTER = 1
ORDER BY STR_ID, STR_TEXT, LVL) DATA
GROUP BY STR_TEXT
This query get correct data
If i put like 10 in place of "LENGTH (A.STR_TEXT)" to get first 10 letters of each string.
BUT .. If "LENGTH (A.STR_TEXT)" was so big , in my case 40 , the query will hang
so, please , advice me .. in this case ???
RESULT from my Query , IF I put "LEVEL <= 10" :
STR_TEXT REP
------------------------------- ------------------------------
000001111100000111110000011111 rep6,rep10,rep9,rep8,rep7
111111111100000000001111111111 rep1,rep10,rep9,rep8,rep7,rep6,rep5,rep4,rep3,rep2
My approach takes the CONNECT BY to an auxiliary table (using CONNECT BY LEVEL in a table with more than 1 row is not a good idea IMHO. Look here) :
with aux as (select level as lvl
from dual
connect by level <= (select max(length(str_text)) from strings_tab))
SELECT STR_TEXT,
RTRIM (
XMLAGG (XMLELEMENT (E, DATA.REP_TEXT || ',' || CHR (10))).EXTRACT (
'//text()'),
',')
REPS
FROM ( SELECT LETTER,
STR_ID,
LVL,
STR_TEXT,
REP_TEXT
FROM ( SELECT DISTINCT SUBSTR (A.STR_TEXT, LVL, 1) LETTER,
A.STR_ID,
LVL,
A.STR_TEXT
FROM STRINGS_TAB A join aux x on x.lvl <=LENGTH (A.STR_TEXT)
) TXT,
( SELECT ROWNUM RN, REPRSNT_TAB.*
FROM REPRSNT_TAB
ORDER BY REP_ID) B
WHERE B.RN = TXT.LVL AND LETTER = 1
ORDER BY STR_ID, STR_TEXT, LVL) DATA
GROUP BY STR_TEXT;
OUTPUT (WITHOUT (CHAR(10))
STR_TEXT REPS
0000011111000001111100000111110000011111 rep6,rep40,rep39,rep38,rep37,rep36,rep30,rep29,rep28,rep27,rep26,rep20,rep19,rep18,rep17,rep16,rep10,rep9,rep8,rep7
1111111111000000000011111111110000000000 rep1,rep30,rep29,rep28,rep27,rep26,rep25,rep24,rep23,rep22,rep21,rep10,rep9,rep8,rep7,rep6,rep5,rep4,rep3,rep2
This is My Answer :
SELECT str_text,
LISTAGG (DATA.REP_TEXT, ',' || CHR (10))
WITHIN GROUP (ORDER BY DATA.rn_rep)
reps
FROM (SELECT *
FROM ( SELECT str_text, SUBSTR (str_text, rn, 1) OneDigit, rn
FROM (SELECT str_text FROM STRINGS_TAB) txt,
( SELECT ROWNUM rn
FROM DUAL
CONNECT BY LEVEL <=
(SELECT MAX (LENGTH (str_text))
FROM STRINGS_TAB)) rep
WHERE REP.RN <= LENGTH(str_text)
ORDER BY Str_text, rn) xx,
( SELECT ROWNUM rn_rep, rep_text
FROM REPRSNT_TAB
ORDER BY rn_rep) desc_rep
WHERE desc_rep.rn_rep = xx.rn AND OneDigit = 1) data
GROUP BY str_text
select Str_id
,Str_text
,xmlquery('let $abc := for $i in 1 to string-length($str)
where substring($str, $i, 1) = "1"
return $data/rows/row[$i]/text()
return string-join( $abc, ",")'
passing Str_text as "str"
, (select xmlelement("rows",xmlagg(xmlelement( "row",rep_text) order by rep_id)) from Reprsnt_Tab) as "data"
returning content
) from Strings_tab
The table Reprsnt_Tab is aggregate to one xmlelemtnt. To access selected row you have to just do $doc/rows/row[$selected_row_nr]/text()