Oracle comma seperated to rows with grouping - oracle

I have the following requirement.
Do we have direct functions available in oracle 12c to accomplish this.
create table t1(input_name varchar2(500),input_values varchar2(500));
insert into t1 values('a,b,c,d,','1,2,3,4');
insert into t1 values('e,f,g,','5,6,7');
insert into t1 values('a1,b1,c1,d1,','11,12,13,14');
insert into t1 values('d,c,b,a,','100,200,300,400');
commit;
select * from t1;
INPUT_NAME INPUT_VALUES
------------------------------ ----------------
a,b,c,d, 1,2,3,4
e,f,g, 5,6,7
a1,b1,c1,d1, 11,12,13,14
d,c,b,a, 100,200,300,400
output:
a b c d e f g a1 b1 c1 d1
1 2 3 4 5 6 7 11 12 13 14
400 300 200 100
Thanks,
Rahmat Ali

Yes... if you have a known set of input names. But you would be better reorganising your data so that you are not storing correlated pairs of comma-separated lists.
SQL Fiddle
Oracle 11g R2 Schema Setup:
create table t1(input_name,input_values) AS
SELECT 'a,b,c,d,','1,2,3,4' FROM DUAL UNION ALL
SELECT 'e,f,g,','5,6,7' FROM DUAL UNION ALL
SELECT 'a1,b1,c1,d1,','11,12,13,14' FROM DUAL UNION ALL
SELECT 'd,c,b,a,','100,200,300,400' FROM DUAL
/
CREATE TYPE pair IS OBJECT(
name VARCHAR2(20),
value VARCHAR2(20)
)
/
CREATE TYPE pair_table IS TABLE OF PAIR
/
Query 1:
SELECT MAX( CASE name WHEN 'a' THEN value END ) AS a,
MAX( CASE name WHEN 'b' THEN value END ) AS b,
MAX( CASE name WHEN 'c' THEN value END ) AS c,
MAX( CASE name WHEN 'd' THEN value END ) AS d,
MAX( CASE name WHEN 'e' THEN value END ) AS e,
MAX( CASE name WHEN 'f' THEN value END ) AS f,
MAX( CASE name WHEN 'g' THEN value END ) AS g,
MAX( CASE name WHEN 'a1' THEN value END ) AS a1,
MAX( CASE name WHEN 'b1' THEN value END ) AS b1,
MAX( CASE name WHEN 'c1' THEN value END ) AS c1,
MAX( CASE name WHEN 'd1' THEN value END ) AS d1
FROM (
SELECT v.name,
v.value,
ROW_NUMBER() OVER ( PARTITION BY v.name ORDER BY ROWNUM ) AS rn
FROM t1 t
CROSS JOIN
TABLE(
CAST(
MULTISET(
SELECT pair(
REGEXP_SUBSTR( t.input_name, '([^,]+)(,|$)', 1, LEVEL, NULL, 1 ),
REGEXP_SUBSTR( t.input_values, '([^,]+)(,|$)', 1, LEVEL, NULL, 1 )
)
FROM DUAL
CONNECT BY level <= REGEXP_COUNT( t.input_name, '([^,]+)(,|$)' )
) AS pair_table
)
) v
)
GROUP BY rn
Results:
| A | B | C | D | E | F | G | A1 | B1 | C1 | D1 |
|-----|-----|-----|-----|--------|--------|--------|--------|--------|--------|--------|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 |
| 400 | 300 | 200 | 100 | (null) | (null) | (null) | (null) | (null) | (null) | (null) |
You can also use a PIVOT statement rather than multiple MAX( CASE ... END ) statements.
If you do not have a static set of input names then you will need to search for dynamic pivots.
Update:
Is there a way where I can avoid using types?
You can avoid creating types and just use a built-in VARRAY or collection like SYS.ODCIVARCHAR2LIST but then you will need two lists and it becomes complicated to correlate between the two.
WITH input_names ( rid, idx, name ) AS (
SELECT t.ROWID,
ROW_NUMBER() OVER ( PARTITION BY t.ROWID ORDER BY ROWNUM ) AS rn,
v.COLUMN_VALUE
FROM t1 t
CROSS JOIN
TABLE(
CAST(
MULTISET(
SELECT REGEXP_SUBSTR( t.input_name, '([^,]+)(,|$)', 1, LEVEL, NULL, 1 )
FROM DUAL
CONNECT BY level <= REGEXP_COUNT( t.input_name, '([^,]+)(,|$)' )
) AS SYS.ODCIVARCHAR2LIST
)
) v
),
input_values ( rid, idx, value ) AS (
SELECT t.ROWID,
ROW_NUMBER() OVER ( PARTITION BY t.ROWID ORDER BY ROWNUM ) AS rn,
v.COLUMN_VALUE
FROM t1 t
CROSS JOIN
TABLE(
CAST(
MULTISET(
SELECT REGEXP_SUBSTR( t.input_values, '([^,]+)(,|$)', 1, LEVEL, NULL, 1 )
FROM DUAL
CONNECT BY level <= REGEXP_COUNT( t.input_values, '([^,]+)(,|$)' )
) AS SYS.ODCIVARCHAR2LIST
)
) v
),
correlated ( name, value, rn ) AS (
SELECT n.name,
v.value,
ROW_NUMBER() OVER ( PARTITION BY n.name
ORDER BY ROWNUM )
FROM input_names n
INNER JOIN
input_values v
ON ( n.rid = v.rid AND n.idx = v.idx )
)
SELECT MAX( CASE name WHEN 'a' THEN value END ) AS a,
MAX( CASE name WHEN 'b' THEN value END ) AS b,
MAX( CASE name WHEN 'c' THEN value END ) AS c,
MAX( CASE name WHEN 'd' THEN value END ) AS d,
MAX( CASE name WHEN 'e' THEN value END ) AS e,
MAX( CASE name WHEN 'f' THEN value END ) AS f,
MAX( CASE name WHEN 'g' THEN value END ) AS g,
MAX( CASE name WHEN 'a1' THEN value END ) AS a1,
MAX( CASE name WHEN 'b1' THEN value END ) AS b1,
MAX( CASE name WHEN 'c1' THEN value END ) AS c1,
MAX( CASE name WHEN 'd1' THEN value END ) AS d1
FROM correlated
GROUP BY rn;

Related

multiple columns' row split in oracle

I got a single string row split into rows
For example,
A,B,C,D,E
into
A
B
C
D
E
but what I would like to is multiple columns' single row
| A,B,C | H,I,J,K,L | Q,R,X,Y,Z |
into
A | H | Q
B | I | R
C | J | X
| K | Y
| L |
How can I do this in oracle?
You can use hiearchy query as follows:
SQL> WITH DATAA ( D ) AS (
2 SELECT '| A,B,C | H,I,J,K,L | Q,R,X,Y,Z |'
3 FROM DUAL
4 )
5 -- your query starts from here
6 SELECT TRIM(REGEXP_SUBSTR(REGEXP_SUBSTR(D.D, '[^|]+', 1, 1), '[^,]+', 1, LEVEL)) AS COL1,
7 TRIM(REGEXP_SUBSTR(REGEXP_SUBSTR(D.D, '[^|]+', 1, 2), '[^,]+', 1, LEVEL)) AS COL2,
8 TRIM(REGEXP_SUBSTR(REGEXP_SUBSTR(D.D, '[^|]+', 1, 3), '[^,]+', 1, LEVEL)) AS COL3
9 FROM DATAA D
10 CONNECT BY LEVEL <= (
11 SELECT MAX(REGEXP_COUNT((REGEXP_SUBSTR(D.D, '[^|]+', 1, COLUMN_VALUE)), ',')) + 1
12 FROM DATAA D
13 CROSS JOIN TABLE ( CAST(MULTISET(
14 SELECT LEVEL LVL
15 FROM DUAL
16 CONNECT BY LEVEL <= REGEXP_COUNT(D.D, '[^|]+')
17 ) AS SYS.ODCIVARCHAR2LIST) ) LVLS
18 );
COL1 COL2 COL3
------- ----------- -----------
A H Q
B I R
C J X
K Y
L Z
SQL>

Oracle find the 1st node and last node in a path, CONNECT BY nocycle

I would like to know if there is anyway it return the beginning of the hierarchy and end of hierarchy for the code below
WITH o AS
(
SELECT 'A' as obj,
'C' as link
FROM dual
UNION ALL
SELECT 'C',
'D'
FROM dual
UNION ALL
SELECT 'D',
'E'
FROM dual
UNION ALL
SELECT 'X',
'Y'
FROM dual),
apath AS
(SELECT obj,
link
, sys_connect_by_path(obj || '->' || link, ',') pth
, connect_by_iscycle as cy
, connect_by_isleaf AS lf
, level
FROM o
CONNECT BY nocycle obj = PRIOR link)
SELECT *
FROM apath
where lf = 1
order by pth
I would like have the result like
Begin_Node, End_Node
A E
C E
D E
X Y
Current code returns
D E ,A->C,C->D,D->E 0 1 3
D E ,C->D,D->E 0 1 2
D E ,D->E 0 1 1
X Y ,X->Y 0 1 1
Thanks in advance.
Just use CONNECT_BY_ROOT and filter by CONNECT_BY_ISLEAF:
WITH o ( obj, link ) AS (
SELECT 'A', 'C' FROM DUAL UNION ALL
SELECT 'C', 'D' FROM DUAL UNION ALL
SELECT 'D', 'E' FROM DUAL UNION ALL
SELECT 'X', 'Y' FROM DUAL
)
SELECT CONNECT_BY_ROOT( obj ) AS begin_node,
link AS end_node
FROM o
WHERE CONNECT_BY_ISLEAF = 1
CONNECT BY nocycle obj = PRIOR link;
Outputs:
BEGIN_NODE | END_NODE
:--------- | :-------
A | E
C | E
D | E
X | Y
If you want all the columns then:
WITH o ( obj, link ) AS (
SELECT 'A', 'C' FROM DUAL UNION ALL
SELECT 'C', 'D' FROM dual UNION ALL
SELECT 'D', 'E' FROM dual UNION ALL
SELECT 'X', 'Y' FROM dual
)
SELECT CONNECT_BY_ROOT( obj ) AS begin_node,
link AS end_node,
SYS_CONNECT_BY_PATH( obj || '->' || link, ',' ) AS path,
CONNECT_BY_ISCYCLE as iscycle,
CONNECT_BY_ISLEAF AS isleaf,
level
FROM o
WHERE CONNECT_BY_ISLEAF = 1
CONNECT BY nocycle obj = PRIOR link;
Which outputs:
BEGIN_NODE | END_NODE | PATH | ISCYCLE | ISLEAF | LEVEL
:--------- | :------- | :-------------- | ------: | -----: | ----:
A | E | ,A->C,C->D,D->E | 0 | 1 | 3
C | E | ,C->D,D->E | 0 | 1 | 2
D | E | ,D->E | 0 | 1 | 1
X | Y | ,X->Y | 0 | 1 | 1
db<>fiddle here
If you're happy with what pth represents, it is simple to extract that info from it:
line #21: remove leading comma
lines #27 and #28 return begin and end nodes
<snip>
18 apath AS
19 (SELECT obj,
20 link
21 , ltrim(sys_connect_by_path(obj || '->' || link, ','), ',') pth
22 , connect_by_iscycle as cy
23 , connect_by_isleaf AS lf
24 , level
25 FROM o
26 CONNECT BY nocycle obj = PRIOR link)
27 SELECT regexp_substr(pth, '^\w+') begin_node,
28 regexp_substr(pth, '\w+$') end_node
29 FROM apath
30 where lf = 1
31 order by pth
32 /
BEGIN_NODE END_NODE
---------- ----------
A E
C E
D E
X Y
SQL>

Age analysis on excel

I would like to distribute a set of data into a table shown below:
RANGE(days) No of days. Amount
0-30 - 0
31-180 1 4,185.78
181-365 2 74,056.86
366 and above 6 587,198.35
TOTAL 9 665,440.99
SOURCE Data
S/N START DATE Details Tran Amt END DATE
1 22/05/2015 A 448,749.14 30/06/2018
2 22/09/2015 B 4,883.02 30/06/2018
3 04/11/2015 C 45,646.27 30/06/2018
4 26/04/2016 D 42,861.99 30/06/2018
5 16/06/2016 E 23,144.23 30/06/2018
6 27/07/2016 F 21,913.70 30/06/2018
7 11/08/2017 G 61,396.94 30/06/2018
8 30/11/2017 H 12,659.92 30/06/2018
9 19/03/2018 I 4,185.78 30/06/2018
TOTAL 665,440.99
Thanks
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE data ( SN, START_DATE, Details, TranAmt ) AS
SELECT 1, DATE '2015-05-22', 'A', 448749.14 FROM DUAL UNION ALL
SELECT 2, DATE '2015-09-22', 'B', 4883.02 FROM DUAL UNION ALL
SELECT 3, DATE '2015-11-04', 'C', 45646.27 FROM DUAL UNION ALL
SELECT 4, DATE '2016-04-26', 'D', 42861.99 FROM DUAL UNION ALL
SELECT 5, DATE '2016-06-16', 'E', 23144.23 FROM DUAL UNION ALL
SELECT 6, DATE '2016-07-27', 'F', 21913.70 FROM DUAL UNION ALL
SELECT 7, DATE '2017-08-11', 'G', 61396.94 FROM DUAL UNION ALL
SELECT 8, DATE '2017-11-30', 'H', 12659.92 FROM DUAL UNION ALL
SELECT 9, DATE '2018-03-19', 'I', 4185.78 FROM DUAL;
Query 1:
WITH end_date ( end_date ) AS (
SELECT DATE '2018-06-30' FROM DUAL
),
ranges ( first, last ) AS (
SELECT 0, 30 FROM DUAL UNION ALL
SELECT 31, 180 FROM DUAL UNION ALL
SELECT 181, 365 FROM DUAL UNION ALL
SELECT 366, NULL FROM DUAL
)
SELECT NVL2(
r.first,
r.first || NVL2(
MAX( r.last ),
' - ' || MAX( r.last ),
' and above'
),
'Total'
) AS range,
COUNT( d.sn ) AS "No of days",
COALESCE( SUM( d.TranAmt ), 0 ) AS Amount
FROM data d
CROSS JOIN end_date e
RIGHT OUTER JOIN ranges r
ON ( d.start_date < e.end_date + 1 - r.first
AND ( r.last IS NULL
OR d.start_date >= e.end_date - r.last ) )
GROUP BY ROLLUP( r.first )
ORDER BY r.first
Results:
| RANGE | No of days | AMOUNT |
|---------------|------------|-----------|
| 0 - 30 | 0 | 0 |
| 31 - 180 | 1 | 4185.78 |
| 181 - 365 | 2 | 74056.86 |
| 366 and above | 6 | 587198.35 |
| Total | 9 | 665440.99 |

split into rows and columns Oracle

I am creating a function that returns a table type object based on the split of the chain, the query is the following:
WITH COLUMNA AS (
SELECT ROWNUM COL_ID, REGEXP_SUBSTR ('A,B,C:D,E,F:','[^:]+',1,LEVEL) COL FROM DUAL
CONNECT BY REGEXP_SUBSTR ('A,B,C:D,E,F:','[^:]+',1,LEVEL) IS NOT NULL
ORDER BY COL_ID
)
SELECT * FROM (SELECT COL_ID, ROWNUM FIL_ID, SUBSTR(COL, INSTR(COL, ',', 1, LVL) + 1, INSTR(COL, ',', 1, LVL + 1) - INSTR(COL, ',', 1, LVL) - 1) NAME
FROM
( SELECT ',' || COL || ',' AS COL, COL_ID FROM COLUMNA ),
( SELECT LEVEL AS LVL FROM DUAL CONNECT BY LEVEL <= 100 )
WHERE LVL <= LENGTH(COL) - LENGTH(REPLACE(COL, ',')) - 1
ORDER BY COL_ID, NAME
) FILA
The result is as follows:
COL_ID FIL_ID NAME
1 1 A
1 2 B
1 3 C
2 4 D
2 5 E
2 6 F
And I Need To Get The Following Result
COL_ID VAL1 VAL2 VAL3 VALN
1 A B C X
2 D E F Y
I hope your valuable help!!!
You need to have a fixed number of columns in your object:
CREATE TYPE values_obj AS OBJECT(
COL_id INTEGER,
VAL1 VARCHAR2(10),
VAL2 VARCHAR2(10),
VAL3 VARCHAR2(10),
VAL4 VARCHAR2(10),
VAL5 VARCHAR2(10)
)
/
CREATE TYPE values_tab AS TABLE OF values_obj
/
CREATE OR REPLACE FUNCTION split_values(
in_list VARCHAR2
) RETURN values_tab
IS
vals VALUES_TAB;
BEGIN
SELECT values_obj(
LEVEL,
REGEXP_SUBSTR( in_list, '([^:,]*),?([^:,]*),?([^:,]*),?([^:,]*),?([^:,]*).*?(:|$)', 1, LEVEL, NULL, 1 ),
REGEXP_SUBSTR( in_list, '([^:,]*),?([^:,]*),?([^:,]*),?([^:,]*),?([^:,]*).*?(:|$)', 1, LEVEL, NULL, 2 ),
REGEXP_SUBSTR( in_list, '([^:,]*),?([^:,]*),?([^:,]*),?([^:,]*),?([^:,]*).*?(:|$)', 1, LEVEL, NULL, 3 ),
REGEXP_SUBSTR( in_list, '([^:,]*),?([^:,]*),?([^:,]*),?([^:,]*),?([^:,]*).*?(:|$)', 1, LEVEL, NULL, 4 ),
REGEXP_SUBSTR( in_list, '([^:,]*),?([^:,]*),?([^:,]*),?([^:,]*),?([^:,]*).*?(:|$)', 1, LEVEL, NULL, 5 )
)
BULK COLLECT INTO vals
FROM DUAL
CONNECT BY LEVEL < REGEXP_COUNT( in_list, '([^:,]*),?([^:,]*),?([^:,]*),?([^:,]*),?([^:,]*).*?(:|$)' );
RETURN vals;
END;
/
Then you can do:
SELECT *
FROM TABLE( split_values( 'A,B,C:D,E,F,G:H,I,J,K,L,M::N' ) );
Which outputs:
COL_ID VAL1 VAL2 VAL3 VAL4 VAL5
------ ---- ---- ---- ---- ----
1 A B C - -
2 D E F G -
3 H I J K L
4 - - - - -
5 N - - - -

how can sumvalue and addition null

How can I sum NULL values?
For example :
select sum(nvl(a,0) + nvl(b,0)) s, c from
(
select 1 a, null b, 'A' c from dual union
select null, null b , 'c' c from dual
)
group by c
Expected result: when a is NULL, b is NULL then sum(nvl(a,0) + nvl(b,0)) is NULL. How can I do this ?
Use a CASE statement to detect when both A and B are NULL:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE sample_data ( a, b, c ) AS
SELECT 1, CAST( NULL AS NUMBER ), 'A' FROM DUAL UNION ALL
SELECT NULL, NULL, 'C' FROM DUAL
Query 1:
SELECT SUM(
CASE
WHEN a IS NULL AND b IS NULL
THEN NULL
ELSE nvl(a,0) + nvl(b,0)
END
) AS s,
c
FROM sample_data
GROUP BY c
Results:
| S | C |
|--------|---|
| 1 | A |
| (null) | C |
You can try:
select Case when (sum(nvl(a,0) + nvl(b,0))) = 0 then null
else sum(nvl(a,0) + nvl(b,0)) end s, c
from
(
select 1 a, null b, 'A' c from dual union
select null, null b , 'c' c from dual
)
group by c

Resources