I have the SQL below which Im using to get row numbers. What I want is to know how many rows are in a particular group. Is this possible?
Thanks
select
to_char(ndt.dt , 'yyyy'),
to_char(ndt.dt , 'MON'),
to_char(ndt.dt , 'dd'),
row_number() OVER (partition by to_char(ndt.dt , 'yyyy'), to_char(ndt.dt , 'MON') ORDER BY ndt.dt)
from (
(SELECT TRUNC (SYSDATE - ROWNUM + 44) dt
FROM DUAL CONNECT BY ROWNUM < 91
order by dt asc) ndt
);
but this gives me
|2016| APR |01| 1|
|2016| APR |02| 2|
...
|2016| APR|30| 30|
|2016|MAY| 01 |1|
|2016| MAY |
whereas what I really want is
|2016| APR |01| 30|
|2016| APR |02| 30|
...
|2016| APR|30| 30|
|2016|MAY| 01 |31|
|2016| MAY |31|
You can try with this, using count instead or row_number:
select to_char(d, 'yyyy'), to_char(d, 'mon'), to_char(d, 'dd'),
count(1) over ( partition by extract(month from d)) as count
from (
select to_date('01042016', 'ddmmyyyy') + level -1 as d
from dual
connect by level <= 61
)
order by 1, 2, 3
The logics behind your +44 is not that clear to me, so I used a simple starting date
Related
I read many articles which introduce the using of connect by, but all of them get the particular days from a giving parameter(almost the begin date and end date)
What I want to know is how could I get split the rows from a certain table?
Example
Table T1
StartDate EndDate T_ID
2017-06-01 2017-06-15 01
2017-06-05 2017-06-06 02
The result I want is
TargetDate T_ID
2017-06-01 01
2017-06-02 01
2017-06-03 01
2017-06-04 01
2017-06-05 01
.
.
.
.
2017-06-15 01
2017-06-06 01
2017-06-06 02
I tried
SELECT T_ID, T1.StartDate+ LEVEL - 1 DD, LEVEL
FROM T1
WHERE T1.T_ID in = '01'
CONNECT BY LEVEL <= (TO_DATE(TRUNC(T1.EndDate)) - T1.StartDate + 1 ) ;
Waiting for your solution. Thanks.
Test Data:
CREATE TABLE t1 ( t_id, startdate, enddate ) AS
SELECT 1, DATE '2017-06-01', DATE '2017-06-15' FROM DUAL UNION ALL
SELECT 2, DATE '2017-06-05', DATE '2017-06-06' FROM DUAL;
Query:
SELECT T_ID,
COLUMN_VALUE AS dt,
ROW_NUMBER() OVER ( PARTITION BY t1.ROWID
ORDER BY Column_value ) AS lvl
FROM T1
CROSS JOIN
TABLE(
CAST(
MUTLTSET(
SELECT t1.Startdate + LEVEL - 1
FROM DUAL
CONNECT BY t1.Startdate + LEVEL - 1 <= t1.EndDate
) AS SYS.ODCIDATELIST
)
);
Output:
T_ID DT LVL
---- ---------- ---
1 2017-06-01 1
1 2017-06-02 2
1 2017-06-03 3
1 2017-06-04 4
1 2017-06-05 5
1 2017-06-06 6
1 2017-06-07 7
1 2017-06-08 8
1 2017-06-09 9
1 2017-06-10 10
1 2017-06-11 11
1 2017-06-12 12
1 2017-06-13 13
1 2017-06-14 14
1 2017-06-15 15
2 2017-06-05 1
2 2017-06-06 2
Here is the query in standard SQL (with a recursive cte) which also works in Oracle:
with all_dates(targetdate, t_id, enddate) as
(
select startdate as targetdate, t_id, enddate from t1
union all
select targetdate + 1, t_id, enddate from all_dates where targetdate < enddate
)
select targetdate, t_id
from all_dates
order by t_id, targetdate;
SELECT DISTINCT T_ID
, T1.StartDate+ LEVEL - 1 DD
, LEVEL
FROM T1
WHERE T1.T_ID IN( 1,2)
CONNECT BY LEVEL <= T1.EndDate - T1.StartDate + 1
But I'm not sure about performances (At moment I didn't find a way to limit without DISTINCT but using CONNECT BY clauses).
As an alternative you can use a CTE like this (you can remove RN column, I left it as a check):
with all_dates(targetdate, t_id, enddate, RN) as
(
select startdate as targetdate, t_id, enddate, 1 AS RN from t1
union all
select T1.startdate + all_dates.RN, T1.t_id, T1.enddate, all_dates.RN+1 AS RN
from t1
inner JOIN all_dates ON T1.startdate+all_dates.RN<=all_dates.enddate
AND T1.T_ID = all_dates.T_ID
)
select targetdate, t_id , RN
from all_dates
order by t_id, targetdate;
Sample data:
CREATE TABLE T1 (StartDate DATE, EndDate DATE, T_ID NUMBER(10,0));
INSERT INTO T1 VALUES ('20170601','20170615', 1);
INSERT INTO T1 VALUES ('20170605','20170606', 2);
INSERT INTO T1 VALUES ('20170701','20170703', 3);
Output:
20170601 1 1
20170602 1 2
20170603 1 3
20170604 1 4
20170605 1 5
20170606 1 6
20170607 1 7
20170608 1 8
20170609 1 9
20170610 1 10
20170611 1 11
20170612 1 12
20170613 1 13
20170614 1 14
20170615 1 15
20170605 2 1
20170606 2 2
20170701 3 1
20170702 3 2
20170703 3 3
If you're wanting to use connect by to achieve this, you will need to add a couple of additional clauses in order to get it to work with multiple rows:
WITH t1 AS (SELECT to_date('01/06/2017', 'dd/mm/yyyy') startdate, to_date('15/06/2017', 'dd/mm/yyyy') enddate, 1 t_id FROM dual UNION ALL
SELECT to_date('05/06/2017', 'dd/mm/yyyy') startdate, to_date('06/06/2017', 'dd/mm/yyyy') enddate, 2 t_id FROM dual)
SELECT t_id,
startdate + LEVEL -1 dd
FROM t1
CONNECT BY LEVEL <= enddate - startdate + 1
AND PRIOR t_id = t_id
AND PRIOR sys_guid() IS NOT NULL
ORDER BY t_id, dd;
T_ID DD
---------- -----------
1 01/06/2017
1 02/06/2017
1 03/06/2017
1 04/06/2017
1 05/06/2017
1 06/06/2017
1 07/06/2017
1 08/06/2017
1 09/06/2017
1 10/06/2017
1 11/06/2017
1 12/06/2017
1 13/06/2017
1 14/06/2017
1 15/06/2017
2 05/06/2017
2 06/06/2017
Imagine this scenario (YYYY/MM/DD):
Start date: 2015/01/01 End date: 2015/08/10
Start date: 2014/10/03 End date: 2015/07/06
Start date: 2015/09/30 End date: 2016/04/28
Using PL/SQL can I calculate the distinct days between these overlapping dates?
Edit: My table has 2 DATE columns, Start_Date and End_Date. The result I'm expecting is 515 days ((2015/08/10 - 2014/10/03) + (2016/04/28 -2015/09/30))
You can do also with pure SQL (no need for PL/SQL):
with
minmax as (select min(start_date) min_dt, max(end_date) max_dt from myTable ),
dates as (
SELECT min_dt + rownum-1 dt1
FROM minmax CONNECT BY ROWNUM <= (max_dt - min_dt +1)
)
select count(*) from dates
where exists(
select 1 from MyTable T2
where dates.dt1 between T2.start_date and T2.end_date )
NOTE: an idea, written from head, not tested. Adapt generated dates as needed, with start date and needed length.
Hope it helps.
EDIT: Using actual table dates
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE DATES ( start_date, end_date ) AS
SELECT DATE '2015-01-01', DATE '2015-08-10' FROM DUAL
UNION ALL SELECT DATE '2014-10-03', DATE '2015-07-06' FROM DUAL
UNION ALL SELECT DATE '2015-09-30', DATE '2016-04-28' FROM DUAL
Query 1:
SELECT COUNT( DISTINCT COLUMN_VALUE ) AS number_of_days
FROM DATES d,
TABLE(
CAST(
MULTISET(
SELECT d.START_DATE + LEVEL - 1
FROM DUAL
CONNECT BY d.START_DATE + LEVEL - 1 < d.END_DATE
)
AS SYS.ODCIDATELIST
)
)
ORDER BY 1
Results:
| NUMBER_OF_DAYS |
|----------------|
| 522 |
Query 2 - Check:
SELECT DATE '2015-08-10' - DATE '2014-10-03'
+ DATE '2016-04-28' - DATE '2015-09-30'
FROM DUAL
Results:
| DATE'2015-08-10'-DATE'2014-10-03'+DATE'2016-04-28'-DATE'2015-09-30' |
|---------------------------------------------------------------------|
| 522 |
I have a query that gives me renders per minute (when there is a render during the minute)
select count(*) as "Total Rendered", to_char(r.request_dt, 'YYYY-MM-DD HH24:MI') as "ByMinute" from form_render r where r.form_type_id = 49
and r.request_dt >= to_timestamp('09-16-2015 08:00', 'mm-dd-yyyy hh24:mi')
group by to_char(r.request_dt, 'YYYY-MM-DD HH24:MI')
order by to_char(r.request_dt, 'YYYY-MM-DD HH24:MI') desc
Total Rendered | ByMinute
19 2015-09-17 09:31
10 2015-09-17 09:30
1 2015-09-17 09:28
6 2015-09-17 09:27
18 2015-09-17 09:25
22 2015-09-17 09:24
12 2015-09-17 09:23
13 2015-09-17 09:21
22 2015-09-17 09:20
However I want a row for all times (by minute) even when there were none rendered (add rows for times when no renders occurred...09:22,09:26, 09:29)
So something like this
Total Rendered | ByMinute
19 2015-09-17 09:31
10 2015-09-17 09:30
0 2015-09-17 09:29
1 2015-09-17 09:28
6 2015-09-17 09:27
0 2015-09-17 09:26
18 2015-09-17 09:25
22 2015-09-17 09:24
12 2015-09-17 09:23
0 2015-09-17 09:22
13 2015-09-17 09:21
22 2015-09-17 09:20
Any help is greatly appreciated
Sean
===========================================================================
Sept 20 attempt.....
===========================================================================
Thank you very much for taking the time to put this together. SQL is definitely not my strength as I am sure you can tell!
So I have patched together the time table and my query but am getting an error (ORA01843: not a valid month.)
When I run my query by itself
select count(*)as "Total_Rendered" , to_char(r.request_dt, 'MM/DD/YYYY HH24:MI')as "ByMinute" from form_render r where r.form_type_id = 49
and r.request_dt >= to_timestamp('09/17/2015 09:11', 'mm/dd/yyyy hh24:mi') and r.request_dt <= to_timestamp('09/17/2015 09:18', 'mm/dd/yyyy hh24:mi')
group by to_char(r.request_dt, 'MM/DD/YYYY HH24:MI')
order by 2 desc
I get the following
TOTAL_RENDERED | ByMinute
---------------------------
4 | 09/17/2015 09:18
16 | 09/17/2015 09:17
4 | 09/17/2015 09:16
2 | 09/17/2015 09:11
When I run the minute_table query
WITH min_date AS /* replace start and stop timestamp here */
(SELECT to_date('09/17/2015 09:11', 'MM/DD/YYYY hh24:mi') minute FROM dual
),
max_date AS
(SELECT to_date('09/17/2015 09:18', 'MM/DD/YYYY hh24:mi') minute FROM dual
),
minute_table AS
(SELECT
(SELECT minute FROM min_date
) + (rownum -1)/(24*60) AS by_minute
FROM dual
CONNECT BY level <=
(SELECT (24*60) *(
(SELECT minute FROM max_date
) -
(SELECT minute FROM min_date
))+1
FROM dual
)
)
select * from minute_table
I get the following....
9/17/2015 9:11:00 AM
9/17/2015 9:12:00 AM
9/17/2015 9:13:00 AM
9/17/2015 9:14:00 AM
9/17/2015 9:15:00 AM
9/17/2015 9:16:00 AM
9/17/2015 9:17:00 AM
9/17/2015 9:18:00 AM
so far so good.
When I combine the two queries to get this query
WITH min_date AS /* replace start and stop timestamp here */
(SELECT to_date('09/17/2015 09:11', 'MM/DD/YYYY hh24:mi:ss') minute FROM dual
),
max_date AS
(SELECT to_date('09/17/2015 09:18', 'MM/DD/YYYY hh24:mi:ss') minute FROM dual
),
minute_table AS
(SELECT
(SELECT minute FROM min_date
) + (rownum -1)/(24*60) AS by_minute
FROM dual
CONNECT BY level <=
(SELECT (24*60) *(
(SELECT minute FROM max_date
) -
(SELECT minute FROM min_date
))+1
FROM dual
)
),
tbl AS
(
select count(*)as "Total_Rendered" , to_char(r.request_dt, 'MM/DD/YYYY HH24:MI')as "ByMinute" from form_render r where r.form_type_id = 49
and r.request_dt >= to_timestamp('09/17/2015 09:11', 'mm/dd/yyyy hh24:mi') and r.request_dt <= to_timestamp('09/17/2015 09:18', 'mm/dd/yyyy hh24:mi')
group by to_char(r.request_dt, 'MM/DD/YYYY HH24:MI')
order by 2 desc
)
SELECT minute_table.by_minute ,
NVL(tbl."Total_Rendered",'0') AS total_rendered
FROM minute_table
left OUTER JOIN tbl
ON tbl."ByMinute" = minute_table.by_minute
order by 1 desc
;
I get an error ORA01843: not a valid month.
I am not entirely certain why the error is occuring but I am confident it has to do with the format differences between the columns I am joining on?
The format of the dates in the time table vs my table are likely the cause but I am not certain.
Time Table has 9/17/2015 9:11:00 AM and
my query has 09/17/2015 09:11 (notice missing leading zero on Month, seconds and AM)
Any help is appreciated.
Thanks again for your time and expertise....
Sean
Slightly different solution without dependency on 12c and with "parameters" for start and stop minutes.
WITH min_date AS /* replace start and stop timestamp here */
(SELECT to_date('2015-09-17 09:28','yyyy-mm-dd hh24:mi') minute FROM dual
),
max_date AS
(SELECT to_date('2015-09-17 09:31','yyyy-mm-dd hh24:mi') minute FROM dual
),
minute_table AS
(SELECT
(SELECT minute FROM min_date
) + (rownum -1)/(24*60) AS by_minute
FROM dual
CONNECT BY level <=
(SELECT (24*60) *(
(SELECT minute FROM max_date
) -
(SELECT minute FROM min_date
))+1
FROM dual
)
),
tbl AS
(SELECT 19 Total_Rendered,
to_date('2015-09-17 09:31','YYYY-MM-DD HH24:MI') By_Minute
FROM dual
UNION ALL
SELECT 1,to_date('2015-09-17 09:30','YYYY-MM-DD HH24:MI') FROM dual
UNION ALL
SELECT 19,to_date('2015-09-17 09:28','YYYY-MM-DD HH24:MI') FROM dual
)
SELECT minute_table.by_minute ,
NVL(tbl.total_rendered,'0') AS total_rendered
FROM minute_table
LEFT OUTER JOIN tbl
ON tbl.By_Minute = minute_table.by_minute
;
So this is working in Oracle 12c. But you have to change it for your use.
with tbl(Total_Rendered, ByMinute) as (
select 19,to_date('2015-09-17 09:31','YYYY-MM-DD HH24:MI') from dual union all
select 1,to_date('2015-09-17 09:30','YYYY-MM-DD HH24:MI') from dual union all
select 19,to_date('2015-09-17 09:28','YYYY-MM-DD HH24:MI') from dual )
select nvl(tbl.total_rendered,'0') as total_rendered,by_minute from
(select to_date('2015-09-17 09:28','YYYY-MM-DD HH24:MI') + (1/24/60) * column_value as by_minute from
TABLE( CAST( MULTISET( SELECT LEVEL FROM DUAL
CONNECT BY LEVEL <= 3 ) AS SYS.ODCINUMBERLIST
) )) time_range
left outer join
tbl
on tbl.ByMinute = time_range.by_minute
Output
TOTAL_RENDERED BY_MINUTE
0 17-SEP-2015 09:29:00
1 17-SEP-2015 09:30:00
19 17-SEP-2015 09:31:00
What I did is first I am using a temporary table tbl which will have data as your input. Then I generated timestamp from '17-SEP-2015 09:29:00' - '17-SEP-2015 09:31:00'to generate a time_range
Now I did a left outer join from this time_range to tbl which has missing data. For the rows not in tbl, I am printing 0.
You can use this query and edit it to get the time_range and join with your data to get this output.
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 |
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.