Group 'n' rows in to columns - oracle - 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;

Related

Duplicated rows numbering

I need to number the rows so that the row number with the same ID is the same. For example:
Oracle database. Any ideas?
Use the DENSE_RANK analytic function:
SELECT DENSE_RANK() OVER (ORDER BY id) AS row_number,
id
FROM your_table
Which, for the sample data:
CREATE TABLE your_table ( id ) AS
SELECT 86325 FROM DUAL UNION ALL
SELECT 86325 FROM DUAL UNION ALL
SELECT 86326 FROM DUAL UNION ALL
SELECT 86326 FROM DUAL UNION ALL
SELECT 86352 FROM DUAL UNION ALL
SELECT 86353 FROM DUAL UNION ALL
SELECT 86354 FROM DUAL UNION ALL
SELECT 86354 FROM DUAL;
Outputs:
ROW_NUMBER
ID
1
86325
1
86325
2
86326
2
86326
3
86352
4
86353
5
86354
5
86354
db<>fiddle here

REGEXP_SUBSTR not able to process only current row

(SELECT LISTAGG(EVENT_DESC, ',') WITHIN GROUP (ORDER BY EVENT_DESC) FROM EVENT_REF WHERE EVENT_ID IN
( SELECT REGEXP_SUBSTR(AFTER_VALUE,'[^,]+', 1, level) FROM DUAL
CONNECT BY REGEXP_SUBSTR(AFTER_VALUE, '[^,]+', 1, level) IS NOT NULL
)
)
A table from which I am fetching AFTER_VALUE has values of integer which is comma seperated like
AFTER_VALUE data
Expected output
1
Event1
1,2
Event1,Event2
1,12,2,5
Event1,Event12,Event2,Event5
15,13
Event15,Event13
these are Ids in EVENT_REF table which have some description. I am trying to basically present
ex. 1,2 as Event1, Event2 and send back from query. There are multiple events so using REPLACE would be very tedious.
When using above query I'm getting error as “ORA-01722: invalid number” whenever there is more than one value in AFTER_VALUE column Ex. if there exists only one id , then the query works but for values like 1,2 or 1,13 etc it throws invalid number error.
PS: The event names are not Event1,Event2 etc , I have just put for reference.
You don't even need regular expressions for this assignment. Standard string function replace() can do the same thing, and faster. You only need an extra 'Event' at the beginning of the string, since that one doesn't "replace" anything.
Like this: (note that you don't need the with clause; I included it only for quick testing)
with
event_ref (after_value) as (
select '1' from dual union all
select '1,2' from dual union all
select '1,12,2,5' from dual union all
select '15,13' from dual
)
select after_value,
'Event' || replace(after_value, ',', ',Event') as desired_output
from event_ref
;
AFTER_VALUE DESIRED_OUTPUT
----------- -----------------------------
1 Event1
1,2 Event1,Event2
1,12,2,5 Event1,Event12,Event2,Event5
15,13 Event15,Event13
Ah,ok, looks, like you have other characters in your comma-separated list, so you can use this query:
with EVENT_REF(EVENT_ID,EVENT_DESC) as (
select 1, 'Desc 1' from dual union all
select 2, 'Desc 2' from dual union all
select 3, 'Desc 3' from dual union all
select 4, 'Desc 4' from dual union all
select 5, 'Desc 5' from dual union all
select 12, 'Desc12' from dual union all
select 13, 'Desc13' from dual union all
select 15, 'Desc15' from dual
)
select
(SELECT LISTAGG(EVENT_DESC, ',')
WITHIN GROUP (ORDER BY EVENT_DESC)
FROM EVENT_REF
WHERE EVENT_ID IN
( SELECT to_number(REGEXP_SUBSTR(AFTER_VALUE,'\d+', 1, level))
FROM DUAL
CONNECT BY level<=REGEXP_COUNT(AFTER_VALUE, '\d+')
)
)
from (
select '1' AFTER_VALUE from dual union all
select '1,2' AFTER_VALUE from dual union all
select '1,12,2,5' AFTER_VALUE from dual union all
select '15,13' AFTER_VALUE from dual
);
PS. And do not forget that to_number has 'default on conversion error' now: https://docs.oracle.com/en/database/oracle/oracle-database/12.2/sqlrf/TO_NUMBER.html
There is no need to split and concatenate substrings, just use regexp_replace:
with EVENT_REF (AFTER_VALUE) as (
select '1' from dual union all
select '1,2' from dual union all
select '1,12,2,5' from dual union all
select '15,13' from dual
)
select regexp_replace(AFTER_VALUE,'(\d+)','Event\1') from EVENT_REF;
REGEXP_REPLACE(AFTER_VALUE,'(\D+)','EVENT\1')
-----------------------------------------------
Event1
Event1,Event2
Event1,Event12,Event2,Event5
Event15,Event13

Query works on a version of oracle but not on other

I have 2 versions of oracle, Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production and Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
And I have this sql:
select an.idai, t1.fecha, 53, t1.val_d60, 0
from
( select ides, cana, ctec, trunc(fecha_d60, 'MM') as fecha, val_d60,
row_number() over (partition by ides,cana,ctec, trunc(fecha_d60, 'MM')
order by val_d60 asc) as orden
from azul_estdata60
where idflagv = 11
and ides < 25
and fecha_d60 >= '01/01/2016' and fecha_d60 <= '31/12/2016'
and cana = 8
order by ides, cana, ctec, val_d60 desc
) t1,
( select ides, cana, ctec, trunc(fecha_d60, 'MM') as fecha,
round(count(*)*.5,0) as percentil
from azul_estdata60
where idflagv in (11,12,13)
and ides < 25
and fecha_d60 >= '01/01/2016' and fecha_d60 <= '31/12/2016'
and cana = 8
group by ides, cana, ctec, trunc(fecha_d60, 'MM')
) t2
inner join azul_analogin an
on an.cana = t2.cana
and an.ctec = t2.ctec and an.ides = t2.ides
where t1.ides = t2.ides
and t1.cana = t2.cana
and t1.ctec = t2.ctec
and t1.fecha = t2.fecha
and orden = percentil;
It works on 11.2.0.1.0 but, on 11.2.0.3.0, I get this error:
ORA-00979: no es una expresión GROUP BY
00979. 00000 - "not a GROUP BY expression"
*Cause:
*Action:
What can I do to solve this?
Thx.
I'm not sure why you get that error. Your query look like syntactically good. But extra complex. I try to rewrite it a bit.
First of all I specify dates as dates. Then I exclude second subquery and compile it with first one. And in result I get next:
with azul_estdata60(cana, ctec, ides, fecha_d60,val_d60,idflagv ) as (
select 8,123, 1, date'2016-01-01',200, 11 from dual union all
select 8,123, 1, date'2016-01-03',2000, 11 from dual union all
select 8,123, 1, date'2016-01-05',2000, 11 from dual union all
select 8,123, 1, date'2016-01-06',20000, 11 from dual union all
select 8,123, 1, date'2016-01-10',200000, 11 from dual union all
select 8,123, 2, date'2016-02-01',201, 12 from dual union all
select 8,123, 3, date'2016-03-01',203, 13 from dual union all
select 8,123, 4, date'2016-04-01',205, 14 from dual union all
select 8,123, 5, date'2016-05-01',219, 13 from dual union all
select 8,123, 6, date'2017-01-01',260, 11 from dual )
, azul_analogin (cana, ctec, ides, IDAI) as (
select 8,123, 1,991 from dual
)
select an.idai, t2.fecha, 53, t2.val_d60, 0 , percentil, orden
from
( select ides, cana, ctec, trunc(fecha_d60, 'MM') as fecha, val_d60,
idflagv,
round( (count(*) over (partition by ides, cana, ctec, trunc(fecha_d60, 'MM')))*0.5,0) as percentil,
row_number() over (partition by idflagv, ides,cana,ctec, trunc(fecha_d60, 'MM')
order by val_d60 asc) as orden
from azul_estdata60
where idflagv in (11,12,13)
and ides < 25
and fecha_d60 >= date'2016-01-01' and fecha_d60 < date'2017-01-01'
and cana = 8
) t2
inner join azul_analogin an
on an.cana = t2.cana
and an.ctec = t2.ctec and an.ides = t2.ides
where 1 = 1
and orden = percentil
and idflagv = 11

Subqueries in 2 tables

I have 2 tables: machine and work.
Table:machine
machine_no downtime location
A1-100-01 2 A1
A1-100 1.5 A1
A1-200 3 A1
CC3-100-01 0.5 CC3
CC3-100 1.5 CC3
Table:work
machine_no date
A1-100-01 2/4/14
A1-100 2/14/14
A1-200 2/6/14
CC3-100-01 3/15/14
CC3-100 3/2/14
I want the output to be like this:
machine_no total_downtime month
A1-100 3.5 (total of A1-100, A1-100-01) 02
A1-200 3 02
When location A1 is selected.
SELECT machine_no, SUM(downtime) as total_downtime
FROM (
SELECT
SUBSTR(machine_no, 1,
CASE WHEN INSTR(machine_no, '-', 1, 2) = 0
THEN LENGTH(machine_no)
ELSE INSTR(machine_no, '-', 1, 2)-1
END) as machine_no,
downtime
FROM machine
WHERE location='A1'
) InnerQuery
GROUP BY machine_no
How do I join table WORK and display the month? I'm using Oracle.
Thank you.
The month column's semantics in your expected query result is unclear. Assuming that it is another aggregation "key", then your query would be
select
regexp_substr(M.machine_no, '^[^-]+-[^-]+') as machine_no,
sum(downtime) as total_downtime,
to_char(W.date, 'mm') as month
from machine M
join work W
on W.machine_no = M.machine_no
group by
regexp_substr(M.machine_no, '^[^-]+-[^-]+'),
to_char(W.date, 'mm')
;
Assuming it is a (somehow) aggregated value, let's say via min() function, then your query would be
select
regexp_substr(M.machine_no, '^[^-]+-[^-]+') as machine_no,
sum(downtime) as total_downtime,
min(to_char(W.date, 'mm')) as month
from machine M
join work W
on W.machine_no = M.machine_no
group by
regexp_substr(M.machine_no, '^[^-]+-[^-]+')
;
Both of these, in addition, assume that the (total of A1-100, A1-100-01) in your expected result is just your note, not really a part of the result. But if not, then your query could be something along the lines of
select
regexp_substr(M.machine_no, '^[^-]+-[^-]+') as machine_no,
sum(downtime)||
case when count(1) > 1 then
' (total of '||
listagg(M.machine_no)
within group (order by M.machine_no)||
')'
end
as total_downtime,
to_char(W.date, 'mm') as month
from machine M
join work W
on W.machine_no = M.machine_no
group by
regexp_substr(M.machine_no, '^[^-]+-[^-]+'),
to_char(W.date, 'mm')
;
And even this works because of a few more assumptions about the (unsaid) properties of your machine and work tables, so I'm going to stop my answer here. :-)
User regular expression to take sub string of machine_no and to_char to get the month
WITH machine(machine_no, downtime, location) as (
select 'A1-100-01', 2, 'A1' from dual union all
select 'A1-100', 1.5, 'A1' from dual union all
select 'A1-200', 3, 'A1' from dual union all
select 'CC3-100-01', 0.5, 'CC3' from dual union all
select 'CC3-100', 1.5, 'CC3' from dual),
work(machine_no, ddate) as (
select 'A1-100-01', to_date('2/4/14', 'mm/dd/yyyy') from dual union all
select 'A1-100', to_date('2/14/14', 'mm/dd/yyyy') from dual union all
select 'A1-200', to_date('2/6/14', 'mm/dd/yyyy') from dual union all
select 'CC3-100-01', to_date('3/15/14', 'mm/dd/yyyy') from dual union all
select 'CC3-100', to_date('3/2/14', 'mm/dd/yyyy') from dual)
--End of data preparation
SELECT regexp_substr(m.machine_no, '^\w+-\w+') AS machine_no,
sum(m.downtime) downtime_sum,
to_char(w.ddate , 'MM') MONTH
FROM WORK w
JOIN machine m ON m.machine_no = w.machine_no
WHERE m.location = 'A1'
GROUP BY regexp_substr(m.machine_no, '^\w+-\w+'),
to_char(w.ddate , 'MM');
Output:
| MACHINE_NO | DOWNTIME_SUM | MONTH |
|------------|--------------|-------|
| A1-200 | 3 | 02 |
| A1-100 | 3.5 | 02 |

How to display the days in 1 week in oracle?

I'm stuck, please help me how to display the cover days (e.g Monday, Tuesday, etc) per week. Please help me. Thank you!
Desired Sample Output:
Data,03/24/2014,Monday,20,Tuesday,30.....
Data,03/31/2014,Monday,12,Tuesday,20.....
Here's my script:
select 'Data'
||','||to_char(d.dtime_day, 'MM/dd/yyyy')
||','||nvl(g.total, 0)
from tablename d
left join (
select trunc(t.create_time, 'IW') as ddate
,count(t.create_time) as total
from tablename1 t
left join tablename2 q
on q.id = t.queue_id
where t.create_time between trunc(sysdate,'IW')-12*7 and sysdate -1
and q.name not like 'Data1%'
or q.name not like 'Data2%'
or q.name not like 'Data3%'
or q.name not like 'Data4%'
or q.name not like 'Data5%'
or q.name not like 'Data6%'
or q.name not like 'Data7%'
or q.name not like 'Data8%'
or q.name not like 'Data9%'
group by trunc(t.create_time, 'IW')
) g on d.dtime_day = g.ddate
where d.dtime_day between trunc(sysdate,'IW')-12*7 and trunc(sysdate) -1
and trunc(d.dtime_day, 'IW')= d.dtime_day
order by d.dtime_day;
Output of the script:
Data,03/24/2014,42
Data,03/31/2014,25
You can also select the day of the week using to_char e.g.
select to_char(sysdate, 'Day') from dual;
Used in Oracle Apex for a LOV in a Form
select basic_day display, basic_day return
from
(select trim(to_char(basic_date, 'DAY')) basic_day
from
( select 7 my_row, trunc(sysdate, 'DAY') basic_date
from dual
union
select 1, trunc(sysdate, 'DAY') + 1
from dual
union
select 2, trunc(sysdate, 'DAY') + 2
from dual
union
select 3, trunc(sysdate, 'DAY') + 3
from dual
union
select 4, trunc(sysdate, 'DAY') + 4
from dual
union
select 5, trunc(sysdate, 'DAY') + 5
from dual
union
select 6, trunc(sysdate, 'DAY') + 6
from dual)
order by my_row
)

Resources