Oracle SQL query to fetch manyToMany record in a single column - sql

I have the following three tables, my requirement is to fetch manytoMany joined tabled as a column. Can someone pls help me in writing the query.
IPtype can only be of two types public and private, so the result should have two more columns as mentioned below. If there is multiple public or private ip mapped to single asset then is should be displayed as comma separated.
Thanks

Looks like outer join (to return assets that don't have any IP addresses) with listagg (to aggregate IP addresses per asset) problem.
SQL> with
2 -- Sample data
3 asset (assetid) as
4 (select 1 from dual union all
5 select 2 from dual union all
6 select 3 from dual union all
7 select 4 from dual
8 ),
9 ip (ipid, ipnumber, iptype) as
10 (select 1, '1.2.3.4' , 'Public' from dual union all
11 select 2, '99.22.3.4', 'Private' from dual union all
12 select 3, '11.22.3.4', 'Public' from dual union all
13 select 4, '55.22.3.4', 'Public' from dual union all
14 select 5, '66.22.3.4', 'Private' from dual union all
15 select 6, '77.22.3.4', 'Private' from dual
16 ),
17 asset_ip_map (assetid, ipid) as
18 (select 1, 1 from dual union all
19 select 1, 2 from dual union all
20 select 2, 3 from dual union all
21 select 2, 4 from dual union all
22 select 3, 5 from dual union all
23 select 3, 6 from dual
24 )
25 -- Query you need
26 select a.assetid,
27 listagg(case when iptype = 'Public' then i.ipnumber end, ', ') within group (order by null) public_ip,
28 listagg(case when iptype = 'Private' then i.ipnumber end, ', ') within group (order by null) private_ip
29 from asset a left join asset_ip_map m on m.assetid = a.assetid
30 left join ip i on i.ipid = m.ipid
31 group by a.assetid
32 order by a.assetid;
ASSETID PUBLIC_IP PRIVATE_IP
---------- ------------------------------ ------------------------------
1 1.2.3.4 99.22.3.4
2 11.22.3.4, 55.22.3.4
3 66.22.3.4, 77.22.3.4
4
SQL>

Related

Cross SQL joins through multiple tables

I have data in multiple tables I need to cross join via different attributes to achieve the output. i.e.
ID
Node
1
A
2
B
3
C
4
D
5
E
6
G
ID
ParentID
1
100
2
200
3
300
4
400
5
500
6
600
7
100
8
200
9
300
10
700
11
800
12
800
ID
Splice Name
7
Irvine
8
Goodyear
9
Phoenix
10
Seattle
11
Augusta
12
Atlanta
Every table has a unique Child ID which corresponds to a Parent ID that is NOT unique. Multiple child IDs can be associated to the same Parent ID (as you can see above, child ID 1 and 7 for instance share Parent ID 100. The ID in each table corresponds with a unique Child ID not shared with any other tables.
What I want to do in my query is have the Nodes show what their Splice Name is. So I want to do select Node, Splice_Name. Expected output for instance would show Node A as having Splice Name Irvine. The example above is only 3 tables and a few rows but I'm working with big data (500 tables and over a million rows)
My question is, how do I write my query to do multiple cross joins? Also my example above is crossing over through one table to get data from another, but how would you cross through multiple tables?
You do not want to CROSS JOIN. Instead, you can use INNER JOINs:
SELECT n.node, s.splice_name
FROM (
relationships rn
INNER JOIN nodes n
ON (rn.id = n.id)
)
INNER JOIN
(
relationships rs
INNER JOIN splices s
ON (rs.id = s.id)
)
ON (rn.parentid = rs.parentid);
Note: The braces are not required here, I just find they add clarity to the precedence of the joins.
Which, for the sample data:
CREATE TABLE nodes (ID, Node) As
SELECT 1, 'A' FROM DUAL UNION ALL
SELECT 2, 'B' FROM DUAL UNION ALL
SELECT 3, 'C' FROM DUAL UNION ALL
SELECT 4, 'D' FROM DUAL UNION ALL
SELECT 5, 'E' FROM DUAL UNION ALL
SELECT 6, 'G' FROM DUAL;
CREATE TABLE relationships (ID, ParentID) AS
SELECT 1, 100 FROM DUAL UNION ALL
SELECT 2, 200 FROM DUAL UNION ALL
SELECT 3, 300 FROM DUAL UNION ALL
SELECT 4, 400 FROM DUAL UNION ALL
SELECT 5, 500 FROM DUAL UNION ALL
SELECT 6, 600 FROM DUAL UNION ALL
SELECT 7, 100 FROM DUAL UNION ALL
SELECT 8, 200 FROM DUAL UNION ALL
SELECT 9, 300 FROM DUAL UNION ALL
SELECT 10, 700 FROM DUAL UNION ALL
SELECT 11, 800 FROM DUAL UNION ALL
SELECT 12, 800 FROM DUAL;
CREATE TABLE splices (ID, Splice_Name) AS
SELECT 7, 'Irvine' FROM DUAL UNION ALL
SELECT 8, 'Goodyear' FROM DUAL UNION ALL
SELECT 9, 'Phoenix' FROM DUAL UNION ALL
SELECT 10, 'Seattle' FROM DUAL UNION ALL
SELECT 11, 'Augusta' FROM DUAL UNION ALL
SELECT 12, 'Atlanta' FROM DUAL;
Outputs:
NODE
SPLICE_NAME
A
Irvine
B
Goodyear
C
Phoenix
If you want all the nodes and splices, including those without a corresponding relationship then use a FULL OUTER JOIN:
SELECT n.node, s.splice_name
FROM (
relationships rn
INNER JOIN nodes n
ON (rn.id = n.id)
)
FULL OUTER JOIN
(
relationships rs
INNER JOIN splices s
ON (rs.id = s.id)
)
ON (rn.parentid = rs.parentid)
Which outputs:
NODE
SPLICE_NAME
A
Irvine
B
Goodyear
C
Phoenix
(null)
Seattle
(null)
Augusta
(null)
Atlanta
D
(null)
G
(null)
E
(null)
fiddle
Maybe you could consider the solution based on Left Joins.
WITH
nodes AS
(
Select 1 "ID", 'A' "NODE" From Dual Union All
Select 2 "ID", 'B' "NODE" From Dual Union All
Select 3 "ID", 'C' "NODE" From Dual Union All
Select 4 "ID", 'D' "NODE" From Dual Union All
Select 5 "ID", 'E' "NODE" From Dual Union All
Select 6 "ID", 'F' "NODE" From Dual
),
parents AS
(
Select 1 "ID", 100 "PARENT_ID" From Dual Union All
Select 2 "ID", 200 "PARENT_ID" From Dual Union All
Select 3 "ID", 300 "PARENT_ID" From Dual Union All
Select 4 "ID", 400 "PARENT_ID" From Dual Union All
Select 5 "ID", 500 "PARENT_ID" From Dual Union All
Select 6 "ID", 600 "PARENT_ID" From Dual Union All
Select 7 "ID", 100 "PARENT_ID" From Dual Union All
Select 8 "ID", 200 "PARENT_ID" From Dual Union All
Select 9 "ID", 300 "PARENT_ID" From Dual Union All
Select 10 "ID", 700 "PARENT_ID" From Dual Union All
Select 11 "ID", 800 "PARENT_ID" From Dual Union All
Select 12 "ID", 800 "PARENT_ID" From Dual
),
splices AS
(
Select 7 "ID", 'Irwine' "SPLICE_NAME" From Dual Union All
Select 8 "ID", 'Goodyear' "SPLICE_NAME" From Dual Union All
Select 9 "ID", 'Phoenix' "SPLICE_NAME" From Dual Union All
Select 10 "ID", 'Seattle' "SPLICE_NAME" From Dual Union All
Select 11 "ID", 'Augusta' "SPLICE_NAME" From Dual Union All
Select 12 "ID", 'Atlanta' "SPLICE_NAME" From Dual
)
Select
p.PARENT_ID,
n.ID "NODE_ID", n.NODE,
p.SPLICE_ID, p.ID "ID",
CASE WHEN p.SPLICE_ID Is Null Then p.SPLICE_NAME
ELSE (Select SPLICE_NAME From splices Where ID = p.SPLICE_ID)
END "SPLICE_NAME"
From
(
Select
p.PARENT_ID, p.ID "ID",
p.SPLICE_ID "SPLICE_ID", p.SPLICE_NAME "SPLICE_NM", Max(p.SPLICE_NAME) OVER(Partition By PARENT_ID) "SPLICE_NAME"
From
(
Select p1.PARENT_ID, p1.ID, s1.ID "SPLICE_ID", s1.SPLICE_NAME
From parents p1
Left Join splices s1 ON(s1.ID = p1.ID)
)p
) p
Left Join nodes n ON(n.ID = p.ID)
Order By p.ID
/*
PARENT_ID NODE_ID NODE SPLICE_ID ID SPLICE_NAME
---------- ---------- ---- ---------- ---------- -----------
100 1 A 1 Irwine
200 2 B 2 Goodyear
300 3 C 3 Phoenix
400 4 D 4
500 5 E 5
600 6 F 6
100 7 7 Irwine
200 8 8 Goodyear
300 9 9 Phoenix
700 10 10 Seattle
800 11 11 Augusta
800 12 12 Atlanta
*/
The resulting dataset could be seelected/filtered/grouped/ordered or whatever you want...
Additionaly:
if there is another table with node data like here:
nodes_2 AS
(
Select 7 "ID", 'G' "NODE" From Dual Union All
Select 8 "ID", 'H' "NODE" From Dual
),
... then just change last Left Join to union of nodes and nodes_2 (_3, _4, _n). The same could be done for splices (Left Join in inner query).. and the result would be:
PARENT_ID NODE_ID NODE SPLICE_ID ID SPLICE_NAME
---------- ---------- ---- ---------- ---------- -----------
100 1 A 1 Irwine
200 2 B 2 Goodyear
300 3 C 3 Phoenix
400 4 D 4
500 5 E 5
600 6 F 6
100 7 G 7 7 Irwine
200 8 H 8 8 Goodyear
300 9 9 Phoenix
700 10 10 Seattle
800 11 11 Augusta
800 12 12 Atlanta

ORACLE SQL | If a column contains a value, then it will exclude a different value from the same column

I have this query that returns the data below it
select LISTAGG(d.DOCUMENT_TYPE_CD, ',') WITHIN GROUP (ORDER BY D.DOCUMENT_TYPE_CD) as value
from test_table d;
VALUE
---------
CI,ECI,POA
now I'm trying to add a condition whenever 'ECI' value is present, it should exclude 'CI' in the result like this one below
VALUE
---------
ECI,POA
I tried using case statement in where condition it prompted an error
select LISTAGG(d.DOCUMENT_TYPE_CD, ',')
WITHIN GROUP (ORDER BY D.DOCUMENT_TYPE_CD) as value
from test_table d
where CASE d.DOCUMENT_TYPE_CD
WHEN 'ECI' THEN d.DOCUMENT_TYPE_CD <> 'CI'
END;
ORA-00905: missing keyword
00905. 00000 - "missing keyword"
*Cause:
*Action:
Error at Line: 7 Column: 36
is there any other way I could resolve this?
See if this helps; read comments within code.
SQL> with
2 test (id, document_type_cd) as
3 -- sample data
4 (select 1, 'ECI' from dual union all
5 select 1, 'CI' from dual union all
6 select 1, 'POA' from dual union all
7 --
8 select 2, 'CI' from dual union all
9 select 2, 'POA' from dual union all
10 --
11 select 3, 'XYZ' from dual union all
12 select 3, 'ABC' from dual
13 ),
14 temp as
15 -- see whether CI and ECI exist per each ID
16 (select id,
17 sum(case when document_type_cd = 'CI' then 1 else 0 end) sum_ci,
18 sum(case when document_type_cd = 'ECI' then 1 else 0 end) sum_eci
19 from test
20 group by id
21 ),
22 excl as
23 -- exclude CI rows if ECI exist for that ID
24 (select a.id,
25 a.document_type_cd
26 from test a join temp b on a.id = b.id
27 where a.document_type_cd <> case when b.sum_ci > 0 and b.sum_eci > 0 then 'CI'
28 else '-1'
29 end
30 )
31 -- finally:
32 select e.id,
33 listagg(e.document_type_cd, ',') within group (order by e.document_type_cd) result
34 from excl e
35 group by e.id;
ID RESULT
---------- --------------------
1 ECI,POA
2 CI,POA
3 ABC,XYZ
SQL>
Something like this:
select LISTAGG(d.DOCUMENT_TYPE_CD, ',')
WITHIN GROUP (ORDER BY D.DOCUMENT_TYPE_CD) as value
from test_table d,
(select sum (case when DOCUMENT_TYPE_CD = 'CI' then 1 else 0 end) C
from test_table) A
where d.DOCUMENT_TYPE_CD <> case when A.c > 0 then 'CI' when A.c = 0 then ' ' end;
DEMO
You may identify the presence of both the values with two conditional aggregations in the same group by and then replace CI inside the result of listagg in one pass.
with a(id, cd) as (
select 1, 'ABC' from dual union all
select 1, 'ECI' from dual union all
select 1, 'CI' from dual union all
select 1, 'POA' from dual union all
select 2, 'XYZ' from dual union all
select 2, 'ECI' from dual union all
select 2, 'CI' from dual union all
select 2, 'POA' from dual union all
select 3, 'CI' from dual union all
select 3, 'POA' from dual union all
select 4, 'ABC' from dual union all
select 4, 'DEF' from dual
)
select
id,
ltrim(
/*Added comma in case CI will be at the beginning*/
replace(
',' || listagg(cd, ',') within group (order by cd asc),
decode(
/*If both are present, then replace CI. If not, then do not replace anything*/
max(decode(cd, 'CI', 1))*max(decode(cd, 'ECI', 1)),
1,
',CI,'
),
','
),
','
) as res
from a
group by id
ID | RES
-: | :----------
1 | ABC,ECI,POA
2 | ECI,POA,XYZ
3 | CI,POA
4 | ABC,DEF
db<>fiddle here
Instead of using GROUP BY, you can also use windowing (aka analytic) functions to check the presence of ECI per group (test data shamelessly stolen from #littlefoot):
with
test (id, document_type_cd) as
-- sample data
(select 1, 'ECI' from dual union all
select 1, 'CI' from dual union all
select 1, 'POA' from dual union all
--
select 2, 'CI' from dual union all
select 2, 'POA' from dual union all
--
select 3, 'XYZ' from dual union all
select 3, 'ABC' from dual
),
temp as
(select id,
document_type_cd,
sum(case when document_type_cd = 'ECI' then 1 else 0 end) over (partition by id) as sum_eci
from test
)
select a.id,
listagg(a.document_type_cd, ',') within group (order by a.document_type_cd) result
from temp a
where a.document_type_cd != 'CI' or sum_eci = 0
group by a.id;

SQL logic to fail a check if any of the related customers has failed

I have the requirement to flag the customers Y only when all the related customers have also passed the check.
below are the two tables:
relationship table :
customer_id related_customer
1 1
1 2
1 3
2 1
2 2
2 3
3 1
3 2
3 3
11 11
11 22
22 11
22 22
Check table
customer_id check_flag
1 y
2 y
3 n
11 y
22 y
I want output like below:
customer_id paas_fail_flag
1 n
2 n
3 n
11 y
22 y
output justification: since 1,2,3 are related customers and since one of them (3) has n in table 2 , so all the related customers should also have n.
11,22 are related customers and both have y in table 2.so in output both should have y.
You need to join relationship to check and use conditional aggregation:
SELECT r.customer_id,
COALESCE(MAX(CASE WHEN c.check_flag = 'n' THEN c.check_flag END), 'y') paas_fail_flag
FROM relationship r INNER JOIN "check" c
ON c.customer_id = r.related_customer
GROUP BY r.customer_id
ORDER BY r.customer_id
See the demo.
Something like this? Sample data in lines #1 - 40; query begins at line #41:
SQL> WITH
2 -- sample data
3 rel (customer_id, related_customer)
4 AS
5 (SELECT 1, 1 FROM DUAL
6 UNION ALL
7 SELECT 1, 2 FROM DUAL
8 UNION ALL
9 SELECT 1, 3 FROM DUAL
10 UNION ALL
11 SELECT 2, 1 FROM DUAL
12 UNION ALL
13 SELECT 2, 2 FROM DUAL
14 UNION ALL
15 SELECT 2, 3 FROM DUAL
16 UNION ALL
17 SELECT 3, 1 FROM DUAL
18 UNION ALL
19 SELECT 3, 2 FROM DUAL
20 UNION ALL
21 SELECT 3, 3 FROM DUAL
22 UNION ALL
23 SELECT 11, 11 FROM DUAL
24 UNION ALL
25 SELECT 11, 22 FROM DUAL
26 UNION ALL
27 SELECT 22, 11 FROM DUAL
28 UNION ALL
29 SELECT 22, 22 FROM DUAL),
30 chk (customer_id, check_flag)
31 AS
32 (SELECT 1, 'y' FROM DUAL
33 UNION ALL
34 SELECT 2, 'y' FROM DUAL
35 UNION ALL
36 SELECT 3, 'n' FROM DUAL
37 UNION ALL
38 SELECT 11, 'y' FROM DUAL
39 UNION ALL
40 SELECT 22, 'y' FROM DUAL),
41 temp
42 AS
43 -- minimum CHECK_FLAG per customer and related customer
44 ( SELECT r.customer_id, r.related_customer, MIN (c.check_flag) mcf
45 FROM rel r JOIN chk c ON c.customer_id = r.related_customer
46 GROUP BY r.customer_id, r.related_customer)
47 SELECT customer_id, MIN (mcf) flag
48 FROM temp
49 GROUP BY customer_id
50 ORDER BY customer_id;
CUSTOMER_ID FLAG
----------- ----
1 n
2 n
3 n
11 y
22 y
SQL>
Assuming that your relationship data could be sparse, for example:
CREATE TABLE relationship ( customer_id, related_customer ) AS
SELECT 2, 3 FROM DUAL UNION ALL
SELECT 3, 1 FROM DUAL UNION ALL
SELECT 3, 2 FROM DUAL UNION ALL
SELECT 11, 22 FROM DUAL;
CREATE TABLE "CHECK" ( customer_id, check_flag ) AS
SELECT 1, 'y' FROM DUAL UNION ALL
SELECT 2, 'y' FROM DUAL UNION ALL
SELECT 3, 'n' FROM DUAL UNION ALL
SELECT 11, 'y' FROM DUAL UNION ALL
SELECT 22, 'y' FROM DUAL;
(Note: The below query will also work on your dense data, where every relationship combination is enumerated.)
Then you can use a hierarchical query:
SELECT customer_id,
MIN(check_flag) AS check_flag
FROM (
SELECT CONNECT_BY_ROOT(c.customer_id) AS customer_id,
c.check_flag AS check_flag
FROM "CHECK" c
LEFT OUTER JOIN relationship r
ON (r.customer_id = c.customer_id)
WHERE CONNECT_BY_ISLEAF = 1
CONNECT BY NOCYCLE
( PRIOR r.related_customer = c.customer_id
OR PRIOR c.customer_id = r.related_customer )
AND PRIOR c.check_flag = 'y'
)
GROUP BY
customer_id
ORDER BY
customer_id
Which outputs:
CUSTOMER_ID
CHECK_FLAG
1
n
2
n
3
n
11
y
22
y
db<>fiddle here

Print message when no data is found

Need a query to get the Employee name, total fuel used by each employee.
If fuel is not used by an employee then the second column should have a
text “No fuel used”.
These are the following two tables:
Table1: EmployeeID, FirstName
1 Vikas
2 nikita
3 Ashish
4 Nikhil
5 anish
Table2: ID, Fuel
1 10
2 9
3 8
4 6
5 12
6 11
7 10
8 9
9 8
10 10
11 9
12 12
13 7
14 15
where The column table2.ID is a foreign key to table1.EmployeeID.
This is code which I have written, Which is most probably wrong.
select ID, FirstName, sum(table2.Fuel) sum_fuel
from table2,table1
where EmployeeID=ID IN (
select ID, coalesce(ID, 'No-fuel used') as ID
from table1 t1
left join table2 t2 on t2.ID = t1.EmployeeID
)
group by fuel
order by ID DESC;
As you can see from two tables that employee with from 1 to 5 of table1 are in table2. So for these employee I need to show total fuel used by every individual. And for employee with ID from 6 to 14 are not available in table1 so for these employee “No fuel used” message should be printed.
You can use a left join. This way, whenever the Id values for tables don't match you'll get null values for sum(fuel) value, and will assign the string 'No fuel used'for sum_fuel column by using nvl() function:
with table1( EmployeeID, FirstName ) as
(
select 1,'Vikas' from dual union all
select 2,'nikita' from dual union all
select 3,'Ashish' from dual union all
select 4,'Nikhil' from dual union all
select 5,'anish' from dual union all
select 15,'pratteek' from dual
), table2( ID, Fuel ) as
(
select 1, 10 from dual union all
select 2, 9 from dual union all
select 3, 8 from dual union all
select 4, 6 from dual union all
select 5, 12 from dual union all
select 6, 11 from dual union all
select 7, 10 from dual union all
select 8, 9 from dual union all
select 9, 8 from dual union all
select 10, 10 from dual union all
select 11, 9 from dual union all
select 12, 12 from dual union all
select 13, 7 from dual union all
select 14, 15 from dual
)
select EmployeeID, FirstName, nvl(to_char(sum(t2.Fuel)),'No fuel used') as sum_fuel
from table1 t1
left join table2 t2
on t1.EmployeeID = t2.ID
group by EmployeeID, FirstName
order by EmployeeID desc;
EMPLOYEEID FIRSTNAME SUM_FUEL
---------- --------- ------------
15 pratteek No fuel used
5 anish 12
4 Nikhil 6
3 Ashish 8
2 nikita 9
1 Vikas 10
Demo
This may work---
SELECT ID
, FirstName
, CASE
WHEN SUM(f.Fuel) > 0 THEN CAST(SUM(f.Fuel) AS NVARCHAR(25))
ELSE 'No fuel used'
END sum_fuel
FROM #emp e
LEFT JOIN #fuel f ON e.EmployeeID = f.id
GROUP BY ID,FirstName
ORDER BY ID DESC

Distinct LISTAGG that is inside a subquery in the SELECT list

Here is a minimal working example of what I'm trying to do and what I'm getting:
I have a query as follows:
/*
with tran_party as -- ALL DUMMY DATA ARE IN THESE CTE FOR YOUR REFERENCE
(select 1 tran_party_id, 11 transaction_id, 101 team_id_redirect
from dual
union all
select 2, 11, 101 from dual
union all
select 3, 11, 102 from dual
union all
select 4, 12, 103 from dual
union all
select 5, 12, 103 from dual
union all
select 6, 12, 104 from dual
union all
select 7, 13, 104 from dual
union all
select 8, 13, 105 from dual),
tran as
(select 11 transaction_id, 1001 account_id, 1034.93 amount from dual
union all
select 12, 1001, 2321.89 from dual
union all
select 13, 1002, 3201.47 from dual),
account as
(select 1001 account_id, 111 team_id from dual
union all
select 1002, 112 from dual),
team as
(select 101 team_id, 'UUU' as team_code from dual
union all
select 102, 'VV' from dual
union all
select 103, 'WWW' from dual
union all
select 104, 'XXXXX' from dual
union all
select 105, 'Z' from dual)
-- */
-- The Actual Query
select a.account_id,
t.transaction_id,
(select listagg (tm_redir.team_code, ', ')
within group (order by tm_redir.team_code)
from tran_party tp_redir
inner join team tm_redir
on tp_redir.team_id_redirect = tm_redir.team_id
inner join tran t_redir
on tp_redir.transaction_id = t_redir.transaction_id
where t_redir.account_id = a.account_id
and t_redir.transaction_id != t.transaction_id)
as teams_redirected
from tran t inner join account a on t.account_id = a.account_id;
NOTE: tran_party.team_id_redirect is a foreign key that references team.team_id.
Current output:
ACCOUNT_ID TRANSACTION_ID TEAMS_REDIRECTED
---------- -------------- ----------------
1001 11 WWW, WWW, XXXXX
1001 12 UUU, UUU, VV
1002 13
Expected output:
I want the repeated items in TEAMS_REDIRECTED column to be selected only once, like this:
ACCOUNT_ID TRANSACTION_ID TEAMS_REDIRECTED
---------- -------------- ----------------
1001 11 WWW, XXXXX
1001 12 UUU, VV
1002 13
What I tried:
Instead of selecting from tran_party directly, I wrote an inline view that selects distinct values from tran_party like this:
select a.account_id,
t.transaction_id,
(select listagg (tm_redir.team_code, ', ')
within group (order by tm_redir.team_code)
from (select distinct transaction_id, team_id_redirect -- Note this inline view
from tran_party) tp_redir
inner join team tm_redir
on tp_redir.team_id_redirect = tm_redir.team_id
inner join tran t_redir
on tp_redir.transaction_id = t_redir.transaction_id
where t_redir.account_id = a.account_id
and t_redir.transaction_id != t.transaction_id)
as teams_redirected
from tran t inner join account a on t.account_id = a.account_id;
While this does give me the expected output, when I use this solution in my actual code, it takes about 13 seconds to retrieve just one row. Thus I cannot use what I already tried.
Any help will be appreciated.
The following method gets rid of the in-line view to fetch duplicates, it uses REGEXP_REPLACE and RTRIM on the LISTAGG function to get the distinct result set in the aggregated list. Thus, it won't do more than one scan.
Adding this piece to your code,
RTRIM(REGEXP_REPLACE(listagg (tm_redir.team_code, ',')
WITHIN GROUP (ORDER BY tm_redir.team_code),
'([^,]+)(,\1)+', '\1'),
',')
Modified query-
SQL> with tran_party as -- ALL DUMMY DATA ARE IN THESE CTE FOR YOUR REFERENCE
2 (select 1 tran_party_id, 11 transaction_id, 101 team_id_redirect
3 from dual
4 union all
5 select 2, 11, 101 from dual
6 union all
7 select 3, 11, 102 from dual
8 union all
9 select 4, 12, 103 from dual
10 union all
11 select 5, 12, 103 from dual
12 union all
13 select 6, 12, 104 from dual
14 union all
15 select 7, 13, 104 from dual
16 union all
17 select 8, 13, 105 from dual),
18 tran as
19 (select 11 transaction_id, 1001 account_id, 1034.93 amount from dual
20 union all
21 select 12, 1001, 2321.89 from dual
22 union all
23 select 13, 1002, 3201.47 from dual),
24 account as
25 (select 1001 account_id, 111 team_id from dual
26 union all
27 select 1002, 112 from dual),
28 team as
29 (select 101 team_id, 'UUU' as team_code from dual
30 union all
31 select 102, 'VV' from dual
32 union all
33 select 103, 'WWW' from dual
34 union all
35 select 104, 'XXXXX' from dual
36 union all
37 select 105, 'Z' from dual)
38 -- The Actual Query
39 select a.account_id,
40 t.transaction_id,
41 (SELECT RTRIM(
42 REGEXP_REPLACE(listagg (tm_redir.team_code, ',')
43 WITHIN GROUP (ORDER BY tm_redir.team_code),
44 '([^,]+)(,\1)+', '\1'),
45 ',')
46 from tran_party tp_redir
47 inner join team tm_redir
48 on tp_redir.team_id_redirect = tm_redir.team_id
49 inner join tran t_redir
50 on tp_redir.transaction_id = t_redir.transaction_id
51 where t_redir.account_id = a.account_id
52 and t_redir.transaction_id != t.transaction_id)
53 AS teams_redirected
54 from tran t inner join account a on t.account_id = a.account_id
55 /
ACCOUNT_ID TRANSACTION_ID TEAMS_REDIRECTED
---------- -------------- --------------------
1001 11 WWW,XXXXX
1001 12 UUU,VV
1002 13
SQL>