Transform multiple rows into a single row in oracle SQL - sql

Sample table
Expected results
Trying to merge the related rows together into a single row.
Not sure what's wrong with my query. Trying to understand why it didn't work some scenarios (E.g. 01.11 but works for 19).
with
COLUMN 8 as (select COL F as COLUMN 8, COL B as COLUMN 7, COL C as COLUMN 22 from TABLE where COL F LIKE '%.__%'),
COLUMN 6 as (select COL E as COLUMN 6, COL B as COLUMN 5 from TABLE where COL E LIKE '%._%'),
COLUMN 4 as (select COL D as COLUMN 4, COL B as COLUMN 3, COL C as COLUMN 21 from TABLE),
COLUMN 2 as (select COL A as COLUMN 2, COL B as COLUMN 1 from TABLE)
select "COLUMN 8", "COLUMN 7", "COLUMN 6", "COLUMN 5", "COLUMN 4", "COLUMN 3", "COLUMN 2", "COLUMN 1"
from COLUMN 8 c
left join COLUMN 6 g on TO_CHAR(TRUNC(c.COLUMN 8,1)) LIKE TO_CHAR(g.COLUMN 6)
left join COLUMN 4 d on TRUNC(g.COLUMN 6,0) LIKE TO_CHAR(d.COLUMN 4)
left join COLUMN 2 s ON d.COLUMN 21 = s.COLUMN 2 OR s.COLUMN 2 = c.COLUMN 22;

Try this (you may have to change the name of the columns in the PIVOT):
with data (code, name, root_code) as (
select '01.11', 'sub sub crop', 'A' from dual union all
select '01.1', 'sub crop', 'A' from dual union all
select '01', 'crop', 'A' from dual union all
select '19', 'Computer', 'C' from dual union all
select '19.1', 'sub computer', 'C' from dual union all
select '19.2', 'sub engine', 'C' from dual union all
select '19.20', 'sub sub engine', 'C' from dual union all
select '19.10', 'sub sub computer', 'C' from dual union all
select 'A', 'Agriculture', null from dual union all
select 'C', 'Technology', null from dual --union all
)
select class_code, class_name, sector_code, sector_name, division_code, division_name, grp_code, grp_name from (
select l1, path, substr(path_item,1,instr(path_item,':')-1) as name, substr(path_item,instr(path_item,':')+1) as code-- , root
from (
select lvl, level as l1, path, regexp_substr(path,'[^/]+',1,level) as path_item, root
from (
select level as lvl, d.code, d.name, sys_connect_by_path(name || ':' || code,'/') as path, connect_by_root(d.code) as root
from data d
start with root_code is null
connect by (
(
(substr(code,1,length(code)-1) = prior code or substr(code,1,length(code)-1) = prior code || '.')
and prior root_code = root_code
)
or (
prior root_code is null and length(code) = 2
and root_code = prior code
)
)
and prior sys_guid() is not null
) d
where lvl = 4
connect by regexp_substr(path,'[^/]+',1,level) is not null and prior path = path and prior sys_guid() is not null
)
)
pivot (
max(name)as name, max(code) as code for l1 in (1 class,2 as sector,3 as division, 4 as grp)
);
A Agriculture 01 crop 01.1 sub crop 01.11 sub sub crop
C Technology 19 Computer 19.1 sub computer 19.10 sub sub computer
C Technology 19 Computer 19.2 sub engine 19.20 sub sub engine

Related

How to find duplicate values according multiple columns and show both

I try to find another examples, but I have not been able to find one that can help me
I am currently trying to find if the value in the STR_ROUTE column is in the STR_STREET column, as shown in the following example
ID
STR_ROUTE
STR_STREET
1
MAIN
Can
2
AV
CAL
3
CLL
CLL
4
STR
VAL
5
VAL
MIN
7
CAL
SQR
in this example as the CAL and VAL values of the STR_ROUTE column are in STR_STREET the expected result is to display the following table with all occurrences
ID
STR_ROUTE
STR_STREET
2
AV
CAL
4
STR
VAL
5
VAL
MIN
7
CAL
SQR
(The third row is not taken into consideration because it is the same registry.)
I was validating with this option, but I have not been able to succeed and does not take the rules into consideration.
It does not take into consideration if the repeated value is within
the same record.
Both the repeated record and the record to which it is compared must be displayed.
SELECT * FROM TABLE WHERE STR_ROUTE IN (SELECT STR_STREET FROM TABLE WHERE STR_STREET)
You may check the presence of values of each column in another column and union the results.
with test_table(ID, STR_ROUTE, STR_STREET) as (
select 1, 'MAIN', 'Can' from dual union all
select 2, 'AV', 'CAL' from dual union all
select 3, 'CLL', 'CLL' from dual union all
select 4, 'STR', 'VAL' from dual union all
select 5, 'VAL', 'MIN' from dual union all
select 7, 'CAL', 'SQR' from dual
)
select *
from test_table
where str_route in (
select f.str_street
from test_table f
)
and str_route != str_street
union all
select *
from test_table
where str_street in (
select f.str_route
from test_table f
)
and str_route != str_street
ID
STR_ROUTE
STR_STREET
5
VAL
MIN
7
CAL
SQR
2
AV
CAL
4
STR
VAL
db<>fiddle here
You can use a hierarchical query and filter to only include the rows where the hierarchical query has descended to the second level and it is a leaf node or where the first level of the hierarchy is not a leaf (and there is a matching second level in the hierarchy):
SELECT *
FROM table_name
WHERE LEVEL = 1 AND CONNECT_BY_ISLEAF = 0
OR LEVEL = 2 AND CONNECT_BY_ISLEAF = 1
CONNECT BY NOCYCLE
PRIOR str_route = str_street;
Which, for the sample data:
CREATE TABLE table_name (ID, STR_ROUTE, STR_STREET) AS
SELECT 1, 'MAIN', 'Can' FROM DUAL UNION ALL
SELECT 2, 'AV', 'CAL' FROM DUAL UNION ALL
SELECT 3, 'CLL', 'CLL' FROM DUAL UNION ALL
SELECT 4, 'STR', 'VAL' FROM DUAL UNION ALL
SELECT 5, 'VAL', 'MIN' FROM DUAL UNION ALL
SELECT 7, 'CAL', 'SQR' FROM DUAL;
Outputs:
ID
STR_ROUTE
STR_STREET
5
VAL
MIN
4
STR
VAL
7
CAL
SQR
2
AV
CAL
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;

Identify only when value matches

I need to return only rows that have the match e.g Value = A, but I only need the rows that have A and with no other values.
T1:
ID Value
1 A
1 B
1 C
2 A
3 A
3 B
4 A
5 B
5 D
5 E
5 F
Desired Output:
2
4
how can I achieve this?
when I try the following, 1&3 are also returned:
select ID from T1 where Value ='A'
With NOT EXISTS:
select t.id
from tablename t
where t.value = 'A'
and not exists (
select 1 from tablename
where id = t.id and value <> 'A'
)
From the sample data you posted there is no need to use:
select distinct t.id
but if you get duplicates then use it.
Another way if there are no null values:
select id
from tablename
group by id
having sum(case when value <> 'A' then 1 else 0 end) = 0
Or if you want the rows where the id has only 1 value = 'A':
select id
from tablename
group by id
having count(*) = 1 and max(value) = 'A'
I think the simplest way is aggregation with having:
select id
from tablename
group by id
having min(value) = max(value) and
min(value) = 'A';
Note that this ignores NULL values so it could return ids with both NULL and A. If you want to avoid that:
select id
from tablename
group by id
having count(value) = count(*) and
min(value) = max(value) and
min(value) = 'A';
Oracle Setup:
CREATE TABLE test_data ( ID, Value ) AS
SELECT 1, 'A' FROM DUAL UNION ALL
SELECT 1, 'B' FROM DUAL UNION ALL
SELECT 1, 'C' FROM DUAL UNION ALL
SELECT 2, 'A' FROM DUAL UNION ALL
SELECT 3, 'A' FROM DUAL UNION ALL
SELECT 3, 'B' FROM DUAL UNION ALL
SELECT 4, 'A' FROM DUAL UNION ALL
SELECT 5, 'B' FROM DUAL UNION ALL
SELECT 5, 'D' FROM DUAL UNION ALL
SELECT 5, 'E' FROM DUAL UNION ALL
SELECT 5, 'F' FROM DUAL
Query:
SELECT ID
FROM test_data
GROUP BY ID
HAVING COUNT( CASE Value WHEN 'A' THEN 1 END ) = 1
AND COUNT( CASE Value WHEN 'A' THEN NULL ELSE 1 END ) = 0
Output:
| ID |
| -: |
| 2 |
| 4 |
db<>fiddle here

Conditional column value, Select

I got 2 tables "Records" and "Char". With 1 -> N relation
I need to make a select, with a subquery/join where the value to present on the join column is a fixed string like "Multiple Chars" or the content Char.char_val
Let me illustrate:
Records:
R_ID | Name Char: C_ID | R_ID | Char_Val
1 A 1 3 c1
2 B 2 1 c2
3 C 3 1 c3
4 2 c3
Expected Result:
R_ID | Name | Char_Val
1 A Multiple Records
2 B c3
3 C c1
I guess my query would be something like:
Select r.R_ID, r.Name, (conditional select) Char_Val
From Records r, Char c
where r.R_ID = c.R_ID
Suggestions for the (conditional select)?
You can use a case statement and aggregation to get a fixed string:
case when count(c.c_id) > 1 then 'Multiple Records' else max(c.char_val) end
and you need to group by r_id and name:
select r.r_id, r.name,
case when count(c.c_id) > 1 then 'Multiple Records'
else max(c.char_val) end as char_val
from records r
join char c on r.r_id = c.r_id
group by r.r_id, r.name
order by r.r_id;
I've also switched to use ANSI joins instead of the old syntax (as #Thorsten suggested).
This is a demo using CTE to generate your data, giving them slightly different names because char is a reserved word:
with t_records (r_id, name) as (
select 1, 'A' from dual
union all select 2, 'B' from dual
union all select 3, 'C' from dual
),
t_char (c_id, r_id, char_val) as (
select 1, 3, 'c1' from dual
union all select 2, 1, 'c2' from dual
union all select 3, 1, 'c3' from dual
union all select 4, 2, 'c3' from dual
)
select r.r_id, r.name,
case when count(c.c_id) > 1 then 'Multiple Records'
else max(c.char_val) end as char_val
from t_records r
join t_char c on r.r_id = c.r_id
group by r.r_id, r.name
order by r.r_id;
R_ID N CHAR_VAL
---------- - ----------------
1 A Multiple Records
2 B c3
3 C c1
Group by r_id. Either MIN = MAX or you want 'Multiple Records':
select r_id, r.name, c.char_vals
from
(
select
r_id,
case when min(char_val) = max(char_val) then min(char_val) else 'Multiple Records' end
as char_vals
from char
group by r_id
) c
join records r using(r_id)
order by r_id;
Following query gives the result (with Char_val separated by comma) you expected:
Select r.R_ID, r.Name, listagg(c.char_val,',') WITHIN GROUP(ORDER BY c.char_val) AS Char_Val
From Records r, Char c
where r.R_ID = c.R_ID
GROUP BY r.R_ID, r.Name

How to convert table using SQL

I'm sorry if this is a duplicate question, but I had no idea which search keywords to use (that's why the question is vague as well)
I have a table like this
Parent_ID Parent_item Child_Item
1 A B
1 A C
2 H I
2 H J
2 H K
And I would like to have the results in the following format:
Parent_ID Parent_or_Child
1 A
1 B
1 C
2 H
2 I
2 J
2 K
How can this be done?
Thanks!
You need to unpivot the data, use UNION
select Parent_ID,Parent_item
from yourtable
Union
select Parent_ID,Child_Item
from yourtable
You need to unpivot the data, and it seems you don't want duplicates so you need to select distinct. In Oracle 11.1 or above, you can use the UNPIVOT operator - the advantage is that the base table will be read just once.
You can add an order by clause if you need it; I didn't, so the rows in the output are in arbitrary order (if you compare to your "desired output").
with
test_data ( Parent_ID, Parent_item, Child_Item ) as (
select 1, 'A', 'B' from dual union all
select 1, 'A', 'C' from dual union all
select 2, 'H', 'I' from dual union all
select 2, 'H', 'J' from dual union all
select 2, 'H', 'K' from dual
)
-- End of test data (NOT part of the query).
-- SQL query begins BELOW THIS LINE.
select distinct parent_id, parent_or_child
from test_data
unpivot (parent_or_child
for col in (parent_item as 'parent_item', child_item as 'child_item'))
;
PARENT_ID PARENT_OR_CHILD
---------- ---------------
2 I
1 A
1 B
1 C
2 H
2 J
2 K
7 rows selected.