Insert one row per hour between two date time range including duration (Oracle) - oracle11g

I have two date time records "start_date" and "end_date" from date_range table.
I would like to insert one row per hour for each hour interval, plus a column with the duration (in hours) so that my results return as follows:
e.g. start_date = 2016/09/01 21:12:00 and end_date = 2016/09/02 01:30:00
Date Hour Duration
2016/09/01 21 0.8
2016/09/01 22 1
2016/09/01 23 1
2016/09/02 00 1
2016/09/02 01 0.5

Here is a plain SQL solution (best to avoid PL/SQL when possible and not too complicated). It uses a recursive factored subquery, available since Oracle 11.1. I created several "rows" of test data to show how this might work for more than one pair of inputs at the same time. Please note, the first subquery is not part of the solution - you would replace it (and the references to it in the actual solution, which is the rest of the query) with your actual table and column names, or whatever your input source.
Note also that "date" and "hour" are reserved words in Oracle, and they shouldn't be used as column names (in the output or anywhere else). I used dt and hr instead.
with
date_range ( row_id, start_date, end_date ) as (
select 101, to_date('2016/09/01 21:12:00', 'yyyy/mm/dd hh24:mi:ss'),
to_date('2016/09/02 01:30:00', 'yyyy/mm/dd hh24:mi:ss') from dual union all
select 102, to_date('2016/09/02 21:00:00', 'yyyy/mm/dd hh24:mi:ss'),
to_date('2016/09/02 22:00:00', 'yyyy/mm/dd hh24:mi:ss') from dual union all
select 103, to_date('2016/09/01 15:00:00', 'yyyy/mm/dd hh24:mi:ss'),
to_date('2016/09/01 15:30:00', 'yyyy/mm/dd hh24:mi:ss') from dual union all
select 104, to_date('2016/09/01 21:12:00', 'yyyy/mm/dd hh24:mi:ss'),
to_date('2016/09/01 21:30:00', 'yyyy/mm/dd hh24:mi:ss') from dual
),
rec ( row_id, from_time, to_time, end_date ) as (
select row_id, start_date,
least(end_date, trunc(start_date, 'hh') + 1/24), end_date
from date_range
union all
select row_id, to_time, least(end_date, to_time + 1/24), end_date
from rec
where end_date > from_time + 1/24
)
select row_id,
to_char(from_time, 'yyyy/mm/dd') as dt,
to_char(from_time, 'hh24') as hr,
round(24 * (to_time - from_time), 2) as duration
from rec
order by row_id, from_time
;
Output:
ROW_ID DT HR DURATION
---------- ---------- -- ----------
101 2016/09/01 21 .8
101 2016/09/01 22 1
101 2016/09/01 23 1
101 2016/09/02 00 1
101 2016/09/02 01 .5
102 2016/09/02 21 1
103 2016/09/01 15 .5
104 2016/09/01 21 .3
8 rows selected

You can do it with procedure like this:
first I prepare your data;
create table date_range
(
start_date date,
end_date date
);
create table result_table
(
date_ varchar2(10),
hour_ varchar2(2),
duration number
);
insert into date_range
select to_date('2016/09/01 21:12:00','YYYY/MM/DD hh24:mi:ss'),to_date('2016/09/02 01:30:00','YYYY/MM/DD hh24:mi:ss') from dual ;
commit;
second I create your procedure;
CREATE OR REPLACE procedure get_dates
AS
rn number:=0;
temp varchar2(10);
BEGIN
select
ceil(24* ( end_date-start_date ))-1
into rn
from
date_range;
FOR i IN 0..rn
LOOP
temp:=i||'/24';
EXECUTE IMMEDIATE '
insert into result_table
select
to_char(start_date+'||temp||',''yyyy/mm/dd'')date_,
to_char(start_date+'||temp||',''hh24'')hour_,
case
when '||i||'=0
then 1-to_number(to_char(start_date+'||temp||',''mi''))/60
when '||i||'='||rn||'
then 1-to_number(to_char(end_date+'||temp||',''mi''))/60
else 1
end duration
from
date_range ';
commit;
END LOOP;
END get_dates;
thirdly I execute procedure ;
begin
get_dates();
end;
finally I see the expected result ;
select
* from
result_table;
DATE_ HOUR_ DURATION
2016/09/01 21 0,8
2016/09/01 22 1
2016/09/01 23 1
2016/09/02 00 1
2016/09/02 01 0,5

select case when lvl=mn then (trunc(frst)+1)- frst
when lvl=mx then lst-trunc(lst)
else 1
end ,a.*
from
(
select frst,lst,sonuc,lvl,first_value(lvl)over() mn,last_value(lvl) over() mx
from
(select to_date('2016/09/01 21:12:00','YYYY/MM/DD hh24:mi:ss') frst,
to_date('2016/09/02 01:30:00','YYYY/MM/DD hh24:mi:ss') lst,
(to_date('2016/09/02 01:30:00','YYYY/MM/DD hh24:mi:ss') - to_date('2016/09/01 21:12:00','YYYY/MM/DD hh24:mi:ss'))*24 sonuc from dual) a
,
( select level lvl
from dual
connect by level <=
(select
case when (to_date('2016/09/02 01:30:00','YYYY/MM/DD hh24:mi:ss') - to_date('2016/09/01 21:12:00','YYYY/MM/DD hh24:mi:ss'))*24>
trunc((to_date('2016/09/02 01:30:00','YYYY/MM/DD hh24:mi:ss') - to_date('2016/09/01 21:12:00','YYYY/MM/DD hh24:mi:ss'))*24)
then trunc((to_date('2016/09/02 01:30:00','YYYY/MM/DD hh24:mi:ss') - to_date('2016/09/01 21:12:00','YYYY/MM/DD hh24:mi:ss'))*24)+1
else trunc((to_date('2016/09/02 01:30:00','YYYY/MM/DD hh24:mi:ss') - to_date('2016/09/01 21:12:00','YYYY/MM/DD hh24:mi:ss'))*24)
end
from dual)
) b
)a

Related

select between date range within speicific time period

I have two seperate columns for date and time each being saved in varchar2
I'm trying to query a specific range of time:
i.e. 1/1/2017 - 1/31/2017
between 6PM-6AM each day
So far I did this:
select * from (select a.*,TO_DATE(billdate||' '||billtime,'YYYY/MM/DD HH24:Mi:SS')
as Timex from billtable a where billdate >= '2017/01/01' and billdate <= '2017/01/31')
where timex>=to_date(''2017/01/01 18:00:00','YYYY/MM/DD HH24:Mi:SS')
and timex<=to_date('2017/01/31 06:00:00','YYYY/MM/DD HH24:Mi:SS')
order by billdate
What can I do further or Is It the wrong way Iam going?
Thanks!
Assuming you're stuck with the data model you have (storing dates and/or times as strings, or separately, is not a good idea) and that you are not interested in the six hours before and after the date range, the formats you've used at least allow you to query those ranges fairly simply:
select a.*, to_date(billdate||' '||billtime,'YYYY/MM/DD HH24:Mi:SS') as timex
from billtable a
where billdate >= '2017/01/01'
and billdate <= '2017/01/31'
and (billtime <= '06:00:00' or billtime >= '18:00:00')
order by billdate, billtime;
With some sample data provided in a CTE:
alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS';
with billtable (billdate, billtime) as (
select '2017/01/01', '00:00:00' from dual
union all select '2017/01/01', '06:00:00' from dual
union all select '2017/01/01', '06:00:01' from dual
union all select '2017/01/31', '17:59:59' from dual
union all select '2017/01/31', '18:00:00' from dual
union all select '2017/01/31', '23:59:59' from dual
)
select a.*, to_date(billdate||' '||billtime,'YYYY/MM/DD HH24:Mi:SS') as timex
from billtable a
where billdate >= '2017/01/01'
and billdate <= '2017/01/31'
and (billtime <= '06:00:00' or billtime >= '18:00:00')
order by billdate, billtime;
BILLDATE BILLTIME TIMEX
---------- -------- -------------------
2017/01/01 00:00:00 2017-01-01 00:00:00
2017/01/01 06:00:00 2017-01-01 06:00:00
2017/01/31 18:00:00 2017-01-31 18:00:00
2017/01/31 23:59:59 2017-01-31 23:59:59
If you already had a date, or were converting to a date - or in fact a timestamp to make this work - you could do:
select billdate, billtime, cast(timex as date)
from (
select a.*, to_timestamp(billdate||' '||billtime,'YYYY/MM/DD HH24:Mi:SS') as timex
from billtable a
where billdate >= '2017/01/01' and billdate <= '2017/01/31'
)
where extract(hour from timex) < 6
or (extract(hour from timex) = 6 and extract(minute from timex) = 0 and extract(second from timex) = 0)
or extract(hour from timex) >= 18
order by timex;

Create multiple rows based off a date range

I have a calendar query and a table below. I have a StartDate and end date for a member. Also on my calendar table I have captured a "Weekof" based on the startDate. I would like to capture if a member is active anytime during that weekof. See expected results.
SELECT DISTINCT
--CA.CALENDAR_DATE,
TO_CHAR(CALENDAR_DATE,'MM/DD/YYYY') AS CALENDAR_DATE
TO_CHAR(NEXT_DAY(CALENDAR_DATE, 'Monday') - 7, 'MM/DD/YY-') ||
TO_CHAR(NEXT_DAY(CALENDAR_DATE, 'Monday') - 1, 'MM/DD/YY') AS WEEK_OF_YEAR,
ROW_NUMBER () OVER ( ORDER BY CALENDAR_DATE) AS MasterCalendar_RNK
FROM CALENDAR CA
WHERE 1=1
--AND CA.CALENDAR_DATE BETWEEN ADD_MONTHS(TRUNC(SYSDATE), -12) AND TRUNC(SYSDATE)
--AND CA.CALENDAR_DATE BETWEEN TRUNC(SYSDATE) -5 AND TRUNC(SYSDATE)
ORDER BY TO_DATE(CALENDAR_DATE,'MM/DD/YYYY') DESC
Table
Member StartDate EndDate
A 1/31/17
B 2/1/17 2/15/17
Expected output:
Member StartDate EndDate Week_Of_Year Active
A 1/31/17 1/30/17-2/5/17 1
A 1/31/17 2/6/17-2/12/17 1
A 1/31/17 2/13/17-2/19/17 1
B 2/1/17 2/15/17 1/30/17/2/5/17 1
B 2/1/17 2/15/17 2/6/17-2/12/17 1
B 2/1/17 2/15/17 2/13/17-2/19/17 1
Current Query:
WITH MASTER_CALENDAR AS (
SELECT TRUNC(SYSDATE) + 1 - LEVEL , A.CALENDAR_DATE
FROM (SELECT C.CALENDAR_DATE FROM MST.CALENDAR C WHERE 1=1 AND C.CALENDAR_DATE > SYSDATE-30 AND C.CALENDAR_DATE < SYSDATE) A
WHERE 1=1
CONNECT BY LEVEL <= 1 --NEED TO UPDATE?
ORDER BY A.CALENDAR_DATE DESC
),
ActiveMembers AS (
SELECT H.CLT_CLT_PGMID, H.START_DT
,CASE WHEN TRUNC(H.END_DT) = '1-JAN-3000'
THEN SYSDATE
ELSE TO_DATE(H.END_DT)
END AS END_DT
FROM H
WHERE 1=1
AND H.CLT_CLT_PGMID IN ('1','2','3')
)
SELECT CLT_CLT_PGMID, STARTDATE, ENDDATE, WEEK_OF_YEAR, ACTIVE -- but not week_start
FROM (
SELECT DISTINCT A.CLT_CLT_PGMID,
TO_CHAR(A.START_DT, 'MM/DD/YY') AS STARTDATE,
TO_CHAR(A.END_DT, 'MM/DD/YY') AS ENDDATE,
NEXT_DAY(CAL.CALENDAR_DATE, 'Monday') - 7 AS WEEK_START, -- for ordering later
TO_CHAR(NEXT_DAY(CAL.CALENDAR_DATE, 'Monday') - 7, 'MM/DD/YY-') ||
TO_CHAR(NEXT_DAY(CAL.CALENDAR_DATE, 'Monday') - 1, 'MM/DD/YY') AS WEEK_OF_YEAR,
1 AS ACTIVE
FROM ActiveMembers A
INNER JOIN MASTER_CALENDAR CAL ON CAL.CALENDAR_DATE BETWEEN A.START_DT AND A.END_DT
--BETWEEN TO_CHAR(A.START_DT,'MM/DD/YYYY') AND COALESCE(A.END_DT,(SYSDATE))
)
WHERE 1=1
ORDER BY
CLT_CLT_PGMID , STARTDATE, ENDDATE, WEEK_START
;
Since the calendar query currently generates strings, it would be simpler to go back to the calendar table, join that to your member/date table, and regenerate the week range string:
With CTEs to represent your calendar table (just with dates for the last few weeks for now) and member data:
with calendar(calendar_date) as (
select trunc(sysdate) + 1 - level from dual connect by level <= 42
),
mytable (member, startdate, enddate) as (
select cast('A' as varchar2(6)), date '2017-01-31', cast (null as date) from dual
union all select cast('B' as varchar2(6)), date '2017-02-01', date '2017-02-15' from dual
)
select member, startdate, enddate, week_of_year, active -- but not week_start
from (
select distinct m.member,
to_char(m.startdate, 'MM/DD/YY') as startdate,
to_char(m.enddate, 'MM/DD/YY') as enddate,
next_day(c.calendar_date, 'Monday') - 7 as week_start, -- for ordering later
to_char(next_day(c.calendar_date, 'Monday') - 7, 'MM/DD/YY-') ||
to_char(next_day(c.calendar_date, 'Monday') - 1, 'MM/DD/YY') as week_of_year,
1 as active
from mytable m
join calendar c
on c.calendar_date between m.startdate and coalesce(m.enddate, trunc(sysdate))
)
order by member, startdate, enddate, week_start;
gets
MEMBER STARTDAT ENDDATE WEEK_OF_YEAR ACTIVE
------ -------- -------- ----------------- ----------
A 01/31/17 01/30/17-02/05/17 1
A 01/31/17 02/06/17-02/12/17 1
A 01/31/17 02/13/17-02/19/17 1
A 01/31/17 02/20/17-02/26/17 1
B 02/01/17 02/15/17 01/30/17-02/05/17 1
B 02/01/17 02/15/17 02/06/17-02/12/17 1
B 02/01/17 02/15/17 02/13/17-02/19/17 1
You haven't specified an upper limit for members with no end-date, so I've used today, via coalesce().
The inner query is only needed for ordering, as the week range string can't be used, and you don't want to see the week start on its own; and you can't use distinct and order by a field you aren't selecting.
I'd do this in a similar way to Alex, but slightly different. Seeing as your weeks start with a Monday, I'd use TRUNC(dt, 'iw') to get the ISO start of the week (which happens to be defined as a Monday) for the specified date. Then I'd get the distinct values of those before joining to your table, like so:
with calendar as (select trunc(sysdate) - level + 1 calendar_date
from dual
connect by level <= 50),
your_table as (select 'A' member, date '2017-01-31' startdate, NULL enddate from dual union all
select 'B' member, date '2017-02-01' startdate, date '2017-02-15' enddate from dual)
select yt.member,
yt.startdate,
yt.enddate,
to_char(c.week_start, 'mm/dd/yyyy')
|| ' - ' || to_char(c.week_start + 6, 'mm/dd/yyyy') week_of_year,
1 as active
from your_table yt
inner join (select distinct trunc(cl.calendar_date, 'iw') week_start
from calendar cl) c on c.week_start <= nvl(yt.enddate, SYSDATE) AND c.week_start + 6 >= yt.startdate
order by yt.member,
c.week_start;
MEMBER STARTDATE ENDDATE WEEK_OF_YEAR ACTIVE
------ ---------- ---------- ----------------------- ----------
A 01/31/2017 01/30/2017 - 02/05/2017 1
A 01/31/2017 02/06/2017 - 02/12/2017 1
A 01/31/2017 02/13/2017 - 02/19/2017 1
A 01/31/2017 02/20/2017 - 02/26/2017 1
B 02/01/2017 02/15/2017 01/30/2017 - 02/05/2017 1
B 02/01/2017 02/15/2017 02/06/2017 - 02/12/2017 1
B 02/01/2017 02/15/2017 02/13/2017 - 02/19/2017 1
Like Alex, I've assumed your null enddate runs up until today (sysdate). However, looking at your results for member B, it looks like you're looking for an overlapping range (since 30th Jan is not between 1st and 15th Feb), so I've amended my join clause accordingly. This results in an extra row for member A, so maybe you're wanting to run null enddates up until the previous Sunday of sysdate? Not sure. I'm sure you'll be able to amend that yourself, if you need to.

Oracle Timestamp based calculation on every day using Two datetime column

I have a table that contains two time stamp t1(event open date) and t2(event close date) and a primary key eventid.
If event is open then t2 will be null whenever even gets closed the same row will be get updated with event closure date t2.
For example I want to check how many issues are open on every day bases on opened date (t1) from 01-apr-2016 to 10-apr-2016.
I have to calculate how many events are open for every day based on a selected date range.
Lets say if eventid 1 has got opened on 1st-APR and got closed on 10th-APR and I am calculating the number of opened issues for every day on 11th-APR then it should give me number of open event 1 from 1st-APR to 10th-APR.
Table Structure:-
================================================
EVENTID T1 T2
================================================
1 01-apr-2016 10-apr-2016
2 02-apr-2016 08-apr-2016
3 05-apr-2016 09-apr-2016
Expected Output:-
==============================================================================
DATE TOTAL_OPEN_EVENTS
==============================================================================
01-apr-2016 1
02-apr-2016 2(1 issue open on 1st(not closed on 2nd) and 1 on 2nd)
03-apr-2016 2
04-apr-2016 2
05-apr-2016 3
06-apr-2016 3
07-apr-2016 3
08-apr-2016 2(1 issue got closed on 8th(which was opened on 2nd))
09-apr-2016 2
10-apr-2016 0
How to do this kind of calculation in Oracle database ?
In order to generate the end report, you need a row for each date in your desired range. You could either use a calendar table, if available, or I find using a query on DUAL using CONNECT BY LEVEL < some_number works well to generate rows on the fly. (In this case "some_number" will be the number of days you want to report on.)
From there, you just need to join the individual dates to the date ranges in your event table:
-- create table "events" table
create table event_date_ranges
as
select 1 as event_id, TO_DATE('2016-APR-01', 'YYYY-MM-DD') as start_date, TO_DATE('2016-APR-10', 'YYYY-MON-DD') as end_date from dual
union all
select 2 as event_id, TO_DATE('2016-APR-02', 'YYYY-MM-DD') as start_date, TO_DATE('2016-APR-08', 'YYYY-MON-DD') as end_date from dual
union all
select 3 as event_id, TO_DATE('2016-APR-05', 'YYYY-MM-DD') as start_date, TO_DATE('2016-APR-09', 'YYYY-MON-DD') as end_date from dual
;
with
date_range_qry as
(-- one way to set the start and end dates for your report
select TO_DATE('2016-APR-01', 'YYYY-MM-DD') as report_start_date
, TO_DATE('2016-APR-10', 'YYYY-MM-DD') as report_end_date
from dual
)
, dates_qry
as
(
-- generate a row for all dates between 2016-APR-01 and 2016-APR-10
select report_start_date + ROWNUM - 1 as report_date
from dual
cross join
date_range_qry drq
connect by level <= (drq.report_end_date - drq.report_start_date + 1)
)
select dq.report_date, count(edr.event_id) as total_open_events
from dates_qry dq
left outer join
event_date_ranges edr
on dq.report_date >= edr.start_date
and dq.report_date < edr.end_date
group by dq.report_date
order by dq.report_date
Output:
REPORT_DATE TOTAL_OPEN_EVENTS
2016-APR-01 1
2016-APR-02 2
2016-APR-03 2
2016-APR-04 2
2016-APR-05 3
2016-APR-06 3
2016-APR-07 3
2016-APR-08 2
2016-APR-09 1
2016-APR-10 0
You can try this:
create table events_log
as
select 1 as event_id, TO_DATE('01-04-2016', 'DD/MM/YYYY') as T1, TO_DATE('10-04-2016', 'DD/MM/YYYY') as T2 from dual
union all
select 2 as event_id, TO_DATE('02-04-2016', 'DD/MM/YYYY') as T1, TO_DATE('08-04-2016', 'DD/MM/YYYY') as T2 from dual
union all
select 3 as event_id, TO_DATE('05-04-2016', 'DD/MM/YYYY') as T1, TO_DATE('09-04-2016', 'DD/MM/YYYY') as T2 from dual
;
--------------
select v.REPORT_DATE, count(t.EVENT_ID) as open_event
from events_log t,
(select to_date('01/04/2016', 'DD/MM/YYYY') + ROWNUM - 1 as report_date
from dual
connect by level <= (to_date('11/04/2016', 'DD/MM/YYYY') -
to_date('01/04/2016', 'DD/MM/YYYY') + 1)) v
where t.T1(+) <= v.report_date
and t.T2(+) >= v.report_date
group by v.report_date
order by v.report_date;
Output will be:
report_date open_event
01/04/2016 1
02/04/2016 2
03/04/2016 2
04/04/2016 2
05/04/2016 3
06/04/2016 3
07/04/2016 3
08/04/2016 3
09/04/2016 2
10/04/2016 1
11/04/2016 0

Total Number and Total Value pl/sql

I made a query within a cursor that calculates the total number of cancellations, cancels or returned items and the total value. However, I cannot get the right value for the totals of the items that been cancelled, return or all cancelled
create or replace PROCEDURE NUM_OF_RET_CAN(PRAM_DATE IN DATE)
AS
CURSOR CUR2 IS
SELECT I.CONDITION, I.DEL_DATE, SUM(DE.QUANTITY) NUMBER_OF_PRO,
SUM(NVL(DE.QUANTITY,0) * NVL (P.COSTS,0)) TOTAL
FROM ITEMS I, DE_DETAILS DE, PARTS P
WHERE DE.PRO_NO = P.PRO_NO
AND I.ITEMS_NO = DE.ITEM_NO
AND TO_CHAR(I.DEL_DATE, 'mm-yyyy') = TO_CHAR(PRAM_DATE, 'mm-yyyy')
GROUP BY I.CONDITION, I.DEL_DATE;
CAL_CUR CUR2%ROWTYPE;
BEGIN
OPEN CUR2;
FETCH CUR2 INTO CAL_CUR;
IF VARCUR1.CONDITION ='CANCELL' THEN
DBMS_OUTPUT.PUT_LINE('CANCELLED: '||CAL_CUR.NUMBER_OF_PRO );
DBMS_OUTPUT.PUT_LINE('Total: '|| CAL_CUR.TOTAL);
ELSIF VARCUR1.CONDITION ='ORDER RETURNED' THEN
DBMS_OUTPUT.PUT_LINE('RETURNED : '||
CAL_CUR.NUMBER_OF_PRO);
DBMS_OUTPUT.PUT_LINE('Total : '|| CAL_CUR.TOTAL);
ELSIF VARCUR1.CONDITION = 'ALL ORDERS ARE CANCELLED!' THEN
DBMS_OUTPUT.PUT_LINE('ALL CANCELLATIONS : '||
CAL_CUR.NUMBER_OF_PRO );
DBMS_OUTPUT.PUT_LINE('Total : '|| CAL_CUR.TOTAL);
ELSE
DBMS_OUTPUT.PUT_LINE('No records for this month');
END IF;
CLOSE CUR2;
END NUM_OF_RET_CAN;
If I run the select Query without using the cursor or the procedure I get this result:
CONDITION DEL_DATE NUMBER_OF_PRO TOTAL
------------ ------------- ------------------- ---------
ALL ORDERS ARE CANCELLED! 12-JAN-16 4 99.96
ALL ORDERS ARE CANCELLED! 10-JAN-16 2 44.98
Expected Answer
CONDITION DEL_DATE NUMBER_OF_PRO TOTAL
------------ ------------- ------------------- ---------
ALL ORDERS ARE CANCELLED! JAN-16 6 144.94
Any help would be much appreciated
Sounds like you just need to group by month rather than day, eg:
select i.condition,
trunc(i.del_date, 'mm') del_date,
sum(de.quantity) number_of_pro,
sum(nvl(de.quantity,0) * nvl (p.costs,0)) total
from items i
inner join de_details de on (i.items_no = de.item_no)
inner join parts p on (de.pro_no = p.pro_no)
where trunc(i.del_date, 'mm') = trunc(pram_date, 'mm')
group by i.condition,
trunc(i.del_date, 'mm');
A few notes:
You'll note that I have converted your old-style joins into ANSI join syntax.
You had aliased the items table as "pros" but elsewhere in the query, the alias "i" was referenced. I believe this is what the alias for the items table should have been, so I updated it accordingly.
I've converted your to_char(... 'mm-yyyy')s to trunc(... 'mm') as I believe that comparing columns in the same format as they're defined is more meaningful (leading to easier maintenance etc).
I'm not sure why you're getting the "not a group by expression" error, since it's working for me:
with items as (select 1 items_no, 'a' condition, sysdate -1 del_date from dual union all
select 2 items_no, 'a' condition, sysdate del_date from dual union all
select 3 items_no, 'a' condition, sysdate + 31 del_date from dual),
de_details as (select 1 item_no, 10 quantity, 1 pro_no from dual union all
select 2 item_no, 20 quantity, 2 pro_no from dual union all
select 3 item_no, 40 quantity, 1 pro_no from dual),
parts as (select 1 pro_no, 100 costs from dual union all
select 2 pro_no, 200 costs from dual)
----- end of mimicking your tables with data in them
select i.condition,
trunc(i.del_date, 'mm') del_date,
sum(de.quantity) number_of_pro,
sum(nvl(de.quantity,0) * nvl (p.costs,0)) total
from items i
inner join de_details de on (i.items_no = de.item_no)
inner join parts p on (de.pro_no = p.pro_no)
--where trunc(i.del_date, 'mm') = trunc(pram_date, 'mm')
group by i.condition,
trunc(i.del_date, 'mm');
CONDITION DEL_DATE NUMBER_OF_PRO TOTAL
--------- --------- ------------- ----------
a 01-JAN-16 30 5000
a 01-FEB-16 40 4000
Are you sure that you added the trunc(..., 'mm') to the colunn in both the select and group by colunn lists?
Here's how I'd write the procedure, assuming that I needed to loop through each of the rows returned by the query and display them via dbms_output:
create or replace procedure num_of_ret_can (pram_date in date)
as
cursor cur2 is
select i.condition,
trunc(i.del_date, 'mm') del_date,
sum(de.quantity) number_of_pro,
sum(nvl(de.quantity,0) * nvl (p.costs,0)) total
from items i
inner join de_details de on (i.items_no = de.item_no)
inner join parts p on (de.pro_no = p.pro_no)
where trunc(i.del_date, 'mm') = trunc(pram_date, 'mm')
group by i.condition,
trunc(i.del_date, 'mm');
v_counter number := 0;
begin
for cal_cur in cur2
loop
v_counter := v_counter + 1;
if cal_cur.condition ='CANCELL' then -- are you sure? Not CANCEL?
dbms_output.put_line('CANCELLED: '||cal_cur.number_of_pro );
dbms_output.put_line('Total: '|| cal_cur.total);
elsif cal_cur.condition ='ORDER RETURNED' then
dbms_output.put_line('RETURNED : '||cal_cur.number_of_pro);
dbms_output.put_line('Total : '||cal_cur.total);
elsif cal_cur.condition = 'ALL ORDERS ARE CANCELLED!' then
dbms_output.put_line('ALL CANCELLATIONS : '||cal_cur.number_of_pro );
dbms_output.put_line('Total : '||cal_cur.total);
end if;
end loop;
if v_counter = 0 then
dbms_output.put_line('No records for this month');
end if;
end num_of_ret_can;
/

Calculate Average for the month in SQlite

I have an Sqlite table as below called balance history:
Table Balance History
Date Amount
2013-11-01 16:26:52 1000
2013-11-15 13:20:52 2000
2013-11-27 12:26:55 3000
I would like to calculate the average for the month.
**
The Expected OutPut will be 1666.67
**
Which will be (1000 * 14 days + 2000 * 12 days + 3000 * 4 days)/30 days
= (14000 + 24000 + 12000)/30 = 1666.67
How can I achieve this in SQlite? any help will be appreciated.
thanks
First, we have to compute the start and the end of each interval.
The simple query (SELECT Date AS "From", Amount FROM BalanceHistory WHERE Date GLOB '2013-11*') gets the start for each interval in the month.
(GLOB is case sensitive and thus allows to use a normal index; LIKE would require a special case-insensitive index.)
If there is no record for the first day of the month, the part after the UNION ALL adds the last record of the previous month and changes the day to the 1st.
The COALESCE computes the end of the interval.
The subquery gets the next date from the table, if there is one in the current month.
If there is no such record, it takes the first day of the next month:
SELECT date("From") AS "From",
COALESCE((SELECT date(MIN(Date))
FROM BalanceHistory
WHERE Date > "From"
AND Date GLOB '2013-11*'),
date('2013-11-01', '+1 month')
) AS "To",
"Amount"
FROM (SELECT Date AS "From",
Amount
FROM BalanceHistory
WHERE Date GLOB '2013-11*'
UNION ALL
SELECT *
FROM (SELECT date(Date, '+1 month', 'start of month'),
Amount
FROM BalanceHistory
WHERE Date < '2013-11'
AND NOT EXISTS (SELECT 1
FROM BalanceHistory
WHERE Date GLOB '2013-11-01*')
ORDER BY Date DESC
LIMIT 1)
);
From To Amount
---------- ---------- ------
2013-11-01 2013-11-15 1000
2013-11-15 2013-11-27 2000
2013-11-27 2013-12-01 3000
We can then wrap this in another query to compute the number of days, and add them up.
The strftime calculates the last day of the month, i.e., the number of days:
SELECT SUM((julianday("To") - julianday("From")) * Amount) /
strftime('%d', '2013-11-01', '+1 month', '-1 day') AS MonthAvg
FROM (SELECT date("From") AS "From",
COALESCE((SELECT date(MIN(Date))
FROM BalanceHistory
WHERE Date > "From"
AND Date GLOB '2013-11*'),
date('2013-11-01', '+1 month')
) AS "To",
"Amount"
FROM (SELECT Date AS "From",
Amount
FROM BalanceHistory
WHERE Date GLOB '2013-11*'
UNION ALL
SELECT *
FROM (SELECT date(Date, '+1 month', 'start of month'),
Amount
FROM BalanceHistory
WHERE Date < '2013-11'
AND NOT EXISTS (SELECT 1
FROM BalanceHistory
WHERE Date GLOB '2013-11-01*')
ORDER BY Date DESC
LIMIT 1)
)
)
And while we're at it, we can wrap this in yet another query to replace all those 2013-11 strings with the month as read from the table.
This allows as to compute this for every month:
SELECT Month,
(SELECT SUM((julianday("To") - julianday("From")) * Amount) /
strftime('%d', Month || '-01', '+1 month', '-1 day')
FROM (SELECT date("From") AS "From",
COALESCE((SELECT date(MIN(Date))
FROM BalanceHistory
WHERE Date > "From"
AND Date GLOB Month || '*'),
date(Month || '-01', '+1 month')
) AS "To",
"Amount"
FROM (SELECT Date AS "From",
Amount
FROM BalanceHistory
WHERE Date GLOB Month || '*'
UNION ALL
SELECT *
FROM (SELECT date(Date, '+1 month', 'start of month'),
Amount
FROM BalanceHistory
WHERE Date < Month
AND NOT EXISTS (SELECT 1
FROM BalanceHistory
WHERE Date GLOB Month || '-01*')
ORDER BY Date DESC
LIMIT 1)
)
)
) AS MonthAvg
FROM (SELECT DISTINCT strftime('%Y-%m', Date) AS Month
FROM BalanceHistory)
ORDER BY 1

Resources