converting comma separated value to multiple rows - sql

I have a table like this:
ID NAME Dept_ID
1 a 2,3
2 b
3 c 1,2
Department is another table having dept_id and dept_name as columns. i want result like,
ID Name Dept_ID
1 a 2
1 a 3
2 b
3 c 1
3 c 2
any help please?

You can do it as:
--Dataset Preparation
with tab(ID, NAME,Dept_ID) as (Select 1, 'a', '2,3' from dual
UNION ALL
Select 2, 'b','' from dual
UNION ALL
Select 3, 'c' , '1,2' from dual)
--Actual Query
select distinct ID, NAME, regexp_substr(DEPT_ID,'[^,]+', 1, level)
from tab
connect by regexp_substr(DEPT_ID,'[^,]+', 1, level) is not null
order by 1;
Edit:
based on which column i need to join? in one table i have comma
separated ids and in other table i have just ids
with tab(ID, NAME,Dept_ID) as (Select 1, 'a', '2,3' from dual
UNION ALL
Select 2, 'b','' from dual
UNION ALL
Select 3, 'c' , '1,2' from dual) ,
--Table Dept
tbl_dept (dep_id,depname) as ( Select 1,'depa' from dual
UNION ALL
Select 2,'depb' from dual
UNION ALL
Select 3,'depc' from dual
) ,
--Seperating col values for join. Start your query from here using with clause since you already have the two tables.
tab_1 as (select distinct ID, NAME, regexp_substr(DEPT_ID,'[^,]+', 1, level) col3
from tab
connect by regexp_substr(DEPT_ID,'[^,]+', 1, level) is not null
order by 1)
--Joining table.
Select t.id,t.name,t.col3,dt.depname
from tab_1 t
left outer join tbl_dept dt
on t.col3 = dt.dep_id
order by 1

with tmp_tbl as(
select
1 ID,
'a' NAME,
'2,3' DEPT_ID
from dual
union all
select
2 ID,
'b' NAME,
'' DEPT_ID
from dual
union all
select
3 ID,
'c' NAME,
'1,2' DEPT_ID
from dual)
select
tmp_out.ID,
tmp_out.NAME,
trim(tmp_out.DEPT_ID_splited)
from(
select
tmp.ID,
tmp.NAME,
regexp_substr(tmp.DEPT_ID,'[^,]+', 1, level) DEPT_ID_splited
from
tmp_tbl tmp
connect by
regexp_substr(tmp.DEPT_ID,'[^,]+', 1, level) is not null) tmp_out
group by
tmp_out.ID,
tmp_out.NAME,
tmp_out.DEPT_ID_splited
order by
tmp_out.ID,
tmp_out.DEPT_ID_splited

Related

Is there a reason I am getting no rows selected for this NOT IN nested query

Query:
SELECT teamid
FROM team
WHERE teamid NOT IN (SELECT team_teamid FROM gymnast);
Tables:
TEAM table
Gymnast table
I have been trying to figure this out for a while, any information would be greatly appreciated. Thank you!
The fiddle for oracle
val NOT IN (some list) means the value does NOT match every entry in the list.
But if one entry in the list is null, value <> null can never be true for that entry, which means the entire NOT IN expression can never be true.
See the following examples:
WITH team (teamid) AS (
SELECT 1 FROM DUAL UNION
SELECT 2 FROM DUAL UNION
SELECT 3 FROM DUAL UNION
SELECT 4 FROM DUAL
)
, gymnast (team_teamid) AS (
SELECT 1 FROM DUAL UNION
SELECT 2 FROM DUAL UNION
SELECT 2 FROM DUAL UNION
SELECT 4 FROM DUAL
)
SELECT teamid FROM team WHERE teamid NOT IN (SELECT team_teamid FROM gymnast)
;
Result:
TEAMID
3
Case 2 with nulls in the list:
WITH team (teamid) AS (
SELECT 1 FROM DUAL UNION
SELECT 2 FROM DUAL UNION
SELECT 3 FROM DUAL UNION
SELECT 4 FROM DUAL
)
, gymnast (team_teamid) AS (
SELECT 1 FROM DUAL UNION
SELECT 2 FROM DUAL UNION
SELECT null FROM DUAL UNION
SELECT 4 FROM DUAL
)
SELECT teamid FROM team WHERE teamid NOT IN (SELECT team_teamid FROM gymnast)
;
Result:
TEAMID
No rows in result, due to the gymnast with a null team_teamid.

Group and exclude rows that had empty values aggregated - oracle sql

I have a oracle sql table that looks like so
"STUDENT_ID","FULL_NAME","SEMESTER_ID","STIP_ID"
"1","Liam Bottrill","1","1"
"1","Liam Bottrill","2","3"
"1","Liam Bottrill","3","2"
"1","Liam Bottrill","4","5"
"2","Maurits Smitham","1","6"
"2","Maurits Smitham","2",""
"2","Maurits Smitham","3","2"
"2","Maurits Smitham","4","6"
"43","Jackie Cotton","1",""
"43","Jackie Cotton","2",""
"43","Jackie Cotton","3",""
"43","Jackie Cotton","4",""
I want to group this table by "STUDENT_ID" and exclude from result any students that have any of "STIP_ID" rows empty
Im aiming for result like this:
"STUDENT_ID","FULL_NAME"
"1","Liam Bottrill"
Liam Bottrill should be displayed while Maurits Smitham and Jackie Cotton should be excluded from result
Can you please help me with such aggregate function?
Here is one way, using aggregation:
SELECT *
FROM yourTable
WHERE STUDENT_ID IN (
SELECT STUDENT_ID
FROM yourTable
GROUP BY STUDENT_ID
HAVING COUNT(CASE WHEN STIP_ID IS NULL THEN 1 END) = 0
);
Another way, using exists logic:
SELECT t1.*
FROM yourTable t1
WHERE NOT EXISTS (
SELECT 1
FROM yourTable t2
WHERE t2.STUDENT_ID = t1.STUDENT_ID AND
t2.STIP_ID IS NULL
);
You can group by the identifier and then use conditional aggregation to find the student where the count when STIP_ID is NULL (which, in Oracle, is the same as an empty string):
SELECT student_id,
MAX(full_name) AS full_name
FROM table_name
GROUP BY student_id
HAVING COUNT(CASE WHEN stip_id IS NULL THEN 1 END) = 0;
Which, for your sample data:
CREATE TABLE table_name (STUDENT_ID, FULL_NAME, SEMESTER_ID, STIP_ID) AS
SELECT 1, 'Liam Bottrill', 1, 1 FROM DUAL UNION ALL
SELECT 1, 'Liam Bottrill', 2, 3 FROM DUAL UNION ALL
SELECT 1, 'Liam Bottrill', 3, 2 FROM DUAL UNION ALL
SELECT 1, 'Liam Bottrill', 4, 5 FROM DUAL UNION ALL
SELECT 2, 'Maurits Smitham', 1, 6 FROM DUAL UNION ALL
SELECT 2, 'Maurits Smitham', 2, NULL FROM DUAL UNION ALL
SELECT 2, 'Maurits Smitham', 3, 2 FROM DUAL UNION ALL
SELECT 2, 'Maurits Smitham', 4, 6 FROM DUAL UNION ALL
SELECT 43, 'Jackie Cotton', 1, NULL FROM DUAL UNION ALL
SELECT 43, 'Jackie Cotton', 2, NULL FROM DUAL UNION ALL
SELECT 43, 'Jackie Cotton', 3, NULL FROM DUAL UNION ALL
SELECT 43, 'Jackie Cotton', 4, NULL FROM DUAL;
Outputs:
STUDENT_ID
FULL_NAME
1
Liam Bottrill
db<>fiddle here

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;

How to select the minimum value in a table or the next one in oracle sql

I have a table L1_CI_PER_ADDRESS with these columns
PER_ID,
SEQ_NUM,
ADDRESS_ID,
ADDRESS_TYPE_XFLG,
START_DT,
END_DT,
SEASON_START_MMDD,
SEASON_END_MMDD,
ADDRESS_PRIO_FLG,
DELIVERABLE_FLG,
VERSION,
LOAD_DATE
I want to select ADDRESS_TYPE_XFLG where the value is MAIN-AE if it exists or the MAIN-EN if the first one does not exists. Else I want to select CORRESPOND-AE or CORRESPOND-AE if MAIN-AE and MAIN-EN do not exists.
How can I do this? I am new to Oracle SQL. I want to remove the duplicates returned when I do my select.
One of the issues is that some person ID's have all four (MAIN-AE, MAIN-EN, CORRESPOND-AE, CORRESPOND-EN), so in this case I just want MAIN-AE to be returned.
I hope my question is clear.
enter image description here
It's top-n query. Use row_number():
select *
from (
select PER_ID, address_id, ADDRESS_TYPE_XFLG,
row_number() over (partition by per_id
order by case ADDRESS_TYPE_XFLG
when 'MAIN-AE' then 1
when 'MAIN-EN' then 2
when 'CORRESPOND-AE' then 3
when 'CORRESPOND-EN' then 4
end) as rn
from L1_CI_PER_ADDRESS)
where rn = 1
If person can own two addresses with the same flag then you need to add proper order after case when section, probably something like , seq_num desc.
Test:
with L1_CI_PER_ADDRESS(PER_ID, address_id, ADDRESS_TYPE_XFLG ) as (
select 1, 1, 'CORRESPOND-AE' from dual union all
select 1, 2, 'MAIN-AE' from dual union all
select 1, 3, 'CORRESPOND-EN' from dual union all
select 1, 4, 'MAIN-EN' from dual union all
select 2, 5, 'CORRESPOND-AE' from dual union all
select 3, 6, 'MAIN-AE' from dual union all
select 4, 7, 'CORRESPOND-EN' from dual union all
select 4, 8, 'MAIN-AE' from dual
)
select PER_ID, address_id
from (
select PER_ID, address_id, ADDRESS_TYPE_XFLG,
row_number() over (partition by per_id
order by case ADDRESS_TYPE_XFLG
when 'MAIN-AE' then 1
when 'MAIN-EN' then 2
when 'CORRESPOND-AE' then 3
when 'CORRESPOND-EN' then 4
end) as rn
from L1_CI_PER_ADDRESS)
where rn = 1
Output:
PER_ID ADDRESS_ID ADDRESS_TYPE_XFLG
---------- ---------- -----------------
1 2 MAIN-AE
2 5 CORRESPOND-AE
3 6 MAIN-AE
4 8 MAIN-AE

Efficient way to pull counts for all permutations of a field

I have an oracle DB w/ a table that contains records associated to a person (based on an ID). The records are categorized as category = 1, 2, or 3.
I would like to pull as follows:
- # of people with only a category 1 record (no category=2 or 3)
- # of people with only a category 2 record (no category=1 or 3)
- # of people with only a category 3 record (no category=1 or 2)
- # of people with both category 1 & 2 records (no category=3)
- # of people with both category 1 & 3 records (no category=2)
- # of people with all category records 1,2, & 3
- # of people with both a category 2 & 3 records (no category=1)
I could only think of the following solution (modified for each case):
select count(*) from table1
where id in (select id from table1 where category=1)
and id not in (select id from table1 where category=2)
and id not in (select id from table1 where category=3)
But, I believe this is a highly inefficient way of doing this, was wondering if anyone had quicker/better way of getting this info.
Thanks!
One way to do this is to bring the categories together, using listagg() and then reaggregate:
select categories, count(*)
from (select listagg(t1.category, ',') within group (order by t1.category) as categories, personid
from table1 t1
group by personid
) x
group by categories;
EDIT:
If you need distinct values:
select categories, count(*)
from (select listagg(t1.category, ',') within group (order by t1.category) as categories, personid
from (select distinct t1.category, t1.personid from table1 t1) t1
group by personid
) x
group by categories;
Here is a query that, for each ID, shows the count of distinct categories and the MIN and MAX category. This query can be used as a sub-query in further processing (you didn't explain exactly HOW you want the results to be presented). When the COUNT is 1, then the single category is that in the MIN_CAT column; when the COUNT is 3, then all three categories are present for that ID; and when the COUNT is 2, then the two categories that are present are in the MIN and the MAX columns. Whatever else you need to do from here should be very simple; for example you can now GROUP BY CT, MIN_CAT, MAX_CT and count ID's.
I do a count(distinct category) to allow the possibility of non-unique (id, category) - as illustrated in the sample data I include in a WITH clause (which is NOT part of the SQL query!)
with
test_data ( id, category ) as (
select 101, 3 from dual union all
select 101, 1 from dual union all
select 101, 3 from dual union all
select 104, 2 from dual union all
select 105, 2 from dual union all
select 105, 2 from dual union all
select 105, 1 from dual union all
select 106, 1 from dual union all
select 106, 2 from dual union all
select 106, 3 from dual union all
select 106, 3 from dual
)
select id,
count(distinct category) as ct,
min(category) as min_cat,
max(category) as max_cat
from test_data
group by id
;
ID CT MIN_CAT MAX_CAT
--- -- ------- -------
101 2 1 3
105 2 1 2
104 1 2 2
106 3 1 3
Oracle Setup:
CREATE TABLE test_data ( id, category ) as
select 101, 3 from dual union all
select 101, 1 from dual union all
select 101, 3 from dual union all
select 104, 2 from dual union all
select 105, 2 from dual union all
select 105, 2 from dual union all
select 105, 1 from dual union all
select 106, 1 from dual union all
select 106, 2 from dual union all
select 106, 3 from dual union all
select 106, 3 from dual union all
select 107, 1 from dual union all
select 107, 3 from dual;
Query:
SELECT c1,
c2,
c3,
LTRIM(
DECODE( c1, 1, ',1' ) || DECODE( c2, 1, ',2' ) || DECODE( c3, 1, ',3' ),
','
) AS categories,
COUNT(1) AS num_people,
LISTAGG( id, ',' ) WITHIN GROUP ( ORDER BY id ) AS people
FROM ( SELECT DISTINCT * FROM test_data )
PIVOT ( COUNT(1) FOR category IN ( 1 AS c1, 2 AS c2, 3 AS c3 ) )
GROUP BY c1, c2, c3;
Output:
C1 C2 C3 CATEGORIES NUM_PEOPLE PEOPLE
-- -- -- ---------- ---------- ----------
0 1 0 2 1 104
1 0 1 1,3 2 101,107
1 1 0 1,2 1 105
1 1 1 1,2,3 1 106