How to count ratio hourly? - sql

I`m stuck a bit with understanding of my further actions while performing queries.
I have two tables "A"(date, response, b_id) and "B"(id, country). I need to count hourly ratio of a number of entries where response exists to the total number of entries on a specific date. The final selection should consist of columns "hour", "ratio".
SELECT COUNT(*) FROM A WHERE RESPONSE IS NOT NULL//counting entries with response
SELECT COUNT(*) FROM A//counting total number of entries
How to count the ratio? Should I create a separate variable for it?
How to count for each hour on a day? Should I make smth like a loop? + How can I get the "hour" part of a date?
What is the best way to select the hours and counted ratio? Should I make a separate table for it?
I`m rather new to make complex queries, so I woud be happy for every kind of help

You can do this as:
select to_char(datecol, 'HH24') as hour,
count(response) as has_response, count(*) as total,
count(response) / count(*) as ratio
from a
where datecol >= date '2018-09-18' and datecol < date '2018-09-19'
group by to_char(datecol, 'HH24');
You can also do this using avg() -- which is also fun:
select to_char(datecol, 'HH24'),
avg(case when response is not null then 1.0 else 0 end) as ratio
from a
where datecol >= date '2018-09-18' and datecol < date '2018-09-19'
group by to_char(datecol, 'HH24')
In this case, that requires more typing, though.

SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE A ( dt, response, b_id ) AS
SELECT DATE '2018-09-18' + INTERVAL '00:00' HOUR TO MINUTE, NULL, 1 FROM DUAL UNION ALL
SELECT DATE '2018-09-18' + INTERVAL '00:10' HOUR TO MINUTE, 'A', 1 FROM DUAL UNION ALL
SELECT DATE '2018-09-18' + INTERVAL '00:20' HOUR TO MINUTE, 'B', 1 FROM DUAL UNION ALL
SELECT DATE '2018-09-18' + INTERVAL '01:00' HOUR TO MINUTE, 'C', 1 FROM DUAL UNION ALL
SELECT DATE '2018-09-18' + INTERVAL '01:10' HOUR TO MINUTE, 'D', 1 FROM DUAL UNION ALL
SELECT DATE '2018-09-18' + INTERVAL '02:00' HOUR TO MINUTE, NULL, 1 FROM DUAL UNION ALL
SELECT DATE '2018-09-18' + INTERVAL '03:00' HOUR TO MINUTE, 'E', 1 FROM DUAL UNION ALL
SELECT DATE '2018-09-18' + INTERVAL '05:10' HOUR TO MINUTE, 'F', 1 FROM DUAL;
Query 1:
SELECT b_id,
TO_CHAR( TRUNC( dt, 'HH' ), 'YYYY-MM-DD HH24:MI:SS' ) AS hour,
COUNT(RESPONSE) AS total_response_per_hour,
COUNT(*) AS total_per_hour,
total_response_per_day,
total_per_day,
COUNT(response) / total_response_per_day AS ratio_for_responses,
COUNT(*) / total_per_day AS ratio
FROM (
SELECT A.*,
COUNT(RESPONSE) OVER ( PARTITION BY b_id, TRUNC( dt ) ) AS total_response_per_day,
COUNT(*) OVER ( PARTITION BY b_id, TRUNC( dt ) ) AS total_per_day
FROM A
)
GROUP BY
b_id,
total_per_day,
total_response_per_day,
TRUNC( dt, 'HH' )
ORDER BY
TRUNC( dt, 'HH' )
Results:
| B_ID | HOUR | TOTAL_RESPONSE_PER_HOUR | TOTAL_PER_HOUR | TOTAL_RESPONSE_PER_DAY | TOTAL_PER_DAY | RATIO_FOR_RESPONSES | RATIO |
|------|---------------------|-------------------------|----------------|------------------------|---------------|---------------------|-------|
| 1 | 2018-09-18 00:00:00 | 2 | 3 | 6 | 8 | 0.3333333333333333 | 0.375 |
| 1 | 2018-09-18 01:00:00 | 2 | 2 | 6 | 8 | 0.3333333333333333 | 0.25 |
| 1 | 2018-09-18 02:00:00 | 0 | 1 | 6 | 8 | 0 | 0.125 |
| 1 | 2018-09-18 03:00:00 | 1 | 1 | 6 | 8 | 0.16666666666666666 | 0.125 |
| 1 | 2018-09-18 05:00:00 | 1 | 1 | 6 | 8 | 0.16666666666666666 | 0.125 |

SELECT withResponses.hour,
withResponses.cnt AS withResponse,
alls.cnt AS AllEntries,
(withResponses.cnt / alls.cnt) AS ratio
FROM
( SELECT to_char(d, 'DD-MM-YY - HH24') || ':00 to :59 ' hour,
count(*) AS cnt
FROM A
WHERE RESPONSE IS NOT NULL
GROUP BY to_char(d, 'DD-MM-YY - HH24') || ':00 to :59 ' ) withResponses,
( SELECT to_char(d, 'DD-MM-YY - HH24') || ':00 to :59 ' hour,
count(*) AS cnt
FROM A
GROUP BY to_char(d, 'DD-MM-YY - HH24') || ':00 to :59 ' ) alls
WHERE alls.hour = withResponses.hour ;
SQLFiddle: http://sqlfiddle.com/#!4/c09b9/2

Related

Oracle generating schedule rows with an interval

I have some SQL that generates rows for every 5 minutes. How can this be modified to get rid of overlapping times (see below)
Note: Each row should be associated with a location_id with no repeats on the location_id. In this case there should be 25 rows generated so the CONNECT by should be something like SELECT count(*) from locations.
My goal is to create a function that takes in a schedule_id and a start_date in the format
'MMDDYYYY HH24:MI'; and stop creating rows if the next entry will cross midnight; that means some of the location_id may not be used.
The end result is to have the rows placed in the schedule table below. Since I don't have a function yet the schedule_id can be hard coded to 1. I've heard about recursive CTE, would this quality for that method?
Thanks in advance to all who answer and your expertise.
ALTER SESSION SET NLS_DATE_FORMAT = 'MMDDYYYY HH24:MI:SS';
create table schedule(
schedule_id NUMBER(4),
location_id number(4),
start_date DATE,
end_date DATE,
CONSTRAINT start_min check (start_date=trunc(start_date,'MI')),
CONSTRAINT end_min check (end_date=trunc(end_date,'MI')),
CONSTRAINT end_gt_start CHECK (end_date >= start_date),
CONSTRAINT same_day CHECK (TRUNC(end_date) = TRUNC(start_date))
);
CREATE TABLE locations AS
SELECT level AS location_id,
'Door ' || level AS location_name,
CASE. round(dbms_random.value(1,3))
WHEN 1 THEN 'A'
WHEN 2 THEN 'T'
WHEN 3 THEN 'G'
END AS location_type
FROM dual
CONNECT BY level <= 25;
with
row_every_5_mins as
( select trunc(sysdate) + (rownum-1)*5/1440 t_from,
trunc(sysdate) + rownum*5/1440 t_to
from dual
connect by level <= 1440/5
) SELECT * from row_every_5_mins;
Current output:
|T_FROM|T_TO|
|-----------------|-----------------|
|08162021 00:00:00|08162021 00:05:00|
|08162021 00:05:00|08162021 00:10:00|
|08162021 00:10:00|08162021 00:15:00|
|08162021 00:15:00|08162021 00:20:00|
…
Desired output
|T_FROM|T_TO|
|-----------------|-----------------|
|08162021 00:00:00|08162021 00:05:00|
|08162021 00:10:00|08162021 00:15:00|
|08162021 00:20:00|08162021 00:25:00|
…
You may avoid recursive query or loop, because you essentially need a row number of each row in locations table. So you'll need to provide an appropriate sort order to the analytic function. Below is the query:
with a as (
select
date '2021-01-01'
+ to_dsinterval('0 23:30:00')
as start_dt_param
from dual
)
, date_gen as (
select
location_id
, start_dt_param
, start_dt_param + (row_number() over(order by location_id) - 1)
* interval '10' minute as start_dt
, start_dt_param + (row_number() over(order by location_id) - 1)
* interval '10' minute + interval '5' minute as end_dt
from a
cross join locations
)
select
location_id
, start_dt
, end_dt
from date_gen
where end_dt < trunc(start_dt_param + 1)
LOCATION_ID | START_DT | END_DT
----------: | :------------------ | :------------------
1 | 2021-01-01 23:30:00 | 2021-01-01 23:35:00
2 | 2021-01-01 23:40:00 | 2021-01-01 23:45:00
3 | 2021-01-01 23:50:00 | 2021-01-01 23:55:00
UPD:
Or if you wish a procedure, then it is even simpler. Because from 12c Oracle has fetch first addition, and analytic function may be simplified to rownum pseudocolumn:
create or replace procedure populate_schedule (
p_schedule_id in number
, p_start_date in date
) as
begin
insert into schedule (schedule_id, location_id, start_date, end_date)
select
p_schedule_id
, location_id
, p_start_date + (rownum - 1) * interval '10' minute
, p_start_date + (rownum - 1) * interval '10' minute + interval '5' minute
from locations
/*Put your order of location assignment here*/
order by location_id
/*The number of 10-minute intervals before midnight from the first end_date*/
fetch first ((trunc(p_start_date + 1) - p_start_date + 1/24/60*5)*24*60/10) rows only
;
commit;
end;
/
begin
populate_schedule(1, timestamp '2020-01-01 23:37:00');
populate_schedule(2, timestamp '2020-01-01 23:35:00');
populate_schedule(3, timestamp '2020-01-01 23:33:00');
end;/
select *
from schedule
order by schedule_id, start_date
SCHEDULE_ID | LOCATION_ID | START_DATE | END_DATE
----------: | ----------: | :------------------ | :------------------
1 | 1 | 2020-01-01 23:37:00 | 2020-01-01 23:42:00
1 | 2 | 2020-01-01 23:47:00 | 2020-01-01 23:52:00
2 | 1 | 2020-01-01 23:35:00 | 2020-01-01 23:40:00
2 | 2 | 2020-01-01 23:45:00 | 2020-01-01 23:50:00
2 | 3 | 2020-01-01 23:55:00 | 2020-01-02 00:00:00
3 | 1 | 2020-01-01 23:33:00 | 2020-01-01 23:38:00
3 | 2 | 2020-01-01 23:43:00 | 2020-01-01 23:48:00
3 | 3 | 2020-01-01 23:53:00 | 2020-01-01 23:58:00
db<>fiddle here
Just loop every 10 minutes instead of every 5 minutes:
WITH input (start_time) AS (
SELECT TRUNC(SYSDATE) + INTERVAL '23:30' HOUR TO MINUTE FROM DUAL
)
SELECT start_time + (LEVEL-1) * INTERVAL '10' MINUTE
AS t_from,
start_time + (LEVEL-1) * INTERVAL '10' MINUTE + INTERVAL '5' MINUTE
AS t_to
FROM input
CONNECT BY (LEVEL-1) * INTERVAL '10' MINUTE < INTERVAL '1' DAY
AND LEVEL <= (SELECT COUNT(*) FROM locations)
AND start_time + (LEVEL-1) * INTERVAL '10' MINUTE < TRUNC(start_time) + INTERVAL '1' DAY;
db<>fiddle here
A CTE is certainly the fastest solution. If you like to get more flexibility for intervals then you can use the SCHEDULER SCHEDULE. As drawback the performance might be weaker.
CREATE OR REPLACE TYPE TimestampRecType AS OBJECT (
T_FROM TIMESTAMP(0),
T_TO TIMESTAMP(0)
);
CREATE OR REPLACE TYPE TimestampTableType IS TABLE OF TimestampRecType;
CREATE OR REPLACE FUNCTION GetGchedule(
start_time IN TIMESTAMP,
stop_time in TIMESTAMP DEFAULT TRUNC(SYSDATE)+1)
RETURN TimestampTableType AS
ret TimestampTableType := TimestampTableType();
return_date_after TIMESTAMP := start_time;
next_run_date TIMESTAMP ;
BEGIN
LOOP
DBMS_SCHEDULER.EVALUATE_CALENDAR_STRING('FREQ=MINUTELY;INTERVAL=5;', NULL, return_date_after, next_run_date);
ret.EXTEND;
ret(ret.LAST) := TimestampRecType(return_date_after, next_run_date);
return_date_after := next_run_date;
EXIT WHEN next_run_date >= stop_time;
END LOOP;
RETURN ret;
END;
SELECT *
FROM TABLE(GetGchedule(trunc(sysdate)));
See syntax for calendar here: Calendaring Syntax

How to select data from day by day with special time?

I want to select data day by day. Condition: data will be cut the day from 10:00 pm to tomorrow 10 pm. For example:
| REGTIME | T8 | T9 | T10 |
| 2008-12-03 | 879 | 785| 542 |
| 2008-12-04 | 556 | 454| 321 |
| 2008-12-05 | 678 | 122| 212 |
| 2008-12-06 | 887 | 544| 214 |
(2008-12-03 data will be show from 2008-12-02 10:00pm to 2008-12-03 10:00 pm)...
That query shows me all date, but it shows day with all same value.
SELECT REGTIME2,
A.T8,T9,T10
FROM
( SELECT
SUM(CASE WHEN sdudent_type = 'AAAA1' THEN 1 ELSE 0 END) AS T8,
SUM(CASE WHEN sdudent_type = 'AAAA2' THEN 1 ELSE 0 END) AS T9,
SUM(CASE WHEN sdudent_type = 'AAAA3' THEN 1 ELSE 0 END) AS T10
FROM class_men C
WHERE REGTIME >= TO_DATE(:REGTIME_from,'YYYYMMDDHH24MISS')
AND REGTIME < TO_DATE(:REGTIME_to,'YYYYMMDDHH24MISS')
) A,
(
select distinct
to_char(REGTIME,'YYYY-MM-DD') AS REGTIME2
from class_men
group by REGTIME
order by to_char(REGTIME,'YYYY-MM-DD') desc
) B
You are getting the results duplicated for all days as you are using a CROSS JOIN (written in the legacy syntax using a comma) to join the two SELECT clauses and there is no correlation between those two statements.
You appear to want something like:
SELECT TRUNC( RegTime ) AS RegTime,
COUNT(CASE WHEN sdudent_type = 'AAAA1' THEN 1 END) AS T8,
COUNT(CASE WHEN sdudent_type = 'AAAA2' THEN 1 END) AS T9,
COUNT(CASE WHEN sdudent_type = 'AAAA3' THEN 1 END) AS T10
FROM class_men C
WHERE REGTIME >= TO_DATE(:REGTIME_from,'YYYYMMDDHH24MISS')
AND REGTIME < TO_DATE(:REGTIME_to,'YYYYMMDDHH24MISS')
GROUP BY TRUNC( RegTime );
or using TO_CHAR( RegTime, 'YYYY-MM-DD' ) instead of TRUNC( RegTime ).
Which, for the sample data:
CREATE TABLE class_men ( regTime, sdudent_type ) AS
SELECT DATE '2021-01-01' + INTERVAL '1' MINUTE * LEVEL, 'AAAA1'
FROM DUAL CONNECT BY LEVEL <= 250 UNION ALL
SELECT DATE '2021-01-01' + INTERVAL '1' MINUTE * LEVEL, 'AAAA2'
FROM DUAL CONNECT BY LEVEL <= 42 UNION ALL
SELECT DATE '2021-01-01' + INTERVAL '1' MINUTE * LEVEL, 'AAAA3'
FROM DUAL CONNECT BY LEVEL <= 13 UNION ALL
SELECT DATE '2021-01-02' + INTERVAL '1' MINUTE * LEVEL, 'AAAA1'
FROM DUAL CONNECT BY LEVEL <= 99 UNION ALL
SELECT DATE '2021-01-02' + INTERVAL '1' MINUTE * LEVEL, 'AAAA2'
FROM DUAL CONNECT BY LEVEL <= 17 UNION ALL
SELECT DATE '2021-01-02' + INTERVAL '1' MINUTE * LEVEL, 'AAAA3'
FROM DUAL CONNECT BY LEVEL <= 24 UNION ALL
SELECT DATE '2021-01-03' + INTERVAL '1' MINUTE * LEVEL, 'AAAA1'
FROM DUAL CONNECT BY LEVEL <= 23 UNION ALL
SELECT DATE '2021-01-03' + INTERVAL '1' MINUTE * LEVEL, 'AAAA3'
FROM DUAL CONNECT BY LEVEL <= 50;
Would output (for the range 20210101000000 to 20210104000000):
REGTIME | T8 | T9 | T10
:------------------ | --: | -: | --:
2021-01-01 00:00:00 | 250 | 42 | 13
2021-01-03 00:00:00 | 23 | 0 | 50
2021-01-02 00:00:00 | 99 | 17 | 24
(Note: the date format depends on the NLS_DATE_FORMAT session parameter [unless you use TO_CHAR].)
db<>fiddle here

Counting records and grouping them by the hour

I'm trying to count the records in my table and grouping them by hour, i'm getting results with my query but I want it to return every hour even if there are no records.
My current query is,
SELECT nvl(count(*),0) AS transactioncount, trunc(date_modified, 'HH') as TRANSACTIONDATE
FROM TABLE
WHERE date_modified between to_date('23-JAN-19 07:00:00','dd-MON-yy hh24:mi:ss') and to_date('24-Jan-19 06:59:59','dd-MON-yy hh24:mi:ss')
group by trunc(date_modified, 'HH');
This returns a result like this,
TRANSACTIONCOUNT | TRANSACTIONDATE
43 | 23-Jan-19 07:00:00
47 | 23-Jan-19 08:00:00
156 | 23-Jan-19 14:00:00
558 | 23-Jan-19 15:00:00
What I want is for it to return every hour between my 2 dates so,
TRANSACTIONCOUNT | TRANSACTIONDATE
43 | 23-Jan-19 07:00:00
47 | 23-Jan-19 08:00:00
0 | 23-Jan-19 09:00:00
0 | 23-Jan-19 10:00:00
0 | 23-Jan-19 11:00:00
0 | 23-Jan-19 12:00:00
0 | 23-Jan-19 13:00:00
156 | 23-Jan-19 14:00:00
558 | 23-Jan-19 15:00:00
--......
0 | 24-Jan-19 00:00:00
0 | 24-Jan-19 01:00:00
0 | 24-Jan-19 02:00:00
--and so on
To fill the holes in the transaction hours you create first a complete table of hours.
You may use Recursive Subquery Factoring to do it
WITH hour_table(TRANSACTIONDATE) AS (
SELECT to_date('23-JAN-19 07:00:00','dd-MON-yy hh24:mi:ss') /* init hour here */
FROM DUAL
UNION ALL
SELECT TRANSACTIONDATE + 1/24
FROM hour_table
WHERE TRANSACTIONDATE + 1/24 < to_date('24-JAN-19 06:59:59','dd-MON-yy hh24:mi:ss') /* limit here */
)
select * from hour_table;
TRANSACTIONDATE
-------------------
23.01.2019 07:00:00
23.01.2019 08:00:00
...
24.01.2019 05:00:00
24.01.2019 06:00:00
Note that you use the staring and ending date in this query, the starting date must be exact an hour.
Next step is as simple as to outer join this hour table to your aggregation and set the default value for the missing hours with NVL.
with hour_table(TRANSACTIONDATE) AS (
SELECT to_date('23-JAN-19 07:00:00','dd-MON-yy hh24:mi:ss') /* init hour here */
FROM DUAL
UNION ALL
SELECT TRANSACTIONDATE + 1/24
FROM hour_table
WHERE TRANSACTIONDATE + 1/24 < to_date('24-JAN-19 06:59:59','dd-MON-yy hh24:mi:ss') /* limit */
),
agg as (
SELECT nvl(count(*),0) AS transactioncount, trunc(date_modified, 'HH') as TRANSACTIONDATE
FROM "TABLE"
WHERE date_modified between to_date('23-JAN-19 07:00:00','dd-MON-yy hh24:mi:ss') and to_date('24-Jan-19 06:59:59','dd-MON-yy hh24:mi:ss')
group by trunc(date_modified, 'HH')
)
select t.TRANSACTIONDATE, nvl(transactioncount,0) transactioncount
from hour_table t
left outer join agg a
on t.TRANSACTIONDATE = a.TRANSACTIONDATE
order by 1;
You might consider using the following with CONNECT BY level logic :
SELECT sum(transactioncount) as transactioncount, transactiondate
FROM
(
with "TABLE"(date_modified) as
(
SELECT timestamp'2019-01-23 08:00:00' FROM dual union all
SELECT timestamp'2019-01-23 08:30:00' FROM dual union all
SELECT timestamp'2019-01-23 09:00:00' FROM dual union all
SELECT timestamp'2019-01-24 05:01:00' FROM dual
)
SELECT nvl(count(*),0) AS transactioncount, trunc(date_modified, 'hh24') as transactiondate
FROM "TABLE" t
GROUP BY trunc(date_modified, 'HH24')
UNION ALL
SELECT 0, timestamp'2019-01-23 07:00:00' + ( level - 1 )/24
FROM dual
CONNECT BY level <= 24 * extract( day from
timestamp'2019-01-24 06:59:59'-
timestamp'2019-01-23 07:00:00') +
extract( hour from
timestamp'2019-01-24 06:59:59'-
timestamp'2019-01-23 07:00:00') + 1
)
GROUP BY transactiondate
ORDER BY transactiondate
Rextester Demo

how to convert HH:MM representation to minutes in oracle sql

how to convert varchar(hh:mm) to minutes in oracle sql.
For example:
HH:MM Minutes
08:00 480
08:45 525
07:57 477
This will work even if the duration is 24 hours or greater:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE durations ( duration ) AS
SELECT '00:30' FROM DUAL UNION ALL
SELECT '07:57' FROM DUAL UNION ALL
SELECT '08:00' FROM DUAL UNION ALL
SELECT '12:00' FROM DUAL UNION ALL
SELECT '20:01' FROM DUAL UNION ALL
SELECT '23:59' FROM DUAL UNION ALL
SELECT '24:00' FROM DUAL UNION ALL
SELECT '24:59' FROM DUAL;
Query 1:
SELECT duration,
( (
DATE '1970-01-01'
+ NUMTODSINTERVAL( SUBSTR( duration, 1, INSTR( duration, ':' ) - 1 ), 'HOUR' )
+ NUMTODSINTERVAL( SUBSTR( duration, INSTR( duration, ':' ) + 1 ), 'MINUTE' )
)
- DATE '1970-01-01'
) * 24 * 60 AS Minutes
FROM durations
Results:
| DURATION | MINUTES |
|----------|---------|
| 00:30 | 30 |
| 07:57 | 477 |
| 08:00 | 480 |
| 12:00 | 720 |
| 20:01 | 1201 |
| 23:59 | 1439 |
| 24:00 | 1440 |
| 24:59 | 1499 |
However, there is an INTERVAL DAY TO SECOND data type that would be better suited to your data:
CREATE TABLE your_table (
duration INTERVAL DAY TO SECOND
);
Then you can just do:
INSERT INTO your_table ( duration ) VALUES ( INTERVAL '08:00' HOUR TO MINUTE );
To get the number of minutes you can then simply do:
SELECT ( ( DATE '1970-01-01' + duration ) - DATE '1970-01-01' ) *24*60 AS minutes
FROM your_table
Try this
TO_NUMBER(SUBSTR('(08:00)',2,INSTR('(08:00)',':')-2))*60+TO_NUMBER(SUBSTR('(08:00)',INSTR('(08:00)',':')+1,2))
If you can convert your input to a real date first, the task becomes much easier. Here, I have shamelessly appended the time to a fake date to create a date such as 2017-01-01 00:30. To find out the number of minutes since midnight, you simply subtract the date for "midnight". It will return the difference in days, so you need to multiply by number of minutes per day to get what you want.
select time
,(to_date('2017-01-01 ' || time, 'yyyy-mm-dd hh24:mi') - date '2017-01-01') * 24 * 60 as minutes
from (select '00:30' as time from dual union all
select '08:00' as time from dual union all
select '08:30' as time from dual union all
select '12:00' as time from dual union all
select '23:59' as time from dual
);
Here is some sample input and output
time minutes
==== =======
00:30 30
08:00 480
08:30 510
12:00 720
23:59 1 439
If you require to Print 08:00 hours as 480 minutes,
Extract the Digit before : and multply with 60 and add the digit after :. So you can convert the HH:MM representation in to minutes.
SELECT REGEXP_SUBSTR(ATT.workdur,'[^:]+',1,1)*60 + REGEXP_SUBSTR(ATT.workdur,'[^:]+',1,2) MINUTES FROM DUAL;

Split week based on weightage

I have a weights defined like below in a table.
DayNum | Day | Weight | Cumulative Weight
1 | MON | 0.3 | 0.3
2 | TUE | 0.15 | 0.45 (Sum of Mon and Tues)
3 | WED | 0.1 | 0.55 (Sum of Mon and Tues and Wed)
4 | THU | 0.1 | 0.65
5 | FRI | 0.15 | 0.8
6 | SAT | 0.2 | 1
And I have amounts in another table defined at weekly level (Mon - Sun) like below.
Item | Date | Amount
A | 30-May-16 | 10 ---- Week in May and June
A | 6-Jun-16 | 20
A | 13-Jun-16 | 30 and so on
A | 27-Jun-16 | 60 ---- Week in Jun and July
Now I want to insert into another table at a daily level, for the weeks which are overlapping between 2 different months (in above example - 30 May to 5 Jun).
Can anyone explain how I can achieve this in Oracle.
Output should be like below.
Item | Date | Amount
A | 30-May-16 | 4.5 (2 days from May which are Mon and Tues - so calculation is 10 * 0.45)
A | 1-Jun-16 | 5.5 (5 days from May which is the rest of the week - 10 minus 4.5)
A | 6-Jun-16 | 20 and so on
A | 27-Jun-16 | 39 (4 days from June which are Mon till Thurs - so calculation is 60 * 0.65)
A | 1-Jul-16 | 21 (3 days from July which is the rest of the week - 60 minus 39)
Try:
WITH some_data AS(
select a.*,
trunc( trunc( add_months( "DATE", 1 ), 'MM' ) - "DATE" )
As days_to_end_of_month,
trunc( add_months( "DATE", 1 ), 'MM' )
As start_of_next_month
from amounts a
), some_other_data AS (
SELECT some_data.*,
CASE WHEN days_to_end_of_month >= 6 THEN Amount
ELSE ( SELECT some_data.amount * "Cumulative Weight" FROM Weights w
WHERE some_data.days_to_end_of_month = DayNum )
END as new_Amount
FROM some_data
)
SELECT Item, "DATE", New_Amount as amount
FROM some_other_data
UNION ALL
SELECT Item, start_of_next_month, amount-new_amount
FROM some_other_data
WHERE days_to_end_of_month < 6
ORDER BY "DATE"
;
Oracle Setup:
CREATE TABLE Weights ( DayNum, Day, Weight ) AS
SELECT 1, 'MON', 0.3 FROM DUAL UNION ALL
SELECT 2, 'TUE', 0.15 FROM DUAL UNION ALL
SELECT 3, 'WED', 0.1 FROM DUAL UNION ALL
SELECT 4, 'THU', 0.1 FROM DUAL UNION ALL
SELECT 5, 'FRI', 0.15 FROM DUAL UNION ALL
SELECT 6, 'SAT', 0.2 FROM DUAL;
CREATE TABLE weekly_levels ( Item, "Date", Amount ) AS
SELECT 'A', DATE '2016-05-30', 10 FROM DUAL UNION ALL
SELECT 'A', DATE '2016-06-06', 20 FROM DUAL UNION ALL
SELECT 'A', DATE '2016-06-13', 30 FROM DUAL UNION ALL
SELECT 'A', DATE '2016-06-27', 60 FROM DUAL;
Query:
SELECT item,
start_date,
SUM( amount * weight ) AS amount
FROM (
SELECT item,
"Date" AS start_date,
LEAST( "Date" + INTERVAL '6' DAY, LAST_DAY( "Date" ) ) AS end_date,
amount
FROM weekly_levels
UNION
SELECT item,
GREATEST( "Date", TRUNC( "Date" + INTERVAL '6' DAY, 'MM' ) ) AS start_date,
"Date" + INTERVAL '6' DAY AS end_date,
amount
FROM weekly_levels
) d
INNER JOIN
Weights w
ON ( w.DayNum BETWEEN TO_CHAR( start_date, 'D' )
AND TO_CHAR( end_date, 'D' ) )
GROUP BY item, start_date
ORDER BY item, start_date;
Output:
ITEM START_DATE AMOUNT
---- ------------------- ----------
A 2016-05-30 00:00:00 4.5
A 2016-06-01 00:00:00 5.5
A 2016-06-06 00:00:00 20
A 2016-06-13 00:00:00 30
A 2016-06-27 00:00:00 39
A 2016-07-01 00:00:00 21