Oracle transform table column to two columns - oracle

Let say I have the next table:
ID_1
ID_2
Value
1
11
A
2
12
A
2
13
A
2
13
B
3
12
A
3
13
B
I want to transform it to:
ID_1
ID_2
Value_A
Value_B
1
11
1
0
2
12
1
0
2
13
1
1
3
12
1
0
3
13
0
1
When there is a value A but no Value B, so put 0 in Value B
When there is a value B but no Value A, so put 0 in Value A
How can I do it in oracle?

Use a PIVOT:
SELECT *
FROM table_name
PIVOT ( COUNT(*) FOR value IN ('A' AS Value_A, 'B' AS Value_B) )
Which, for the sample data:
CREATE TABLE table_name (ID_1, ID_2, Value) AS
SELECT 1, 11, 'A' FROM DUAL UNION ALL
SELECT 2, 12, 'A' FROM DUAL UNION ALL
SELECT 2, 13, 'A' FROM DUAL UNION ALL
SELECT 2, 13, 'B' FROM DUAL UNION ALL
SELECT 3, 12, 'A' FROM DUAL UNION ALL
SELECT 3, 13, 'B' FROM DUAL;
Outputs:
ID_1
ID_2
VALUE_A
VALUE_B
1
11
1
0
3
12
1
0
2
12
1
0
3
13
0
1
2
13
1
1
fiddle

You could use below solution to get the job done
select ID_1, ID_2
, decode(Value_A, null, 0, 1) Value_A
, decode(Value_B, null, 0, 1) Value_B
from your_Table t
pivot (
max(Value) for value in (
'A' as Value_A
, 'B' as Value_B
)
)
order by ID_1, ID_2
;
demo

Related

LISTAG function in oracle giving duplicate values

I have two tables User_details and Level_details.
User_details table:
ID Name
1 A
2 B
3 C
4 D
5 E
Level_details table:
trns_id Lvl usr_id
66 1 1
66 1 5
77 1 2
77 2 3
66 2 4
66 2 3
77 2 3
66 2 4
I am getting the result like:
trns_id Lvl name
66 1 A, E
66 2 D, C, D
77 1 B
77 2 C, C
I am using LISTAG function to get name
LISTAGG(( SELECT name FROM User_details l WHERE l.usr_id = id and trns_id=t1.trns_id and lvl=t1.lvl ), ',') WITHIN GROUP( ORDER BY lvl ) AS Name
You can use the distinct modifier in a listagg function call:
SELECT trns_id, lvl, LISTAGG(DISTINCT name, ', ') WITHIN GROUP (ORDER BY name)
FROM level_details l
JOIN user_details u ON l.usr_id = u.id
GROUP BY trns_id, lvl
If your database version doesn't support DISTINCT within LISTAGG, then you'll have to first select distinct values (lines #21 - 23), then aggregate them (line #20). Lines #1 - 17 represent sample data; you already have that and don't type it. Query you need begins at line #18.
SQL> with user_details (usr_id, name) as
2 (select 1, 'A' from dual union all
3 select 2, 'B' from dual union all
4 select 3, 'C' from dual union all
5 select 4, 'D' from dual union all
6 select 5, 'E' from dual
7 ),
8 level_details (trns_id, lvl, usr_id) as
9 (select 66, 1, 1 from dual union all
10 select 66, 1, 5 from dual union all
11 select 77, 1, 2 from dual union all
12 select 77, 2, 3 from dual union all
13 select 66, 2, 4 from dual union all
14 select 66, 2, 3 from dual union all
15 select 77, 2, 3 from dual union all
16 select 66, 2, 4 from dual
17 )
18 select x.trns_id,
19 x.lvl,
20 listagg(x.name, ', ') within group (order by x.lvl) name
21 from (select distinct u.usr_id, u.name, d.trns_id, d.lvl
22 from user_details u join level_details d on d.usr_id = u.usr_id
23 ) x
24 group by x.trns_id,
25 x.lvl;
TRNS_ID LVL NAME
---------- ---------- ---------------
66 1 A, E
66 2 C, D
77 1 B
77 2 C
SQL>
LISTAGG gives duplicate values if you have duplicate values
trns_id Lvl usr_id
77 2 3
77 2 3
You can remove duplicates first:
select trns_id, Lvl, LISTAGG(name)
from (
select distinct l.trns_id l.Lvl, u.name
from User_details u
join Level_details l on l.usr_id=u.ID
)
group by trns_id, Lvl

How to get the nth string from the delimited strings using REGEXP_SUBSTR in Oracle

I have a string which is delimited by # and I want the third set / cell in the string.
For example:
select REGEXP_SUBSTR( 'abc#def##########xyz','[^#]+', 1,1,null) from dual;
Outputs: abc
select REGEXP_SUBSTR( 'abc#def##########xyz','[^#]+', 1,2,null) from dual;
Outputs: def
However
select REGEXP_SUBSTR( 'abc#def##########xyz','[^#]+', 1,3,null) from dual;
Outputs: xyz
But what I expect is to get null since the third cell between ## is empty.
This is a familiar issue. Use a different pattern as that answer suggests:
select REGEXP_SUBSTR( 'abc#def##########xyz','(.*?)(#|$)', 1, 1, null, 1) from dual;
abc
select REGEXP_SUBSTR( 'abc#def##########xyz','(.*?)(#|$)', 1, 2, null, 1) from dual;
def
select REGEXP_SUBSTR( 'abc#def##########xyz','(.*?)(#|$)', 1, 3, null, 1) from dual;
(null)
select REGEXP_SUBSTR( 'abc#def##########xyz','(.*?)(#|$)', 1, 12, null, 1) from dual;
xyz
Or get all of them at once with a hierarchical query (or recursive CTE):
select level as pos,
REGEXP_SUBSTR( 'abc#def##########xyz','(.*?)(#|$)', 1, level, null, 1) as result
from dual
connect by level <= regexp_count('abc#def##########xyz', '#') + 1;
POS RESULT
---------- --------------------
1 abc
2 def
3 (null)
4 (null)
5 (null)
6 (null)
7 (null)
8 (null)
9 (null)
10 (null)
11 (null)
12 xyz
12 rows selected.
How about SUBSTR + INSTR combination?
SQL> with test (col) as (select 'abc#def##########xyz' from dual)
2 select substr(col, instr(col, '#', 1, 2) + 1,
3 instr(col, '#', 1, 3) - instr(col, '#', 1, 2) - 1
4 ) third_string,
5 --
6 substr(col, instr(col, '#', 1, 1) + 1,
7 instr(col, '#', 1, 2) - instr(col, '#', 1, 1) - 1
8 ) second_string
9 from test;
THIRD_STRING SECOND_STRING
--------------- ---------------
def
SQL>
The second_string explained (a simpler case, as it actually returns something):
the first INSTR line finds the 2nd appearance of the # character
the second INSTR line finds the 3rd appearance of the # character and subtracts the 2nd appearance (so it creates a substring length)

Direct-Path INSERT query generates ORA-00918 error

can you please explain why the error ORA-00918 is generated while executing this query
INSERT INTO CLG_TEST_2 (CLG_TEST_2.record_id, CLG_TEST_2.chain_id,
CLG_TEST_2.chain_n,
CLG_TEST_2.contact_info)
select * from (
SELECT 1, 1, 0, '2222' from dual UNION ALL
SELECT 2, 2, 0, '4444' from dual UNION ALL
SELECT 3, 3, 0, '6666' from dual
)
Error at line 1
ORA-00918: column ambiguously defined
Script Terminated on line 2.
The issue is in the fact that you are using a select * over a query without giving aliases to the columns; this will work:
INSERT INTO CLG_TEST_2 (CLG_TEST_2.record_id,
CLG_TEST_2.chain_id,
CLG_TEST_2.chain_n,
CLG_TEST_2.contact_info)
select *
from (
SELECT 1 a, 1 b, 0 c, '2222' d from dual UNION ALL
SELECT 2 , 2 , 0 , '4444' from dual UNION ALL
SELECT 3 , 3 , 0 , '6666' from dual
)
However, you can simplify your code:
INSERT INTO CLG_TEST_2 (record_id, chain_id, chain_n, contact_info)
SELECT 1, 1, 0, '2222' from dual UNION ALL
SELECT 2, 2, 0, '4444' from dual UNION ALL
SELECT 3, 3, 0, '6666' from dual
Something more about the reason of the error.
Your code:
SQL> INSERT INTO CLG_TEST_2 (
2 CLG_TEST_2.record_id,
3 CLG_TEST_2.chain_id,
4 CLG_TEST_2.chain_n,
5 CLG_TEST_2.contact_info)
6 select * from (
7 SELECT 1, 1, 0, '2222' from dual UNION ALL
8 SELECT 2, 2, 0, '4444' from dual UNION ALL
9 SELECT 3, 3, 0, '6666' from dual
10 );
select * from (
*
ERROR at line 6:
ORA-00918: column ambiguously defined
Slightly different:
SQL> INSERT INTO CLG_TEST_2 (
2 CLG_TEST_2.record_id,
3 CLG_TEST_2.chain_id,
4 CLG_TEST_2.chain_n,
5 CLG_TEST_2.contact_info)
6 select * from (
7 SELECT 1, 2, 0, '2222' from dual UNION ALL
8 SELECT 2, 2, 0, '4444' from dual UNION ALL
9 SELECT 3, 3, 0, '6666' from dual
10 );
3 rows created.
What's different?
In the first row, I changed
SELECT 1, 1, 0, '2222' --> SELECT 1, 2, 0, '2222'
^ ^
The reason:
SQL> SELECT 1, 2, 0, '2222' from dual UNION ALL
2 SELECT 2, 2, 0, '4444' from dual UNION ALL
3 SELECT 3, 3, 0, '6666' from dual;
1 2 0 '222
---------- ---------- ---------- ----
1 2 0 2222
2 2 0 4444
3 3 0 6666
SQL> SELECT 1, 1, 0, '2222' from dual UNION ALL
2 SELECT 2, 2, 0, '4444' from dual UNION ALL
3 SELECT 3, 3, 0, '6666' from dual;
1 1 0 '222
---------- ---------- ---------- ----
1 1 0 2222
2 2 0 4444
3 3 0 6666
SQL>
Here you have two columns with the same alias '1', and this is confusing for the external select *.
Also, a direct-path insert is something different
I don't see any "Direct-Path" insert. Anyway, try this one
INSERT INTO CLG_TEST_2 (CLG_TEST_2.record_id, CLG_TEST_2.chain_id,
CLG_TEST_2.chain_n,
CLG_TEST_2.contact_info)
SELECT 1, 1, 0, '2222' from dual UNION ALL
SELECT 2, 2, 0, '4444' from dual UNION ALL
SELECT 3, 3, 0, '6666' from dual
btw, why do you use string from numbers?

Fetch recursive tree with only certain elements "expanded"

We have a table with a self-referencing tree structure (id, parent_id). Let's assume the following tree structure:
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
I'd like to fetch this data for displaying it in a tree. But only certain records expanded. I'm currently using the following query:
SELECT ID, NAME "PATH"
FROM GROUPS
WHERE PRIOR ID IN(1, 4)
CONNECT BY PARENT_ID = PRIOR ID
START WITH PARENT_ID IS NULL;
This works very well and returns the following records:
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
The problem is that this query returns every record of which the direct parent is expanded, but not the whole parent chain. So if we're just expanding id 4, records 5, 6, 7 still shouldn't be returned as 1 is not expanded.
What I have been trying so far is to fetch a custom column which indicates whether the element is expanded, which computes out of whether it is explicitly expanded AND the parent is expanded as well.
SELECT ...
CASE WHEN (ID IN (4) AND PRIOR EXPANDED = 1) THEN 1 ELSE 0 end "EXPANDED"
...
WHERE "EXPANDED" = 1
This does not work as I can use the EXPANDED alias neither in the WHERE statement nor the PRIOR EXPANDED statement.
Is there a simple way of achieving this using a simple query?
Oracle Setup:
CREATE TABLE hierarchy ( id, parent_id ) AS
SELECT 1, NULL FROM DUAL UNION ALL
SELECT 2, 1 FROM DUAL UNION ALL
SELECT 3, 2 FROM DUAL UNION ALL
SELECT 4, 1 FROM DUAL UNION ALL
SELECT 5, 4 FROM DUAL UNION ALL
SELECT 6, 5 FROM DUAL UNION ALL
SELECT 7, NULL FROM DUAL UNION ALL
SELECT 8, 7 FROM DUAL UNION ALL
SELECT 9, 8 FROM DUAL UNION ALL
SELECT 10, 9 FROM DUAL UNION ALL
SELECT 11, 8 FROM DUAL;
Query - IN clause has all parents explanded:
SELECT LPAD( '+ ', LEVEL*2, ' ' ) || id
FROM hierarchy
START WITH parent_id IS NULL
CONNECT BY PRIOR id = parent_id
AND parent_id IN ( 1, 2, 4, 5, 7, 8, 9 );
Output:
+ 1
+ 2
+ 3
+4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
Query - IN clause has all parents expanded except 4 and 8:
SELECT LPAD( '+ ', LEVEL*2, ' ' ) || id
FROM hierarchy
START WITH parent_id IS NULL
CONNECT BY PRIOR id = parent_id
AND parent_id IN ( 1, 2, 5, 7, 9 );
Output:
+ 1
+ 2
+ 3
+4
+ 7
+ 8
Update - Showing leaf nodes:
SELECT LPAD( '+ ', LEVEL*2, ' ' ) || id AS value,
isleaf
FROM (
-- Find the leaves first (as if all parents are expanded)
SELECT h.*,
CONNECT_BY_ISLEAF AS isLeaf
FROM hierarchy h
START WITH parent_id IS NULL
CONNECT BY PRIOR id = parent_id
)
START WITH parent_id IS NULL
CONNECT BY PRIOR id = parent_id
AND parent_id IN ( 1, 2, 4, 7, 9 );
Output:
VALUE ISLEAF
---------------- ----------
+ 1 0
+ 2 0
+ 3 1
+ 4 0
+ 5 0
+ 7 0
+ 8 0
1 Indicates that the node has no children and 0 indicates that the node has children (even though they might not be expanded).
OK, just saw your note about requiring the entire parent chain to be expanded. The following does this by using sys_connect_by_path to build that chain, then checking that it is all 1's (have to lop off the last node which is at the current level):
With 1 and 4 expanded you get:
WITH hier as (
SELECT 1 id, NULL parent_id, 1 expanded FROM DUAL UNION ALL
SELECT 2, 1, 0 FROM DUAL UNION ALL
SELECT 3, 2, 0 FROM DUAL UNION ALL
SELECT 4, 1, 1 FROM DUAL UNION ALL
SELECT 5, 4, 0 FROM DUAL UNION ALL
SELECT 6, 4, 0 FROM DUAL UNION ALL
SELECT 7, 4, 0 FROM DUAL UNION ALL
SELECT 8, 1, 0 FROM DUAL UNION ALL
SELECT 9, 1, 0 FROM DUAL UNION ALL
SELECT 10, 9, 0 FROM DUAL UNION ALL
SELECT 11, 9, 0 FROM DUAL )
SELECT LPAD( '+ ', lvl*2, ' ' ) || id
FROM (
SELECT ID
, parent_id
, level as lvl
, sys_connect_by_path(expanded,'-') as path_expanded
FROM hier
CONNECT BY PARENT_ID = PRIOR ID
START WITH PARENT_ID IS NULL
)
WHERE --every node in the path from the parent is expanded.
instr(substr(path_expanded,1,length(path_expanded)-2),'0') = 0
OR parent_id is null ;
LPAD('+',LVL*2,'')||ID
+ 1
+ 2
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
Change to un-expand at node 1 and you get:
WITH hier as (
SELECT 1 id, NULL parent_id, 0 expanded FROM DUAL UNION ALL
SELECT 2, 1, 0 FROM DUAL UNION ALL
SELECT 3, 2, 0 FROM DUAL UNION ALL
SELECT 4, 1, 1 FROM DUAL UNION ALL
SELECT 5, 4, 0 FROM DUAL UNION ALL
SELECT 6, 4, 0 FROM DUAL UNION ALL
SELECT 7, 4, 0 FROM DUAL UNION ALL
SELECT 8, 1, 0 FROM DUAL UNION ALL
SELECT 9, 1, 0 FROM DUAL UNION ALL
SELECT 10, 9, 0 FROM DUAL UNION ALL
SELECT 11, 9, 0 FROM DUAL )
SELECT LPAD( '+ ', lvl*2, ' ' ) || id
FROM (
SELECT ID
, parent_id
, level as lvl
, sys_connect_by_path(expanded,'-') as path_expanded
FROM hier
CONNECT BY PARENT_ID = PRIOR ID
START WITH PARENT_ID IS NULL
)
WHERE --every node in the path from the parent is expanded.
instr(substr(path_expanded,1,length(path_expanded)-2),'0') = 0
OR parent_id is null ;
LPAD('+',LVL*2,'')||ID
+ 1

transpose row into a single column with pivot or decode in dual table - Oracle

If i give this query,
Select 1,2,3,4,5,6,7,8,9 from dual;
It will look like this
1 2 3 4 5 6 7 8 9 - column names
1 2 3 4 5 6 7 8 9 - Associated values
But i want to show like this
1
2
3
4
5
6
7
8
9
I don't know how to do this with dual table
"Unpivot version":
select val from (select 1, 2, 3, 4, 5, 6, 7, 8, 9 from dual)
unpivot (val for tmp in ("1", "2", "3", "4", "5", "6", "7", "8", "9"))
Simpler alternative giving the same results:
select * from table(sys.odcinumberlist(1, 2, 3, 4, 5, 6, 7, 8, 9))
Just use union all:
select 1 as name from dual union all
select 2 as name from dual union all
select 3 as name from dual union all
select 4 as name from dual union all
select 5 as name from dual union all
select 6 as name from dual union all
select 7 as name from dual union all
select 8 as name from dual union all
select 9 as name from dual;

Resources