Day level Calculation in oracle sql - sql

I have two year static data .I have column name as stats_date,P2P_volume .Initially i created following query for single day in oracle sql developer
select '1' as KPI_ID, 'P2P' as KPI_DESC,'22-MAR-17' as dates,
(sum(case when STATS_DATE between add_months('22-MAR-17',0)-13
and add_months('22-MAR-17',0)-7 then P2P_VOLUME else 0 end )) LAST_WEEK_Volume,
(sum(case when STATS_DATE between add_months('22-MAR-17',0)-6
and add_months('22-MAR-17',0) then P2P_VOLUME else 0 end )) THIS_WEEK_Volume from table
my problem is i want create dynamic query which will give me Last_week_volume,and this_week_volume Date wise for two years.rather than single day

In the absence of a complete set of sample data and requirements here are my assumptions:
There is one row per day per KPI
The definition of current week, previous week can be satisfied
by using the 'IW' date format mask
This solution uses a subquery to calculate the sum of the measures for each week. This feeds into the main query which uses an analytic lag() function to show the totals for the current week and the previous week.
with cte as (
select kpi
, to_char(static_date, 'YYYY') as yr
, to_char(static_date, 'IW') as wk
, sum(volume) as wk_volume
, sum(value) as wk_value
, sum(revenue) as wk_revenue
from t23
group by kpi,to_char(static_date, 'YYYY'), to_char(static_date, 'IW')
)
select kpi
, yr||'-'||wk as year_wk
, wk_volume as curr_wk_volume
, lag(wk_volume) over (order by yr, wk) as prev_wk_volume
, wk_value as curr_wk_value
, lag(wk_value) over (order by yr, wk) as prev_wk_value
, wk_revenue as curr_wk_revenue
, lag(wk_revenue) over (order by yr, wk) as prev_wk_revenue
from cte
order by 1, 2
/
There is a SQL Fiddle demo here.

Related

Calculate Revenue Recognition Per Month in Oracle SQL

I have a table with the order lines which show the Booking Amount and the booked date, but the revenue is recognised over 3 months (so 1/3 in the booked month and a further 1/3 in each of the next 2 months).
I need to create a query that would show the total revenue recognised in each month.
Is there an analytic function that could work this out? as at the moment I have cobbled together 3 joined queries that give the number but in 3 seperate columns, where I need it in one column:
select TRUNC(OM.BOOKING_DATE, 'MONTH') as Month
, SUM(OM.BOOKED_VALUE)/3 as Month_1
, M2.Month_2
, M3.Month_3
from ORDERS.OM,
(select TRUNC(ADD_MONTHS(OM.BOOKING_DATE,1), 'MONTH') as Month
, SUM(OM.BOOKED_VALUE)/3 as Month_2
from ORDERS.OM
GROUP By TRUNC(ADD_MONTHS(OM.BOOKING_DATE,1), 'MONTH')) M2,
(select TRUNC(ADD_MONTHS(OM.BOOKING_DATE,2), 'MONTH') as Month
, SUM(OM.BOOKED_VALUE)/3 as Month_3
from ORDERS.OM
GROUP By TRUNC(ADD_MONTHS(OM.BOOKING_DATE,2), 'MONTH')) M3
WHERE TRUNC(OM.BOOKING_DATE, 'MONTH') = M2.MONTH
AND TRUNC(OM.BOOKING_DATE, 'MONTH') = M3.MONTH
GROUP By TRUNC(OM.BOOKING_DATE, 'MONTH'), M2.Month_2, M3.Month_3
Order by 1 DESC
Triple every row and sum
select t.Month, SUM(t.Val) as Value
from ORDERS.OM
cross join lateral (select TRUNC(OM.BOOKING_DATE, 'MONTH') as Month, OM.BOOKED_VALUE/3.0 as Val from dual union all
select TRUNC(ADD_MONTHS(OM.BOOKING_DATE,1), 'MONTH'), OM.BOOKED_VALUE/3.0 from dual union all
select TRUNC(ADD_MONTHS(OM.BOOKING_DATE,2), 'MONTH'), OM.BOOKED_VALUE/3.0 from dual ) t
group by t.Month

SQL - lag variable creation using window function

I have daily city level data with some counts. I have to aggregate this data at monthly level(1st day of each month) and then create lag variables based on last 1 week from 1st day of month.
I have used following code to create lag variables for last 1 month using (after aggregating data at monthly level ( with 1st date of month)
sum(count) over (partition by City order by month_date rows between 1 preceding and 1 preceding) as last_1_month_count
Is there a way to aggregate data at monthly level and create lag variables based on last 7,14,21,28 days using window function?
you can use this L
select
CITY
, month(Date)
, year(date)
, sum(count)
from table1
where date < Datediff(days , 7 , getdate())
group by
City
, month(Date)
, year(date)
I think you're looking for something like this. The first cte summarizes city counts to the day, week, month, year. The second summarizes the counts to the week, month, year. To group sales by weeks starting from the 1st day it uses the DAY function along with YEAR and MONTH. Since DAY returns and integer, groups of distinct weeks can be created by dividing by 7, i.e. DAY(day_dt)/7.
One way to get the prior week sales would be to join the week sales summary cte to itself where the week is offset by -1. Since the prior week might possible have 0 sales it seems safer to LEFT JOIN than to use LAG imo
with
day_sales_cte(city, day_dt, yr, mo, wk, sum_count) as (
select city, day_dt, year(day_dt), month(day_cte), day(day_dt)/7, sum([count]) sum_counts
from city_level_data
group by city, day_dt, year(day_dt), month(day_cte), day(day_dt)/7)
wk_sales_cte(city, yr, mo, wk, sum_count) as (
select city, yr, mo, wk, sum(sum_counts) sum_counts
from sales_cte
group by city, yr, mo, wk)
select ws.*, ws2.sum_sales prior_wk_sales
from wk_sales_cte ws
left join wk_sales_cte ws2 on ws.city=ws2.city
and ws.yr=ws2.yr
and ws.mo=ws2.mo
and ws.wk=ws.wk-1;

PostgreSQL subquery - calculating average of lagged values

I am looking at Sales Rates by month, and was able to query the 1st table. I am quite new to PostgreSQL and am trying to figure out how I can query the second (I had to do the 2nd one in Excel for now)
I have the current Sales Rate and I would like to compare it to the Sales Rate 1 and 2 months ago, as an averaged rate.
I am not asking for an answer how exactly to solve it because this is not the point of getting better, but just for hints for functions to use that are specific to PostgreSQL. What I am trying to calculate is the 2 month average in the 2nd table based on the lagged values of the 2nd table. Thanks!
Here is the query for the 1st table:
with t1 as
(select date,
count(sales)::numeric/count(poss_sales) as SR_1M_before
from data
where date between '2019-07-01' and '2019-11-30'
group by 1),
t2 as
(select date,
count(sales)::numeric/count(poss_sales) as SR_2M_before
from data
where date between '2019-07-01' and '2019-10-31'
group by 1)
select t0.date,
count(t0.sales)::numeric/count(t0.poss_sales) as Sales_Rate
t1.SR_1M_before,
t2.SR_2M_before
from data as t0
left join t1 on t0.date=t1.date
left join t2 on t0.date=t1.date
where date between '2019-07-01' and '2019-12-31'
group by 1,3,4
order by 1;
As commented by a_horse_with_no_name, you can use window functions to take the average of the two previous monthes with a range clause:
select
date,
count(sales)::numeric/count(poss_sales) as Sales_Rate,
avg(count(sales)::numeric/count(poss_sales)) over(
order by date
rows between '2 month' preceding and '1 month' preceding
) Sales_Rate,
count(sales)::numeric/count(poss_sales) as Sales_Rate
- avg(count(sales)::numeric/count(poss_sales)) over(
order by date
rows between '2 month' preceding and '1 month' preceding
) PercentDeviation
from data
where date between '2019-07-01' and '2019-12-31'
group by date
order by date;
Your data is a bit confusing -- it would be less confusing if you had decimal places (that is, 58% being the average of 57% and 58% is not obvious).
Because you want to have NULL values on the first two rows, I'm going to calculate the values using sum() and count():
with q as (
<whatever generates the data you have shown>
)
select q.*,
(sum(sales_rate) over (order by date
rows between 2 preceding and 1 preceding
) /
nullif(count(*) over (order by date
rows between 2 preceding and 1 preceding
)
) as two_month_average
from q;
You could also express this using case and avg():
select q.*,
(case when row_number() over (order by date) > 2)
then avg(sales_rate) over (order by date
rows between 2 preceding and 1 preceding
)
end) as two_month_average
from q;

SQL manipulation of table (aggregate and grouping)

I would like to make a daily query (using bigquery) to compare the sums for different metrics between yesterday and today. sample dataset look like this:
assuming today is 23 Dec 2019, the query will aggregate different metrics (revenue, cost, profit) for different customer for 23 Dec (today) and 22 Dec (yesterday), if sum(yesterday)/sum(today) is not within the threshold of 0.5-1.5, then it will be labelled as anomalous
the query will be made daily and new result will simply be appended. ideally the final table would look like this:
My main concern is that I am able to do this for one metric only (i.e revenue), but not sure how to apply to all metrics (and also make the query more efficient). this is the code i have written
SELECT cust_id,
SUM(CASE WHEN date = DATE_ADD(CURRENT_DATE(), INTERVAL -1 DAY)
THEN revenue
END) AS sum(yesterday),
SUM(CASE WHEN date = DATE_ADD(CURRENT_DATE(), INTERVAL 0 DAY)
THEN revenue
END) AS sum(today),
SUM(CASE WHEN date = DATE_ADD(CURRENT_DATE(), INTERVAL -1 DAY)
THEN revenue
END) / SUM(CASE WHEN date = DATE_ADD(CURRENT_DATE(), INTERVAL 0 DAY)
THEN revenue
END) as ratio,
FROM `dataset`
GROUP BY cust_id
and the code gives me:
Apologies in advance for the lack of clarity in the question, as I am new to this and not sure how to phrase this question more accurately
My suggestion would be to put the source data in an Excel pivot table. (move the Values group to the rows to get the desired view.).
if you want to stick to SQL however, you need to unpivot the rows first, by putting each measure in a separate row and then group the intermediate results, like this:
WITH unpivoted AS
(
SELECT
date
, 'revenue' AS metrics
, SUM( revenue ) AS amount
, cust_id
FROM
`dataset`
GROUP
BY
date
, cust_id
UNION ALL
SELECT
date
, 'cost' AS metrics
, SUM( cost ) AS amount
, cust_id
FROM
`dataset`
GROUP
BY
date
, cust_id
-- add more desired metrics
)
SELECT
date as date_generated
, cust_id
, metrics
, SUM( CASE WHEN date = DATE_ADD( CURRENT_DATE() , INTERVAL 0 DAY ) THEN amount END ) AS today
, SUM( CASE WHEN date = DATE_ADD( CURRENT_DATE() , INTERVAL -1 DAY ) THEN amount END ) AS yesterday
...
FROM
unpivoted
WHERE
date >= DATE_ADD(CURRENT_DATE(), INTERVAL -1 DAY )
AND date <= DATE_ADD(CURRENT_DATE(), INTERVAL 0 DAY )
GROUP
BY
date, cust_id, metrics
You can summarize the data and then use lag() or a join to bring in the previous days data:
with t as (
select cust_id, date,
sum(revenue) as revenue,
sum(cost) as cost,
sum(profit) as profit
from dataset
where date >= date_add(current_date, interval -1 day)
group by cust_id, date
)
select t.cust_id,
today, yesterday
from t today left join
t yesterday
on yesterday.cust_id = today.cust_id and
yesterday.date = date_add(current_date, interval -1 day)
where today.date = current_date;
You can unpivot the columns first and then group the results. After that, you might need to use LAG() to show data from one day and the previous one in the same row.
WITH unpivoted AS
(
SELECT
date,
'revenue' AS metrics,
SUM( revenue ) AS amount,
cust_id
FROM
`dataset`
GROUP BY
date, metrics, cust_id
UNION ALL
SELECT
date,
'cost' AS metrics,
SUM( cost ) AS amount,
cust_id
FROM
`dataset`
GROUP BY
date, metrics, cust_id
UNION ALL
SELECT
date,
'profit' AS metrics,
SUM( profit ) AS amount,
cust_id
FROM
`dataset`
GROUP BY
date, metrics, cust_id
)
SELECT
date as date_generated,
metrics,
cust_id,
LAG(SUM( amount )) OVER (PARTITION BY cust_id, metrics ORDER BY date) yesterday,
SUM( amount ) AS today,
LAG(SUM( amount )) OVER (PARTITION BY cust_id, metrics ORDER BY date) / SUM(amount) as ratio,
CASE WHEN LAG(SUM( amount )) OVER (PARTITION BY cust_id, metrics ORDER BY date) / SUM(amount)<0.5 then 'TRUE'
WHEN LAG(SUM( amount )) OVER (PARTITION BY cust_id, metrics ORDER BY date) / SUM(amount)>1.5 then 'TRUE'
WHEN LAG(SUM( amount )) OVER (PARTITION BY cust_id, metrics ORDER BY date) / SUM(amount) is NULL then 'TRUE'
ELSE 'FALSE'
END as anomalous
FROM
unpivoted
WHERE date >= DATE_ADD(CURRENT_DATE(), INTERVAL -1 DAY ) AND date <= DATE_ADD(CURRENT_DATE(), INTERVAL 0 DAY )
GROUP BY
date_generated, cust_id, metrics
ORDER BY
date_generated, metrics, cust_id
Note that my solution is only limited to current day and previous day (today and yesterday) when using the WHERE clause, so this could be used to aggregate metrics from more than two days.

Weekly/monthly/quarterly grouping in query

Lets say I have table with following columns
1. Client - string.
2. Profit - integer.
3. Deal_Date - date.
I need query that will retrieve sum of profit breakdown by week/month/quater etc.
Expected output for weeks
1 row, sum (profit) of all deals that registered from (03.19.2012 - 03.12.2012).
2 row, sum (profit) of all deals that registered from (03.12.2012 - 03.05.2012).
...
n row, sum (profit) of all deals that registered from (05.17.2011 - 05.10.2011).
NOTE (dates set just for example)
The same for month, years, etc.
Could someone help me with such query?
Btw performance is very important.
This query uses simple date formats to extract the various elements you want to track and analytics to get the sums.
select client
, yr
, qtr
, wk
, sum ( profit ) over ( partition by client, yr) as yr_profit
, sum ( profit ) over ( partition by client, yr, qtr) as qtr_profit
, sum ( profit ) over ( partition by client, yr, wk) as wk_profit
from (
select client
, profit
, to_char(deal_date, 'yyyy') as yr
, to_char(deal_date, 'q') as qt
, to_char(deal_date, 'ww') as wk
from your_table )
/
This will produce one row for each row in the current table. So you probebly will want to wrap it in a further outer query which only returns only distinct rows.
A variant would be to use rollup instead. I'm not sure how well that works when the grouping criteria aren't perfectly hierarchical (weeks don't fit neatly into quarters).
select client
, yr
, qtr
, wk
, sum ( profit ) as profit
from (
select client
, profit
, to_char(deal_date, 'yyyy') as yr
, to_char(deal_date, 'q') as qt
, to_char(deal_date, 'ww') as wk
from your_table )
group by rollup ( client, yr, qtr, wk )
/
Just make an SP and loop the code for each week or month or year as you wish.