Convert columns to rows in oracle [duplicate] - oracle

This question already has answers here:
Oracle SQL pivot query
(4 answers)
Closed 8 years ago.
this is my table in oracle 11g:
**date qty1 qty2 qty3 qty4**
2-Feb-14 61 64 52 54
2-Mar-14 124 130 149 156
i want to convert it into the following table. i.e. add 7 days to the date and transpose the qty. And i have till qty52 such metrics
***date qty***
**2-Feb-14 61**
9-Feb-14 64
16-Feb-14 52
23-Feb-14 54
**2-Mar-14 124**
9-Mar-14 130
16-Mar-14 149
23-Mar-14 156

have a try:
WITH t(my_date, val, val2, val3, val4)
AS (
SELECT to_date('01/01/2014 12:00:00 AM', 'dd/mm/yyyy hh:mi:ss am'), 1,2,3,4 from dual
UNION ALL
SELECT to_date('01/02/2014 12:00:00 AM', 'dd/mm/yyyy hh:mi:ss am'), 5,6,7,8 FROM dual
)
SELECT (my_date-7) + (row_number() OVER (partition by my_date ORDER BY my_date)*7) my_date, value as qty
FROM (
( SELECT my_date, val, val2, val3, val4 FROM t
) unpivot ( value FOR value_type IN (val, val2, val3, val4) ) );
output:
MY_DATE QTY
----------------------- ----------
01/01/2014 12:00:00 AM 1
08/01/2014 12:00:00 AM 2
15/01/2014 12:00:00 AM 3
22/01/2014 12:00:00 AM 4
01/02/2014 12:00:00 AM 5
08/02/2014 12:00:00 AM 6
15/02/2014 12:00:00 AM 7
22/02/2014 12:00:00 AM 8

select date,qty from
(select date,qty1 as qty
from tbl
union
select date+7 as date,qty2 as qty
from tbl
union
select date+14 as date,qty3 as qty
from tbl
union
select date+21 as date,qty4 as qty
from tbl)
order by date

If you've got Oracle 11g, I'd look at doing it with UNPIVOT.
select
start_date + to_number(week_number) * 7,
qty
from (
select *
from quantity_data
unpivot (qty for week_number
in (qty1 as '0', qty2 as '1', qty3 as '2', qty4 as '3'))
)
This is an alternative to the example from ajmalmhd04, using to_number instead of the row_number analytic function. The answer from ajmalmhd04 is probably more generic though
If you haven't got Oracle 11g then try this for an option:
with pivot_data as (
select 0 as pivot_col from dual union all
select 1 from dual union all
select 2 from dual union all
select 3 from dual
)
select
start_date + (7 * pivot_col) as start_date,
case
when pivot_col = 0 then qty1
when pivot_col = 1 then qty2
when pivot_col = 2 then qty3
when pivot_col = 3 then qty4 end as qty
from
quantity_data cross join pivot_data
order by 1

Try this
with tab(date_d,qty1,qty2,qty3,qty4) as (
select '2-Feb-14',61,64,52,54 from dual union all
select '2-Mar-14',124,130,149,156 from dual),
tab2(dd, ss) as (select date_d, qty1||','||qty2||','||qty3||','||qty4 from tab)
select to_date(dd) + ((level-1) * 7) "DATE", regexp_substr(ss, '[^(,)]+', 1, level) "QTY"
from tab2
connect by level <= length(ss) - length(replace(ss, ',')) + 1
and prior ss = ss
and prior sys_guid() is not null
output
| DATE | QTY |
|---------------------------------|-----|
| March, 02 2014 00:00:00+0000 | 124 |
| March, 09 2014 00:00:00+0000 | 130 |
| March, 16 2014 00:00:00+0000 | 149 |
| March, 23 2014 00:00:00+0000 | 156 |
| February, 02 2014 00:00:00+0000 | 61 |
| February, 09 2014 00:00:00+0000 | 64 |
| February, 16 2014 00:00:00+0000 | 52 |
| February, 23 2014 00:00:00+0000 | 54 |
Let me know if it meets your requirement.

Related

Get month wise yearly report in oracle

all I have a employee table with the following fields employee name, wages date, wages I want to sum records month-wise.here is table data. I m using oracle database 11g
and here is the output I want.
You can use ROLLUP in your GROUP BY
WITH t(NAME, dt, w) AS (
SELECT 'adam', DATE '2020-01-01', 200 FROM dual UNION ALL
SELECT 'adam', DATE '2020-02-01', 200 FROM dual UNION ALL
SELECT 'adam', DATE '2020-03-01', 200 FROM dual UNION ALL
SELECT 'jhone', DATE '2020-01-01', 100 FROM dual UNION ALL
SELECT 'jhone', DATE '2020-02-01', 200 FROM dual UNION ALL
SELECT 'jhone', DATE '2020-03-01', 151 FROM dual
)
SELECT NAME, NVL(TO_CHAR(dt, 'fmMon'), 'total') AS mon, SUM(w) AS sum_w
FROM t
GROUP BY NAME, ROLLUP(TO_CHAR(dt, 'fmMon'));
+-----------------+
|NAME |MON |SUM_W|
+-----------------+
|adam |Feb |200 |
|adam |Jan |200 |
|adam |Mar |200 |
|adam |total|600 |
|jhone|Feb |200 |
|jhone|Jan |100 |
|jhone|Mar |151 |
|jhone|total|451 |
+-----------------+
If you need to transpose your result, you can PIVOT it:
WITH t(NAME, dt, w) AS (
SELECT 'adam', DATE '2020-01-01', 200 FROM dual UNION ALL
SELECT 'adam', DATE '2020-02-01', 200 FROM dual UNION ALL
SELECT 'adam', DATE '2020-03-01', 200 FROM dual UNION ALL
SELECT 'jhone', DATE '2020-01-01', 100 FROM dual UNION ALL
SELECT 'jhone', DATE '2020-02-01', 200 FROM dual UNION ALL
SELECT 'jhone', DATE '2020-03-01', 151 FROM dual
)
SELECT *
FROM (
SELECT NAME, NVL(TO_CHAR(dt, 'fmMon'), 'total') AS mon, SUM(w) AS sum_w
FROM t
GROUP BY NAME, ROLLUP(TO_CHAR(dt, 'fmMon'))
)
PIVOT (
SUM(sum_w)
FOR mon IN ('Jan','Feb','Mar','total')
);
+-------------------------------+
|NAME |'Jan'|'Feb'|'Mar'|'total'|
+-------------------------------+
|adam |200 |200 |200 |600 |
|jhone|100 |200 |151 |451 |
+-------------------------------+
You can use conditional aggregation as follows:
Select name,
Sum(case when to_char(date,'mon') = 'jan' then wages end) as jan,
Sum(case when to_char(date,'mon') = 'feb' then wages end) as feb,
...
Sum(wages) as total
From yourTable
Group by name;
You need to use the where condition to only consider one year data.

How to query date differences between dates of same column?

I have a table with a date field and I need a query to return the ID of records that are on a certain day.
Example
ID UPDATED_DATE
42 31-DEC-19 12.00.00.000000000 AM
43 24-DEC-19 12.00.00.000000000 AM
44 03-DEC-19 12.00.00.000000000 AM
45 18-NOV-19 12.00.00.000000000 AM
46 08-NOV-19 12.00.00.000000000 AM
47 01-NOV-19 12.00.00.000000000 AM
48 26-OCT-19 12.00.00.000000000 AM
49 04-OCT-19 12.00.00.000000000 AM
50 20-SEP-19 12.00.00.000000000 AM
I need a query to find if the DAY part of the date is >= 1 and < 5.
In the example, I will get the ID as 44, 47 and 49 as output.
Can anyone help me for the query, please?
Use TO_CHAR with appropriate format mask:
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
SQL> with test as
2 (select 42 id, date '2019-12-31' updated_date from dual union all
3 select 43, date '2019-12-24' from dual union all
4 select 44, date '2019-12-03' from dual union all
5 select 45, date '2019-11-18' from dual union all
6 select 46, date '2019-11-08' from dual union all
7 select 47, date '2019-11-01' from dual union all
8 select 48, date '2019-10-26' from dual union all
9 select 49, date '2019-10-04' from dual union all
10 select 50, date '2019-09-20' from dual
11 )
12 select id, updated_date, to_char(updated_date, 'fmdd') dd
13 from test
14 where to_char(updated_date, 'fmdd') >= 1
15 and to_char(updated_date, 'fmdd') < 5
16 order by id;
ID UPDATED_DA DD
---------- ---------- --
44 03.12.2019 3
47 01.11.2019 1
49 04.10.2019 4
SQL>
If you want the difference (as per the original version of the question) between day-of-month of two rows to be between 1 and 4 days then use LAG/LEAD:
SELECT ID,
UPDATED_DATE
FROM (
SELECT ID,
UPDATED_DATE,
EXTRACT(DAY FROM updated_date) AS day,
LAG( EXTRACT(DAY FROM updated_date) )
OVER ( ORDER BY EXTRACT(DAY FROM updated_date) ) AS prev_day,
LEAD( EXTRACT(DAY FROM updated_date) )
OVER ( ORDER BY EXTRACT(DAY FROM updated_date) ) AS next_day
FROM table_name
)
WHERE day - prev_day BETWEEN 1 AND 4
OR next_day - day BETWEEN 1 AND 4
Which for your test data ouptuts:
ID | UPDATED_DATE
-: | :--------------------
47 | 01-NOV-19 12:00:00 AM
44 | 03-DEC-19 12:00:00 AM
49 | 04-OCT-19 12:00:00 AM
46 | 08-NOV-19 12:00:00 AM
45 | 18-NOV-19 12:00:00 AM
50 | 20-SEP-19 12:00:00 AM
43 | 24-DEC-19 12:00:00 AM
48 | 26-OCT-19 12:00:00 AM
If you want the day-of-month to be between 1 and 4 then use EXTRACT:
SELECT *
FROM table_name
WHERE EXTRACT(DAY FROM updated_date) BETWEEN 1 AND 4
Which outputs:
ID | UPDATED_DATE
-: | :--------------------
44 | 03-DEC-19 12:00:00 AM
47 | 01-NOV-19 12:00:00 AM
49 | 04-OCT-19 12:00:00 AM
db<>fiddle here

I have one requirement where I have to show the records between specific date and time every day of one week

I have one requirement where I have to show the records between specific date and time every day in one week duration.
in one week duration( 2019-04-01 till 2019-04-06) ,for instance record of 2019-04-01 at 19 PM till 8 Am of 2019-04-02 ,and record of 2019-04-02 at 19 PM till 08 AM of 2019-04-03 and ...
would you please help me!
Use recursive query to create proper periods then join with your data or do it simpler with condition like here:
select callbegin, callerno
from table4
where callerno in ('7032','750')
and callbegin between timestamp '2019-04-01 19:00:00'
and timestamp '2019-04-06 08:00:00'
and ('19' <= to_char(callbegin, 'hh24') or to_char(callbegin, 'hh24') < '08');
demo
Here's how I understood the question.
SQL> alter session set nls_date_format = 'dd.mm.yyyy hh24:mi';
Session altered.
SQL> break on period;
SQL> with
2 data (id, datum) as
3 (select 1, to_date('01.04.2019 15:30', 'dd.mm.yyyy hh24:mi') from dual union all
4 select 2, to_date('01.04.2019 20:00', 'dd.mm.yyyy hh24:mi') from dual union all -- 1st
5 select 3, to_date('02.04.2019 01:15', 'dd.mm.yyyy hh24:mi') from dual union all -- 1st perios
6 select 4, to_date('02.04.2019 11:00', 'dd.mm.yyyy hh24:mi') from dual union all
7 select 5, to_date('02.04.2019 23:15', 'dd.mm.yyyy hh24:mi') from dual union all -- 2nd period
8 select 6, to_date('03.04.2019 00:10', 'dd.mm.yyyy hh24:mi') from dual union all -- 2nd
9 select 7, to_date('04.04.2019 22:20', 'dd.mm.yyyy hh24:mi') from dual -- 3rd period
10 ),
11 test as
12 (select date '2019-04-01' dstart,
13 date '2019-04-06' dend
14 from dual
15 ),
16 inter as
17 (select dstart + level - 1 datum
18 from test
19 connect by level <= dend - dstart + 1
20 ),
21 from_to as
22 (select datum + 19/24 date_from,
23 lead(datum) over (order by datum) + 8/24 date_to
24 from inter
25 )
26 select f.date_From ||' - '|| f.date_to period,
27 d.id,
28 d.datum
29 from data d join from_to f on 1 = 1
30 where d.datum between f.date_from and f.date_to
31 order by f.date_From, d.id;
PERIOD ID DATUM
----------------------------------- ---------- ----------------
01.04.2019 19:00 - 02.04.2019 08:00 2 01.04.2019 20:00
3 02.04.2019 01:15
02.04.2019 19:00 - 03.04.2019 08:00 5 02.04.2019 23:15
6 03.04.2019 00:10
04.04.2019 19:00 - 05.04.2019 08:00 7 04.04.2019 22:20
SQL>
This is how to filter data by days and time by one week:
With date_list as (
Select
to_date(to_char( (sysdate - level), 'yyyymmdd') || '19', 'yyyymmddhh24') begin_time,
to_date(to_char( ((sysdate - level)+1), 'yyyymmdd') || '08', 'yyyymmddhh24') end_time
From dual connect by level <= 7
)
Select begin_time, your_table.*
From
your_table t1,
date_list t2
Where
t1.your_date between t2.begin_time and t2.end_time;

Oracle Stored Procedure - Number of heat waves (number of pattern matches in a series)

I need to postprocess a Oracle dataset in order to find the number of heat waves.
By definition, a heat waves occurs when the data value is greater than a threshold at least two consecutive times.
For example, given the threshold=20 and the sequence
23 31 32 17 16 23 16 21 22 18
the heat waves are 2:
{23,31,32} and {21,22}
and the lenght of the longest one is 3 (size of bigger subset)
My input dataset consists of several sequences; a sample input result set is:
-----------------------------
| ID | DAY | VALUE |
-----------------------------
| 100 | 1/1/17 | 20 |
| 100 | 2/1/17 | 21 |
| 200 | 1/1/17 | 12 |
| 200 | 2/1/17 | 24 |
| ... ... ...
In other words, I have a sequence per each ID and I need to output something like that:
-----------------------
| ID | #heat waves |
-----------------------
| 100 | 3 |
| 200 | 1 |
Here the current version of my stored procedure:
create or replace PROCEDURE sp (
p_query IN VARCHAR2,
cursor_ out sys_refcursor
) AS
processed processed_data_table := processed_data_table();
c sys_refcursor;
BEGIN
OPEN c FOR p_query;
processed.EXTEND;
processed(processed.count) := processed_data_obj();
fetch c INTO processed(processed.count).ID,
processed(processed.count).DAY, processed(processed.count).VALUE;
while c%found
processed.EXTEND;
processed(processed.count) := processed_data_obj();
fetch c INTO processed(processed.count).ID,
processed(processed.count).DAY, processed(processed.count).VALUE;
END loop;
CLOSE c;
processed.TRIM;
// HERE I NEED TO PROCESS processed TABLE AND STORE RESULT IN output
TABLE
OPEN cursor_ FOR
SELECT *
FROM TABLE( output);
END sp;
Anyone could help me providing a solution?
Thanks
In Oracle 12c, use MATCH_RECOGNIZE:
select id, count(*) "# of heatwaves" from series_data
match_recognize ( partition by id
order by day
one row per match
after match skip past last row
pattern ( over_threshold{2,} )
define
over_threshold as value > 20 )
group by id
UPDATE: Also show longest heat wave for each series
To get the longest heatwave in each series, we have to introduce a MEASURES clause to the MATCH_RECOGNIZE, as below:
select id,
max(heatwave_length) "longest heatwave",
count(distinct heatwave_number) "# of heatwaves"
from series_data
match_recognize ( partition by id
order by day
measures
FINAL COUNT(*) as heatwave_length,
MATCH_NUMBER() heatwave_number
all rows per match
after match skip past last row
pattern ( over_threshold{2,} )
define
over_threshold as value > 20 )
group by id
order by id;
Full example with data:
with series_data ( id, day, value ) as
( SELECT 100, date '2017-01-01', 23 from dual union all
SELECT 100, date '2017-01-02', 31 from dual union all
SELECT 100, date '2017-01-03', 32 from dual union all
SELECT 100, date '2017-01-04', 44 from dual union all
SELECT 100, date '2017-01-05', 16 from dual union all
SELECT 100, date '2017-01-06', 23 from dual union all
SELECT 100, date '2017-01-07', 16 from dual union all
SELECT 100, date '2017-01-08', 21 from dual union all
SELECT 100, date '2017-01-09', 22 from dual union all
SELECT 100, date '2017-01-10', 18 from dual union all
SELECT 200, date '2017-01-01', 23 from dual union all
SELECT 200, date '2017-01-02', 31 from dual union all
SELECT 200, date '2017-01-03', 32 from dual union all
SELECT 200, date '2017-01-04', 17 from dual union all
SELECT 200, date '2017-01-05', 16 from dual union all
SELECT 200, date '2017-01-06', 23 from dual union all
SELECT 200, date '2017-01-07', 16 from dual union all
SELECT 200, date '2017-01-08', 21 from dual union all
SELECT 200, date '2017-01-09', 22 from dual union all
SELECT 200, date '2017-01-10', 22 from dual union all
SELECT 200, date '2017-01-11', 6 from dual union all
SELECT 200, date '2017-01-12', 22 from dual union all
SELECT 200, date '2017-01-13', 22 from dual )
select id,
max(heatwave_length) "longest heatwave",
count(distinct heatwave_number) "# of heatwaves"
from series_data
match_recognize ( partition by id
order by day
measures
FINAL COUNT(*) as heatwave_length,
MATCH_NUMBER() heatwave_number
all rows per match
after match skip past last row
pattern ( over_threshold{2,} )
define
over_threshold as value > 20 )
group by id
order by id;
Results:
ID longest heatwave # of heatwaves
----- -------------- --------------
100 4 2
200 3 3

How can I combine records on the basis of some conditions

I have following table,
id id_concept start_date end_date
----------------------------------------------
100 282 20/06/2016 24/06/2016
100 282 15/07/2016 18/07/2016
300 282 01/09/2016 02/09/2016
I need to combine the records which has same id,id_concept and the time between END_DATE of one record and START_DATE of the next is 30 days or less (<=30)
Also for the combined records, I need to take the start_date as the very first start_date of the record and end _date as the end_date of the last record
The o/p should be like,
id id_concept start_date end_date count
---------------------------------------------------
100 282 20/06/2016 18/07/2016 2
300 282 01/09/2016 02/09/2016 1
SQL Fiddle
PostgreSQL 9.3 Schema Setup:
CREATE TABLE table_name ( id, id_concept, start_date, end_date ) AS
SELECT 100, 282, DATE '2016-06-20', DATE '2016-06-24' UNION ALL
SELECT 100, 282, DATE '2016-07-15', DATE '2016-07-18' UNION ALL
SELECT 300, 282, DATE '2016-09-01', DATE '2016-09-02';
Query 1:
SELECT id,
id_concept,
MIN( start_date ) AS start_date,
MAX( end_date ) AS end_date,
COUNT(*) AS "count"
FROM (
SELECT id,
id_concept,
start_date,
end_date,
SUM( diff ) OVER (
PARTITION BY id, id_concept
ORDER BY start_date, end_date
) AS grp
FROM (
SELECT t.*,
CASE
WHEN LAG( end_date ) OVER (
PARTITION BY id, id_concept
ORDER BY start_date, end_date
) >= start_date - INTERVAL '30' DAY
THEN 0
ELSE 1
END AS diff
FROM table_name t
) t
) t
GROUP BY id, id_concept, grp
Results:
| id | id_concept | start_date | end_date | count |
|-----|------------|-----------------------------|-----------------------------|-------|
| 300 | 282 | September, 01 2016 00:00:00 | September, 02 2016 00:00:00 | 1 |
| 100 | 282 | June, 20 2016 00:00:00 | July, 18 2016 00:00:00 | 2 |

Resources