splitting column in many rows gives too many rows - oracle

I am trying this statement
with value_table as
(select 1 id, '1/2/3' objnr from dual
union all
select 2, '4/5/6' from dual),
test as
(select id, objnr col from value_table where id in (1, 2))
select id, regexp_substr(col, '[^/]+', 1, level) result
from test
connect by level <= length(regexp_replace(col, '[^/]+')) + 1
order by 1
I want to get 6 rows
1 1
1 2
1 3
2 4
2 5
2 6
but I am getting the rows multiple times.
When I try it with just one id, it works without a problem.
As a workaround, I just made a loop for every single id, another solution is to use distinct, but that takes ages to execute, when I try it, with real data(over 1000 entries).
Can someone provide a more sophisticated solution?

This query is executed
with test as
(
select 1 id, '1/2/3' objnr from dual union all
select 2 id, '4/5/6' objnr from dual
)
select id, regexp_substr (objnr, '[^/]+', 1, rn) result
from test
cross
join (select rownum rn
from (select max (length (regexp_replace (objnr, '[^/]+'))) + 1 mx
from test
)
connect by level <= mx
)
where regexp_substr (objnr, '[^/]+', 1, rn) is not null
and id in (1, 2)
order by id,result ;
ID RESULT
1 1
1 2
1 3
2 4
2 5
2 6
If you use Oracle 11g, you can also use REGEXP_COUNT instead of the combination of REGEXP_REPLACE and LENGTH, which would look like this:
cross
join (select rownum rn
from (select max (regexp_count (objnr, '/') + 1) mx
from test
)
connect by level <= mx
)
to have outer join behaviour so made a slight variation as below:
with test as
(
select 1 id, '1/2/3' objnr from dual union all
select 2 id, '4/5/6' objnr from dual
)
select id, regexp_substr (objnr, '[^/]+', 1, rn) result
from test
left outer join (select rownum rn
from (select max (regexp_count (objnr, '/') + 1) mx
from test
)
connect by level <= mx
) splits
ON splits.rn <= length(regexp_replace (objnr, '[^/]+'))+1
and id in (1, 2)
order by id,result ;

try distinct as used below
with value_table as
(select 1 id, '1/2/3' objnr from dual
union all
select 2, '4/5/6' from dual),
test as
(select id, objnr col from value_table where id in (1, 2))
select distinct id, regexp_substr(col, '[^/]+', 1, level) result
from test
connect by level <= regexp_count(col, '[^/]+')
order by 1
tried with sqlfiddle http://sqlfiddle.com/#!4/03d80/14
EDIT:
If you don't want distinct try the below
with value_table as
(select 1 id, '1/2/3' objnr from dual
union all
select 2, '4/5/6' from dual),
test as
(select id, objnr col from value_table where id in (1, 2))
select id, regexp_substr(col, '[^/]+', 1, level) result
from test
connect by level <= regexp_count(col, '[^/]+')
and id = prior id
and prior dbms_random.value is not null
order by 1
which uses the model clause PRIOR the sqlfiddle link is http://sqlfiddle.com/#!4/03d80/31

Related

Multiply with Previous Value from One colum in Oracle SQL

I have the following result, which is easily calculated in Excel, but how to do it in Oracle, the result is the following, based on a previous select and comes from one column,
Result from select Expected result
1.62590
0.60989 0.991620151
0.83859 0.831562742
the result is based on 1.62590 * 0.60989 = 0.991620151,
1.62590 * 0.60989 * 0.83859 = 0.831562742
You can use:
SELECT id,
result,
EXP(SUM(LN(result)) OVER (ORDER BY id)) AS expected
FROM table_name;
Note: Use any other column instead of id to give the appropriate ordering or, if your rows are already ordered, use the ROWNUM pseudo-column instad of id.
Which, for the sample data:
CREATE TABLE table_name (id, Result) AS
SELECT 1, 1.62590 FROM DUAL UNION ALL
SELECT 2, 0.60989 FROM DUAL UNION ALL
SELECT 3, 0.83859 FROM DUAL;
Outputs:
ID
RESULT
EXPECTED
1
1.6259
1.62590000000000000000000000000000000001
2
.60989
.9916201510000000000000000000000000000026
3
.83859
.8315627424270900000000000000000000000085
fiddle
One option is to use a recursive CTE; it, though, expects that sample data can be sorted, somehow, so I added the ID column which starts with 1, while other values are incremented by 1:
Sample data:
SQL> with
2 test (id, col) as
3 (select 1, 1.62590 from dual union all
4 select 2, 0.60989 from dual union all
5 select 3, 0.83859 from dual
6 ),
Query begins here:
7 product (id, col, prod) as
8 (select id, col, col
9 from test
10 where id = 1
11 union all
12 select t.id, t.col, t.col * p.prod
13 from test t join product p on p.id + 1 = t.id
14 )
15 select id,
16 round(prod, 10) result
17 from product;
ID RESULT
---------- ----------
1 1,6259
2 ,991620151
3 ,831562742
SQL>
You can use a MODEL clause:
SELECT *
FROM (SELECT ROW_NUMBER() OVER (ORDER BY id) AS rn, result FROM table_name)
MODEL
DIMENSION BY (rn)
MEASURES ( result, 0 AS expected)
RULES (
expected[rn] = result[cv()] * COALESCE(expected[cv()-1], 1)
)
order by rn;
Which, for the sample data:
CREATE TABLE table_name (id, Result) AS
SELECT 1, 1.62590 FROM DUAL UNION ALL
SELECT 2, 0.60989 FROM DUAL UNION ALL
SELECT 3, 0.83859 FROM DUAL;
Outputs:
RN
RESULT
EXPECTED
1
1.6259
1.6259
2
.60989
.991620151
3
.83859
.83156274242709
fiddle

Oracle Pivot Column in group query

I have the following working query:
WITH pivot_data AS (
select PSGROUP,
PSCOLUMN as PSCOLUMN
FROM LOG_PS_STATUS
)
SELECT *
FROM pivot_data
PIVOT (
MAX(NULL) --<-- pivot_clause
FOR PSCOLUMN--<-- pivot_for_clause
IN (&PS_COLUMNS.) --<-- pivot_in_clause
);
It shows results as expected:
Values:
PSGroup PSColumn
A 1
A 2
A 3
B 1
B 2
B 3
C 3
Result is giving like:
PSGroup(Column vertically) PSColoumn(Horizontally)
1 2 3
A
B
C
Now I want to make PSGroup column as group of PSColumn and output should be like:
A
1 2 3
B
1 2 3
C
3
You can use listagg:
WITH pivot_data(PSGroup,PSColumn) AS (
select 'A', 1 FROM dual UNION all
select 'A', 2 FROM dual UNION all
select 'A', 3 FROM dual UNION all
select 'B', 1 FROM dual UNION all
select 'B', 2 FROM dual UNION all
select 'B', 3 FROM dual UNION all
select 'C', 3 FROM DUAL),
res(PSGroup,PSColumns) as(
SELECT PSGroup, LISTAGG(PSColumn, ' ') WITHIN GROUP (order by PSColumn)
FROM pivot_data
GROUP BY PSGroup)
select DECODE(PSColumns,NULL,PSGroup,NULL) AS PSGroup, PSColumns from(
select PSGroup, NULL as PSColumns from res
union all
select PSGroup, PSColumns from res)t
ORDER BY t.PSGroup, t.PSColumns NULLS first
Also note that LISTAGG(PSColumn, ' ') WITHIN GROUP (order by PSColumn) is limited to 4000 characters

Oracle 9.2 pivot distinct value

The pivot function is available from Oracle 11 and i will need similar result using Oracle 9.2.
The main argument is that i need to pivot some values with a distinct result in a table like this:
id col3
1 a
1 b
--
2 a
2 a
2 b
--
3 a
3 b
3 c
My result sould be something like this
id a b c
1 1 1 0
2 1 1 0
3 1 1 1
To create a "manual" pivot i'm using a case/when but I am not able to understand how to get distinct value.
Right now the query is this:
with t as
( select 1 as id, 'a' as col1 from dual union all
select 1 as id, 'b' from dual union all
select 2 as id, 'a' from dual union all
select 2 as id, 'a' from dual union all
select 2 as id, 'b' from dual union all
select 3 as id, 'a' from dual union all
select 3 as id, 'b' from dual union all
select 3 as id, 'c' from dual
)
select t.id,
count(case when t.col1 = 'a' then 1 end) a,
count(case when t.col1 = 'b' then 1 end) b,
count(case when t.col1 = 'c' then 1 end) c
This produce correct value but obviously it just "count" the total a/b/c value and not the distinct.
thanks for the support
If I correctly understand your need, you can try something like the following; it aggregates by id and counts the distinct values of col3:
with t as
( select 1 as id, 'a' as col1 from dual union all
select 1 as id, 'b' from dual union all
select 2 as id, 'a' from dual union all
select 2 as id, 'a' from dual union all
select 2 as id, 'b' from dual union all
select 3 as id, 'a' from dual union all
select 3 as id, 'b' from dual union all
select 3 as id, 'c' from dual
)
select id,
count(distinct decode (col1, 'a', id, null)) a,
count(distinct decode (col1, 'b', id, null)) b,
count(distinct decode (col1, 'c', id, null)) c
from t
group by id
Of course the query depends on the number of different values of col3, but this is the same problem than pivot.

Group 'n' rows in to columns - oracle

I have a situation where I need to split 'n' rows in to column group. For example, Below is dataset
COMMENT_TEXT
T1
T2
T3
T4
T5
T6
Expected Output:
SUN MON TUE
T1 T2 T3
T4 T5 T6
My Query:
SELECT htbp1.comment_text
FROM hxc_time_building_blocks htbp,
hxc_time_building_blocks htbp1
WHERE htbp1.parent_building_block_id = htbp.time_building_block_id
AND htbp1.parent_building_block_ovn = htbp.parent_building_block_ovn
AND htbp.parent_building_block_id = 116166
AND htbp.parent_building_block_ovn = 1
ORDER BY htbp1.time_building_block_id
Is there any way I can do PIVOT with a 'n' rows and without aggregate function?
Edit: T1/T2/T3 as sample data sets but in real it can be any random free text or null.
SELECT * FROM (SELECT htbp1.comment_text, TO_CHAR (htbp.start_time, 'DY') par_time,
trunc((rownum-1) / 7) buck
FROM hxc_time_building_blocks htbp,
hxc_time_building_blocks htbp1,
hxc_timecard_summary hts
WHERE hts.RESOURCE_ID = :p_resource_id
AND TRUNC(hts.STOP_TIME) = TRUNC(:p_wkend_date)
AND htbp1.parent_building_block_id = htbp.time_building_block_id
AND htbp1.parent_building_block_ovn = htbp.parent_building_block_ovn
AND htbp.parent_building_block_id = hts.timecard_id
AND htbp.parent_building_block_ovn = hts.timecard_ovn
ORDER BY htbp1.time_building_block_id ) PIVOT( max(comment_text) FOR par_time
IN ('SUN' AS "SUN",
'MON' AS "MON",
'TUE' AS "TUE",
'WED' AS "WED",
'THU' AS "THU",
'FRI' AS "FRI",
'SAT' AS "SAT"));
When I added the another table 'hxc_timecard_summary' which is parent then data is going crazy, but if I use the hardcoded parameters like the one in the first then the rows are showing up fine.
PIVOT also uses an aggregate function but you don't need a GROUP BY:
with tab as (
select sysdate - 7 date_col, 'T1' comment_text from dual
union all select sysdate - 6, 'T2' from dual
union all select sysdate - 5, 'T3' from dual
union all select sysdate - 4, 'T4' from dual
union all select sysdate - 3, 'T5' from dual
union all select sysdate - 2, 'T6' from dual
union all select sysdate - 1, 'T7' from dual
)
select * from (select to_char(date_col, 'D') day_of_week, comment_text from tab)
PIVOT (max(comment_text) for day_of_week in (7 as sun, 1 as mon, 2 as tue));
Also, I suppose you need the second column with a date to form your new columns.
And you cannot use expressions for FOR clause - this should be column(s). For example, this won't work:
select * from tab
PIVOT (max(comment_text) for to_char(date_col, 'D') in (7 as sun, 1 as mon, 2 as tue));
because of to_char(date_col, 'D')
Try using pivot.
It allows rows to be mapped to columns.
Its from 11g onwards I believe.
with tab as (
select 'T1' comment_text from dual
union all select 'T2' from dual
union all select 'T3' from dual
union all select 'T4' from dual
union all select 'T5' from dual
union all select 'T6' from dual
)
select regexp_substr(txt, '[^,]+', 1, 1) sun,
regexp_substr(txt, '[^,]+', 1, 2) mon,
regexp_substr(txt, '[^,]+', 1, 3) tue
from (
select buck, wm_concat(comment_text) txt
from (
select comment_text, trunc((rownum-1) / 3) buck
from (select comment_text from tab order by comment_text)
)
group by buck
);
wm_concat(comment_text) (Oracle 10g) =
listagg(comment_text, ',') within group(order by comment_text) (Oracle 11g)
But these two functions are both aggregate
My third try, no aggregate functions at all (works fine in Oracle 10g)
with tab as (
select 'T1' comment_text from dual
union all select 'T2' from dual
union all select 'T3' from dual
union all select 'T4' from dual
union all select 'T5' from dual
union all select 'T6' from dual
)
select regexp_substr(txt, '[^(#####)]+', 1, 1) sun,
regexp_substr(txt, '[^(#####)]+', 1, 2) mon,
regexp_substr(txt, '[^(#####)]+', 1, 3) tue
from (
select sys_connect_by_path(comment_text, '#####') txt, parent_id
from (
select rownum id, comment_text, mod(rownum-1, 3) parent_id
from (select comment_text from tab order by comment_text)
)
start with parent_id = 0
connect by prior id = parent_id
) where parent_id = 2;

ORACLE SQL | Modifying data in ORDER BY

following structure in a ORACLE table:
FILE_NAME
-----------
12345_l.tif
12345_m.tif
12345_r.tif
12345_x.tif
12345_y.tif
Need the following result:
First *_m*
Then *_l*
Then *_r*
Then * (everything else)
Trying with:
SELECT FILE_NAME FROM TABLE
WHERE FILE_NAME LIKE '12345%'
ORDER BY regexp_replace(FILE_NAME, '_m', '_1'),
regexp_replace(FILE_NAME, '_l', '_2'),
regexp_replace(FILE_NAME, '_r', '_3')
But this gives me a wrong result.
Anybody with a hint?
TIA Matt
Change your ORDER BY to order it by a numeric:
ORDER BY regexp_replace(FILE_NAME, '_m', 1),
regexp_replace(FILE_NAME, '_l', 2),
regexp_replace(FILE_NAME, '_r', 3);
e.g.
WITH t
AS (SELECT '12345_l.tif' AS file_name FROM dual
UNION
SELECT '12345_m.tif' FROM dual
UNION
SELECT '12345_r.tif' FROM dual
UNION
SELECT '12345_x.tif' FROM dual
UNION
SELECT '12345_y.tif' FROM dual)
SELECT file_name
FROM t
ORDER BY regexp_replace(FILE_NAME, '_m', 1),
regexp_replace(FILE_NAME, '_l', 2),
regexp_replace(FILE_NAME, '_r', 3);
Gives:
==============
12345_m.tif
12345_l.tif
12345_r.tif
12345_x.tif
12345_y.tif
Hope it helps...
Alternatively you could use:
ORDER BY (CASE SUBSTR(file_name, INSTR(file_name, '_')+1, 1)
WHEN 'm' THEN 1
WHEN 'l' THEN 2
WHEN 'r' THEN 3
ELSE 4
END) ASC;
E.G.:
WITH t
AS (SELECT '12345_l.tif' AS file_name FROM dual
UNION
SELECT '12345_y.tif' FROM dual
UNION
SELECT '12345_r.tif' FROM dual
UNION
SELECT '12345_x.tif' FROM dual
UNION
SELECT '12345_m.tif' FROM dual)
SELECT file_name
FROM t
ORDER BY (CASE SUBSTR(file_name, INSTR(file_name, '_')+1, 1)
WHEN 'm' THEN 1
WHEN 'l' THEN 2
WHEN 'r' THEN 3
ELSE 4
END) ASC;
Gives:
12345_m.tif
12345_l.tif
12345_r.tif
12345_x.tif
12345_y.tif

Resources