Oracle Hierarchical Query to get different levels into different columns - oracle

I've source data Like this
Childid | Parent ID
------- | ---------
1 | NULL
2 | 1
3 | 1
4 | 2
5 | 4
6 | 5
7 | 6
I need an oracle query to show the out put like this.
Child | L1Parent | l2Parent | L3Parent | L4Parent | L5Parent
----- | -------- | -------- | -------- | -------- | --------
1 | NULL
2 | 1
3 | 1
4 | 1 | 2
5 | 1 | 2 | 4
6 | 1 | 2 | 4 | 5
7 | 1 | 2 | 4 | 5 | 6

This seems to me easier:
SELECT childId,
trim(SYS_CONNECT_BY_PATH(decode(level,2,parentId,''), ' ')) AS L1,
trim(SYS_CONNECT_BY_PATH(decode(level,3,parentId,''), ' ')) AS L2,
trim(SYS_CONNECT_BY_PATH(decode(level,4,parentId,''), ' ')) AS L3,
trim(SYS_CONNECT_BY_PATH(decode(level,5,parentId,''), ' ')) AS L4,
trim(SYS_CONNECT_BY_PATH(decode(level,6,parentId,''), ' ')) AS L5,
trim(SYS_CONNECT_BY_PATH(decode(level,7,parentId,''), ' ')) AS L6,
trim(SYS_CONNECT_BY_PATH(decode(level,8,parentId,''), ' ')) AS L7
FROM table_name
START WITH parentId is null
CONNECT BY PRIOR childId = parentId;

Oracle Setup:
CREATE TABLE table_name ( childId, parentId ) AS
SELECT 1, NULL FROM DUAL UNION ALL
SELECT 2, 1 FROM DUAL UNION ALL
SELECT 3, 1 FROM DUAL UNION ALL
SELECT 4, 2 FROM DUAL UNION ALL
SELECT 5, 4 FROM DUAL UNION ALL
SELECT 6, 5 FROM DUAL UNION ALL
SELECT 7, 6 FROM DUAL;
Query:
SELECT childId,
CASE WHEN p02 = 1 THEN NULL WHEN p03 = 1 THEN SUBSTR( path, p02 ) ELSE SUBSTR( path, p02, p03 - p02 - 1 ) END AS Lp1,
CASE WHEN p03 = 1 THEN NULL WHEN p04 = 1 THEN SUBSTR( path, p03 ) ELSE SUBSTR( path, p03, p04 - p03 - 1 ) END AS lp2,
CASE WHEN p04 = 1 THEN NULL WHEN p05 = 1 THEN SUBSTR( path, p04 ) ELSE SUBSTR( path, p04, p05 - p04 - 1 ) END AS lp3,
CASE WHEN p05 = 1 THEN NULL WHEN p06 = 1 THEN SUBSTR( path, p05 ) ELSE SUBSTR( path, p05, p06 - p05 - 1 ) END AS lp4,
CASE WHEN p06 = 1 THEN NULL WHEN p07 = 1 THEN SUBSTR( path, p06 ) ELSE SUBSTR( path, p06, p07 - p06 - 1 ) END AS lp5,
CASE WHEN p07 = 1 THEN NULL WHEN p08 = 1 THEN SUBSTR( path, p07 ) ELSE SUBSTR( path, p07, p08 - p07 - 1 ) END AS lp6
FROM (
SELECT childId,
path,
INSTR( path, '/', 1, 2 ) + 1 AS p02,
INSTR( path, '/', 1, 3 ) + 1 AS p03,
INSTR( path, '/', 1, 4 ) + 1 AS p04,
INSTR( path, '/', 1, 5 ) + 1 AS p05,
INSTR( path, '/', 1, 6 ) + 1 AS p06,
INSTR( path, '/', 1, 7 ) + 1 AS p07,
INSTR( path, '/', 1, 8 ) + 1 AS p08
FROM (
SELECT childId,
SYS_CONNECT_BY_PATH( parentId, '/') AS path
FROM table_name
START WITH parentId IS NULL
CONNECT BY PRIOR childId = parentId
)
);
Output:
CHILDID LP1 LP2 LP3 LP4 LP5 LP6
---------- --- --- --- --- --- ---
1
2 1
4 1 2
5 1 2 4
6 1 2 4 5
7 1 2 4 5 6
3 1
Query 2 - Regular Expressions:
SELECT childId,
TO_NUMBER( REGEXP_SUBSTR( path, '/([^/]*)', 1, 2, NULL, 1 ) ) AS lp1,
TO_NUMBER( REGEXP_SUBSTR( path, '/([^/]*)', 1, 3, NULL, 1 ) ) AS lp2,
TO_NUMBER( REGEXP_SUBSTR( path, '/([^/]*)', 1, 4, NULL, 1 ) ) AS lp3,
TO_NUMBER( REGEXP_SUBSTR( path, '/([^/]*)', 1, 5, NULL, 1 ) ) AS lp4,
TO_NUMBER( REGEXP_SUBSTR( path, '/([^/]*)', 1, 6, NULL, 1 ) ) AS lp5,
TO_NUMBER( REGEXP_SUBSTR( path, '/([^/]*)', 1, 7, NULL, 1 ) ) AS lp6,
TO_NUMBER( REGEXP_SUBSTR( path, '/([^/]*)', 1, 8, NULL, 1 ) ) AS lp7
FROM (
SELECT childId,
SYS_CONNECT_BY_PATH( parentId, '/') AS path
FROM table_name
START WITH parentId IS NULL
CONNECT BY PRIOR childId = parentId
);

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>

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 - - - -

Oracle 11g Sql convert date from blob field

I have a problem in converting a date value stored in a blob field in Oracle 11g sql command. When i execute the sql:
select dump(HIGH_VALUE) from all_tab_columns where COLUMN_NAME='TARIH'
i receive the following result;
Typ=23 Len=7: 120,116,3,6,1,1,1
I know that these numbers represent a date (not datetime), but i don't know how to extract the date from this result.
Thanks in advance,
Alper
Oracle stores dates in tables as 7-bytes
byte 1 - century + 100
byte 2 - (year MOD 100 ) + 100
byte 3 - month
byte 4 - day
byte 5 - hour + 1
byte 6 - minute + 1
byte 7 - seconds+ 1
So 120,116,3,6,1,1,1 converts to:
byte 1 - century = 120 - 100 = 20
byte 2 - year = 116 - 100 = 16
byte 3 - month = 3
byte 4 - day = 6
byte 5 - hour = 1 - 1 = 0
byte 6 - minute = 1 - 1 = 0
byte 7 - seconds = 1 - 1 = 0
So 2016-03-06T00:00:00
Oracle Setup:
CREATE TABLE file_upload ( file_blob BLOB );
INSERT INTO file_upload VALUES (
utl_raw.cast_to_raw(
CHR(120) || CHR(116) || CHR(3) || CHR(6) || CHR(1) || CHR(1) || CHR(1)
)
);
Query:
SELECT DUMP( DBMS_LOB.SUBSTR( file_blob, 7, 1 ) ) AS dmp,
TO_DATE(
TO_CHAR(
( ASCII( SUBSTR( chars, 1, 1 ) ) - 100 ) * 100
+ ASCII( SUBSTR( chars, 2, 1 ) ) - 100,
'0000'
)
|| TO_CHAR( ASCII( SUBSTR( chars, 3, 1 ) ), '00' )
|| TO_CHAR( ASCII( SUBSTR( chars, 4, 1 ) ), '00' )
|| TO_CHAR( ASCII( SUBSTR( chars, 5, 1 ) ) - 1, '00' )
|| TO_CHAR( ASCII( SUBSTR( chars, 6, 1 ) ) - 1, '00' )
|| TO_CHAR( ASCII( SUBSTR( chars, 7, 1 ) ) - 1, '00' ),
'YYYYMMDDHH24MISS'
) AS converted_date
FROM (
SELECT file_blob,
UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR( file_blob, 7, 1 ) ) AS chars
FROM file_upload
);
Output:
DMP CONVERTED_DATE
------------------------------- -------------------
Typ=23 Len=7: 120,116,3,6,1,1,1 2016-03-06 00:00:00

Oracle Self Join to retrieve recursive data

I have the following schema and data
segmentid paramid paramvalue
103 1 4418
101 1 4834
102 1 5587
104 1 7413
105 1 9965
106 1 7421
107 1 7782
103 2 1990|2000
102 2 2005|2010
105 2 1985|1990
104 2 1981
101 3 F
103 3 M
101 4 M
103 4 S
102 5 SUKKHUR
105 5 LAHORE
106 5 HYDRABAD
107 5 KHAIRPUR
101 5 ISLAMABAD
Now i will have inputs of different param values like Karachi M and date of birth range. I want to retrieve only that segment id whose all parameters are returned to be true.
If any parameter is failed the segment should fail.
Below is my idea but its returning if any paramvalue is true as ive used or but when i and the data is not retrieved.
select tpv.* from tblsegment ts , tblsegmentparameter tsp , tblsegmentparamvalue tpv
where ts.segmentid = tpv.segmentid and tsp.parameterid = tpv.paramid
and
(
(lower(tsp.paramname) like 'city' and tpv.paramvalue = 'KARACHI' and tsp.parameterid = tpv.paramid)
or
(lower(tsp.paramname) like 'gender' and tpv.paramvalue = 'M')
or
(lower(tsp.paramname) like 'maritalstatus' and tpv.paramvalue = 'S')
or
(lower(tsp.paramname) like 'product' and tpv.paramvalue = (select distinct ta.productid from tblcustchannelacct ta ,tblcustomer tc, tblaccount tta
where ta.relationship_id = '5327016301000015=5311' and ta.channel_id = '0001' and ta.account_id = tta.account_id and ta.customer_id = tc.customerid )
)
or
(lower(tsp.paramname) like 'dob' and
(
(
to_char( '1985') between
to_char( REGEXP_SUBSTR ( tpv.paramvalue, '^[^|]*'))
and
to_char(REGEXP_SUBSTR( tpv.paramvalue, '*[^|]*$'))
) or
(
to_char( '1986') = tpv.paramvalue
)
)
)
)
order by tsp.sortorder;
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE tblsegmentparamvalue ( segmentid, paramid, paramvalue ) AS
SELECT 103, 1, '4418' FROM DUAL
UNION ALL SELECT 101, 1, '4834' FROM DUAL
UNION ALL SELECT 102, 1, '5587' FROM DUAL
UNION ALL SELECT 104, 1, '7413' FROM DUAL
UNION ALL SELECT 105, 1, '9965' FROM DUAL
UNION ALL SELECT 106, 1, '7421' FROM DUAL
UNION ALL SELECT 107, 1, '7782' FROM DUAL
UNION ALL SELECT 103, 2, '1990|2000' FROM DUAL
UNION ALL SELECT 102, 2, '2005|2010' FROM DUAL
UNION ALL SELECT 105, 2, '1985|1990' FROM DUAL
UNION ALL SELECT 104, 2, '1981' FROM DUAL
UNION ALL SELECT 101, 3, 'F' FROM DUAL
UNION ALL SELECT 103, 3, 'M' FROM DUAL
UNION ALL SELECT 101, 4, 'M' FROM DUAL
UNION ALL SELECT 103, 4, 'S' FROM DUAL
UNION ALL SELECT 102, 5, 'SUKKHUR' FROM DUAL
UNION ALL SELECT 105, 5, 'LAHORE' FROM DUAL
UNION ALL SELECT 106, 5, 'HYDRABAD' FROM DUAL
UNION ALL SELECT 107, 5, 'KHAIRPUR' FROM DUAL
UNION ALL SELECT 101, 5, 'ISLAMABAD' FROM DUAL;
Query 1:
WITH segments AS (
SELECT segmentid
FROM tblsegmentparamvalue
GROUP BY segmentid
HAVING ( COUNT( CASE WHEN paramid = 1 THEN 1 END ) = 0
OR COUNT( CASE WHEN paramid = 1 AND paramvalue = '4834' THEN 1 END ) > 0 )
AND ( COUNT( CASE WHEN paramid = 2 THEN 1 END ) = 0
OR COUNT( CASE WHEN paramid = 2 AND '1985' BETWEEN SUBSTR( paramvalue, 1, 4 ) AND SUBSTR( paramvalue, -4 ) THEN 1 END ) > 0 )
AND ( COUNT( CASE WHEN paramid = 3 THEN 1 END ) = 0
OR COUNT( CASE WHEN paramid = 3 AND paramvalue = 'F' THEN 1 END ) > 0 )
AND ( COUNT( CASE WHEN paramid = 4 THEN 1 END ) = 0
OR COUNT( CASE WHEN paramid = 4 AND paramvalue = 'M' THEN 1 END ) > 0 )
AND ( COUNT( CASE WHEN paramid = 5 THEN 1 END ) = 0
OR COUNT( CASE WHEN paramid = 5 AND paramvalue = 'ISLAMABAD' THEN 1 END ) > 0 )
)
SELECT t.*
FROM tblsegmentparamvalue t
INNER JOIN
segments s
ON ( t.segmentid = s.segmentid )
ORDER BY
t.segmentid,
paramid
Results:
| SEGMENTID | PARAMID | PARAMVALUE |
|-----------|---------|------------|
| 101 | 1 | 4834 |
| 101 | 3 | F |
| 101 | 4 | M |
| 101 | 5 | ISLAMABAD |
This is an example how to get results for three paramters:
select segmentid, paramid, paramvalue
from (
select tpv.*, count(distinct paramid) over (partition by tpv.segmentid) cnt
from tblsegmentparamvalue tpv
join tblsegment ts on ts.segmentid = tpv.segmentid
join tblsegmentparameter tsp on tsp.parameterid = tpv.paramid
where (lower(tsp.paramname) like 'city' and tpv.paramvalue = 'KARACHI')
or (lower(tsp.paramname) like 'gender' and tpv.paramvalue = 'M')
or (lower(tsp.paramname) like 'martialstatus' and tpv.paramvalue = 'S') )
where cnt = 3
SQLFiddle demo
Add rest of parameters (dob, product) as you did it in your query and change last line to where cnt=5 to get full results.

Resources