ORACLE Group query by week, month and year - sql

I have a bunch of queries that take data with a time stamp and spit out SUMS based the last few weeks, months, and year to date. It looks like this
Week1 Sum for most recent week
Week2 Sum for second most recent week
WeekN Sum for N most recent week
Jan-Dec Sum for January-December
YTD Sum for everything this year
This is how the query currently does this
SELECT TIME_PERIOD, INDEX, SUM(ITEM)
FROM (SELECT
INDEX ,
(CASE
WHEN ACTIVITY_DAY>=(TO_DATE( :end_day,
'yyyy-mm-dd' )-6)
AND ACTIVITY_DAY<=(TO_DATE( :end_day,
'yyyy-mm-dd' )-0) THEN 'WEEK1'
WHEN ACTIVITY_DAY>=(TO_DATE( :end_day,
'yyyy-mm-dd' )-13)
AND ACTIVITY_DAY<=(TO_DATE( :end_day,
'yyyy-mm-dd' )-7) THEN 'WEEK2'
ELSE NULL
END) AS TIME_PERIOD,
MAX(ITEMS) AS ITEM
FROM
SOURCE
GROUP BY
INDEX ,
DAY
UNION
ALL SELECT
INDEX ,
(CASE
WHEN ACTIVITY_DAY>=TO_DATE( :year||'-01-01',
'yyyy-mm-dd' )
AND ACTIVITY_DAY<=TO_DATE( :year||'-01-31',
'yyyy-mm-dd' ) THEN 'Jan'
WHEN ACTIVITY_DAY>=TO_DATE( :year||'-02-01',
'yyyy-mm-dd' )
AND ACTIVITY_DAY<TO_DATE( :year||'-03-01',
'yyyy-mm-dd' ) THEN 'Feb'
ELSE NULL
END) AS TIME_PERIOD ,
MAX(ITEMS) AS ITEM
FROM
SOURCE
GROUP BY
INDEX ,
DAY
UNION
ALL SELECT
INDEX ,
(CASE
WHEN ACTIVITY_DAY>=TO_DATE( :year||'-01-01',
'yyyy-mm-dd' )
AND ACTIVITY_DAY<=TO_DATE( :end_day,
'yyyy-mm-dd' ) THEN 'YTD'
ELSE NULL
END) AS TIME_PERIOD,
MAX(ITEMS) AS ITEM
FROM
SOURCE
GROUP BY
INDEX ,
DAY)
GROUP BY INDEX, TIME_PERIOD
Is there a better way in Oracle?

I think you are looking for something like this:
with data as
(
select sysdate - floor(dbms_random.value(1,400)) dt, floor(dbms_random.value(1,100)) val
from dual
connect by level <= 100
)
select
time_period,
sum(val) period_sum
from
(
select -- weeks
'Week'||(to_char(sysdate, 'WW') - to_char(dt, 'WW') + 1) time_period,
val,
(to_char(sysdate, 'WW') - to_char(dt, 'WW') + 1) ord
from data
where dt >= trunc(sysdate,'YY')
union all
select -- months
to_char(dt, 'Mon') time_period,
val,
100+to_char(dt,'MM') ord
from data
where dt >= trunc(sysdate,'YY')
union all
select -- months
'YTD' time_period,
val,
200
from data
where dt >= trunc(sysdate,'YY')
)
group by
time_period, ord
order by
ord;
Note that you won't need the WITH block, I was just using it to create some dummy data. The Ord column might be unnecessary for you, I was just using it to order the data in a logical fashion.

Related

Difference between two dates in business days? Google Bigquery

How do I calculate the difference between two dates in business days in Google Bigquery?
I want to replicate this example below:
I have tried these examples but they do not give the expected results:
DATE_DIFF but only counting business days
I also used this logic,ionand it did not work:
CREATE TEMP FUNCTION BusinessDateDiff(start_date DATE, end_date DATE) AS (
(SELECT COUNTIF(MOD(EXTRACT(DAYOFWEEK FROM date), 7) > 1)
FROM UNNEST(GENERATE_DATE_ARRAY(
start_date, DATE_SUB(end_date, INTERVAL 1 DAY))) AS date)
);
Consider below
create temp function BusinessDateDiff(delivery DATE, eta DATE) AS ((
select if(delivery > eta, 1, -1) * count(*)
from unnest(generate_date_array(
least(delivery, eta), greatest(delivery, eta) - 1
)) day
where not extract(dayofweek from day) in (1, 7)
));
select *,
BusinessDateDiff(DELIVERY_DATE, ORIGINAL_ETA_DATE) as BUSINESS_DAYS
from your_table
if applied to sample data as in your question - output is
getting desired result as follows:
CREATE TEMP FUNCTION BusinessDateDiff(start_date DATE, end_date DATE) AS (
(SELECT -1*COUNTIF(MOD(EXTRACT(DAYOFWEEK FROM date), 7) > 1)
FROM UNNEST(GENERATE_DATE_ARRAY( start_date , DATE_SUB(end_date,INTERVAL 1 DAY))) AS date));
CREATE TEMP FUNCTION BusinessDateDiff1( end_date DATE, start_date DATE) AS (
(SELECT COUNTIF(MOD(EXTRACT(DAYOFWEEK FROM date), 7) > 1)
FROM UNNEST(GENERATE_DATE_ARRAY( end_date , DATE_SUB(start_date,INTERVAL 1 DAY))) AS date));
WITH OrdersTable AS (
SELECT DATE '2022-06-28' AS DELIVERY_DATE,
DATE '2022-08-17' AS ORIGINAL_ETA_DATE
UNION ALL
SELECT DATE '2022-07-01' AS DELIVERY_DATE,
DATE '2022-07-14' AS ORIGINAL_ETA_DATE
UNION ALL
SELECT DATE '2022-06-30' AS DELIVERY_DATE,
DATE '2022-07-08' AS ORIGINAL_ETA_DATE
UNION ALL
SELECT DATE '2022-06-30' AS DELIVERY_DATE,
DATE '2022-07-08' AS ORIGINAL_ETA_DATE
UNION ALL
SELECT DATE '2022-06-29' AS DELIVERY_DATE,
DATE '2022-07-06' AS ORIGINAL_ETA_DATE
UNION ALL
SELECT DATE '2022-06-27' AS DELIVERY_DATE,
DATE '2022-07-01' AS ORIGINAL_ETA_DATE
UNION ALL
SELECT DATE '2022-06-30' AS DELIVERY_DATE,
DATE '2022-07-05' AS ORIGINAL_ETA_DATE
UNION ALL
SELECT DATE '2022-06-30' AS DELIVERY_DATE,
DATE '2022-06-28' AS ORIGINAL_ETA_DATE
)
SELECT
DELIVERY_DATE,
ORIGINAL_ETA_DATE,
case when DELIVERY_DATE < ORIGINAL_ETA_DATE then
BusinessDateDiff(DELIVERY_DATE, ORIGINAL_ETA_DATE)
when DELIVERY_DATE > ORIGINAL_ETA_DATE then
BusinessDateDiff1(ORIGINAL_ETA_DATE, DELIVERY_DATE)
else 0 end AS BUSINESS_DAYS
FROM OrdersTable
[![Desired Result][1]][1]
[1]: https://i.stack.imgur.com/efmw3.png

Creating a query with a column of dates that I specify without the ability to create a temporary table

I have read only access in my database and I have a table that looks like this;
I want an output that looks like the following
Is this possible? I think I would need to have a table of dates to pass through but I'm not sure how to go about that. Any help would be appreciated.
Use a sub-query factoring clause (WITH) to generate the dates to join:
WITH dates ( dt ) AS (
SELECT DATE '2016-07-01' FROM DUAL UNION ALL
SELECT DATE '2017-07-01' FROM DUAL UNION ALL
SELECT DATE '2019-08-01' FROM DUAL
)
SELECT EXTRACT(MONTH FROM d.dt) AS Month,
EXTRACT(YEAR FROM d.dt) AS Year,
NVL2(t.SomeValue, 'Y', 'N') AS "Change?"
FROM dates d
LEFT OUTER JOIN table_name t
ON ( TRUNC(t.effective_date, 'MM') = d.dt )
My desired output would include every month and year from 1/2012 on.
Then use a recursive sub-query factoring clause:
WITH dates ( dt ) AS (
SELECT DATE '2012-01-01' FROM DUAL
UNION ALL
SELECT ADD_MONTHS( dt, 1 )
FROM dates
WHERE ADD_MONTHS( dt, 1 ) <= SYSDATE
)
SELECT EXTRACT(MONTH FROM d.dt) AS Month,
EXTRACT(YEAR FROM d.dt) AS Year,
NVL2(t.SomeValue, 'Y', 'N') AS "Change?"
FROM dates d
LEFT OUTER JOIN table_name t
ON ( TRUNC(t.effective_date, 'MM') = d.dt )
You can use recursion to break the records in to who years...
WITH
fragmented (
window_start,
window_close,
some_value,
interval_start,
interval_close
)
AS
(
SELECT
window_start,
window_close,
some_value,
interval_start,
CASE
WHEN add_months(interval_start, 12) < window_close
THEN add_months(interval_start, 12)
ELSE window_close
END
AS interval_close
FROM
(
SELECT
effective_date AS window_start,
LEAD(effective_date) OVER (ORDER BY effective_date) AS window_close,
some_value,
effective_date AS interval_start
FROM
example
)
lookahead
UNION ALL
SELECT
window_start,
window_close,
some_value,
add_months(interval_start, 12),
CASE
WHEN add_months(interval_start, 24) < window_close
THEN add_months(interval_start, 24)
ELSE window_close
END
FROM
fragmented
WHERE
interval_close < window_close
)
SELECT
*
FROM
fragmented
ORDER BY
window_start,
interval_start
;
Demo : https://dbfiddle.uk/?rdbms=oracle_18&fiddle=ca1fef00069c178c28e09d209db35395

Is it possible to map multiple select statement into columns?

I have this table:
code(integer) |number_of_data(integer)| date (Date)
I need to:
Group by day, this for tho month of june only
Select sum of number_of_data for the day for each code != 0
Select n_data for code = 0
For the first 2 points I came up with:
select sum(number_of_data) nData, TO_CHAR(date, 'DD') dayOfMonth from T1
where to_char( date, 'mm') = 6
and code <> 0
group by TO_CHAR(date, 'DD') order by TO_CHAR(date, 'DD');
it gives me this table result:
nData | dayOfMonth
which is fine, anyway I'm missing requirement 3, whose query would be the same but with the opposite condition (code=0).
Is there a way to add it to the above query so to get this result:
nData | nDataZero | dayOfMonth
?
Whit some regards to the syntax in MS Sql. This is a way i would solve this in a oracle sql-like way:)
SELECT sum(nData) nData , sum(nDataZero) nDataZero, T1
from (
select sum(number_of_data) nData, 0 nDataZero , TO_CHAR(date, 'DD') dayOfMonth
from T1
where to_char( date, 'mm') = 6 and code <> 0
group by TO_CHAR(date, 'DD')
order by TO_CHAR(date, 'DD')
UNION
select 0 nData, sum(number_of_data) nDataZero , TO_CHAR(date, 'DD') dayOfMonth
from T1
where to_char( date, 'mm') = 6 and code == 0
group by TO_CHAR(date, 'DD')
order by TO_CHAR(date, 'DD'))
group by T1;
Rgds
Assuming that there will only be one entry with CODE = 0 for each day, then you can do:
SELECT SUM( CASE CODE WHEN 0 THEN NULL ELSE number_of_data END ) AS nData,
MAX( CASE CODE WHEN 0 THEN number_of_data END ) AS nDataZero,
EXTRACT( DAY FROM "Date" ) AS dayOfMonth
FROM T1
WHERE EXTRACT( MONTH FROM "Date" ) = 6
GROUP BY EXTRACT( DAY FROM "Date" )
ORDER BY EXTRACT( DAY FROM "Date" );
If there will be more than one entry then you will need to specify how it is to be handled (i.e. change MAX to SUM if you want the total of the CODE = 0 values).
You can use CASE to separate data into 2 slots, code=0 and code<>0:
select sum(number_of_data) nData, TO_CHAR(date, 'DD') dayOfMonth, CASE WHEN code = 0 THEN 0 ELSE 1 AS x
from T1
where to_char( date, 'mm') = 6
group by TO_CHAR(date, 'DD'), x
order by TO_CHAR(date, 'DD');

How to get the week start and end dates?

I have variable called WeekBeginDate and I want only to pull data for that week. For example, if the beginning of the week date is 07/21/2014 which is Monday in this case, then I want only to pull the data from 07/21/2014 to 7/27/2014.
The variable will always contain the date for the beginning of the week only but I don’t have the date for the end of the week.
The week begins on Monday and ends on Sunday. I can’t figure out how to calculate or sum the number of hours if I only have the date for the beginning of week.
SELECT DT, sum (TOT_HOURS)as TOT_HOURS FROM MYTABLE where DT >= #WeekBeginDate and <=#WeekEndDate group by DT
Note, that I only have the variable for the WeekBeginDate.
just modify your table columns in this CTE it may works :
;WITH workhours AS
(
SELECT DATEADD(DAY
, -(DATEPART(dw, DT) -1)
, DT) AS week_start
, DATEADD(DAY
, 7 - (DATEPART(dw, DT))
, DT) AS week_end
FROM MYTABLE
)
SELECT week_start
, week_end
, SUM(TOT_HOURS) total_hrs_per_week
FROM workhours
GROUP BY week_start
, week_end
You may need to add 6 days to the beginning of the week
and group by something else if you need total weekly hours, i'm calling it "id".
not by dt (or don't group at all if it is a total for the whole table):
SELECT id, DT, sum (TOT_HOURS)as TOT_HOURS FROM MYTABLE
where DT BETWEEN #WeekBeginDate and DATEADD(d,6,#WeekBeginDate)
GROUP BY id
This should be of some use to you. I am casting to date so the 24 hrs of day is considered.
DECLARE #WeekBeginDate DATETIME
SET #WeekBeginDate = '2014-07-28 12:08:31.633';
WITH MYTABLE (DT,TOT_HOURS)
AS (
SELECT '2014-06-27 00:08:31.633',5 UNION ALL
SELECT '2014-07-27 00:08:31.633',5 UNION ALL
SELECT '2014-07-28 00:08:31.633',1 UNION ALL
SELECT '2014-07-29 00:08:31.633',1 UNION ALL
SELECT '2014-07-30 00:08:31.633',1 UNION ALL
SELECT '2014-07-31 00:08:31.633',1 UNION ALL
SELECT '2014-08-01 00:08:31.633',1 UNION ALL
SELECT '2014-08-02 00:08:31.633',1 UNION ALL
SELECT '2014-08-03 00:08:31.633',1
)
SELECT CAST(#WeekBeginDate AS DATE) AS StartDate,
DATEADD(d, 6, CAST(#WeekBeginDate AS DATE)) AS EndDate,
SUM (TOT_HOURS)AS TOT_HOURS
FROM MYTABLE
WHERE CAST(DT AS DATE) BETWEEN CAST(#WeekBeginDate AS DATE) AND DATEADD(d, 6, CAST(#WeekBeginDate AS DATE))
Just add 6 (or 7) days...
SELECT DT, sum (TOT_HOURS)as TOT_HOURS FROM MYTABLE
where DT BETWEEN #WeekBeginDate and #WeekBeginDate + 6 group by DT
select #weekBeginDate = DATEADD(wk, DATEDIFF(wk,0,GETDATE()), 0)
select #WeekEndDate = DATEADD(dd, 6, DATEADD(wk, DATEDIFF(wk,0,GETDATE()), 0))
SELECT DT, sum (TOT_HOURS)as TOT_HOURS FROM MYTABLE where DT >= #WeekBeginDate and <=#WeekEndDate group by DT
Here is where having a calendar table would be very useful,
especially if your logic needs to change if Monday is a holiday.
Basically create a table with pre-calculated values for weeks and just join to it.
http://www.made2mentor.com/2011/04/calendar-tables-why-you-need-one

Oracle order by year, month

I have a query which I want to order by year and then month. I have tryed order by to_date( depdate, 'mm' ) and TO_CHAR(depdate, 'YYYY/MM'). Here is an sqlfiddle to the table i am querying and the query itself sqlfiddle
You want to sort by the date value, not by the character string representation. That means that you also want to group by the date value. trunc(<<date column>>, 'mm') truncates a date to midnight on the first of the month. So something like this
SELECT to_char(trunc(DEPDATE,'MM'), 'Mon-YYYY') AS MONTH,
SUM(AMOUNTROOM) AS ROOMTOTAL,
SUM(AMOUNTEXTRAS) AS EXTRATOTAL,
SUM(AMOUNTEXTRAS + AMOUNTROOM) AS OATOTAL
FROM checkins
WHERE checkinstatus = 'D' AND depdate > TO_DATE('2013-12-01', 'yyyy/mm/dd')
AND depdate <= TO_DATE('2014-04-10', 'yyyy/mm/dd')
GROUP BY trunc(depdate,'mm')
ORDER BY trunc(depdate,'mm');
should be what you're looking for. See the updated fiddle
Check out this query. If it is a date field, just plain order by would work for you. You need not use TO_CHAR to convert to string and then sort:
WITH TAB AS
(
SELECT SYSDATE DATEVAL FROM DUAL
UNION
SELECT SYSDATE + 100 DATEVAL FROM DUAL
UNION
SELECT SYSDATE -500 DATEVAL FROM DUAL
UNION
SELECT SYSDATE + 30 DATEVAL FROM DUAL
UNION
SELECT SYSDATE -30 DATEVAL FROM DUAL
) SELECT * FROM TAB
ORDER BY DATEVAL DESC