Rolling up monthly data to quarterly - sql

I have monthly data for each company and it's associated MRR (Monthly Revenue) as of the month end date. I am trying to build a quarterly report which shows the progression of the company within the quarter. Following is the reproducible sample dataset with quarter end dates:
WITH
quarter_end_date AS
(
SELECT '2021-04-30' q_end_date
UNION ALL
SELECT '2021-07-31' q_end_date
UNION ALL
SELECT '2021-10-31' q_end_date
UNION ALL
SELECT '2022-01-31' q_end_date
),
data AS
(
SELECT '2020-05-31' as_of_date, 'A' company_id, '100' mrr,'new' category
UNION ALL
SELECT '2020-06-30' as_of_date, 'A' company_id, '100' mrr,'no change' category
UNION ALL
SELECT '2020-07-31' as_of_date, 'A' company_id, '100' mrr,'no change' category
UNION ALL
SELECT '2020-08-31' as_of_date, 'A' company_id, '100' mrr,'no change' category
UNION ALL
SELECT '2020-09-30' as_of_date, 'A' company_id, '0' mrr,'churn' category
UNION ALL
SELECT '2020-10-31' as_of_date, 'A' company_id, '100' mrr,'new' category
UNION ALL
SELECT '2020-11-30' as_of_date, 'A' company_id, '100' mrr,'no change' category
UNION ALL
SELECT '2020-12-31' as_of_date, 'A' company_id, '0' mrr,'churn' category
UNION ALL
SELECT '2021-01-31' as_of_date, 'A' company_id, '0' mrr,'new' category
UNION ALL
SELECT '2021-02-28' as_of_date, 'A' company_id, '100' mrr,'no change' category
UNION ALL
SELECT '2021-03-31' as_of_date, 'A' company_id, '100' mrr,'no change' category
UNION ALL
SELECT '2021-04-30' as_of_date, 'A' company_id, '120' mrr,'expansion' category
UNION ALL
SELECT '2021-05-31' as_of_date, 'A' company_id, '90' mrr,'contraction' category
UNION ALL
SELECT '2021-06-30' as_of_date, 'A' company_id, '70' mrr,'contraction' category
UNION ALL
SELECT '2021-07-31' as_of_date, 'A' company_id, '100' mrr,'expansion' category
UNION ALL
SELECT '2021-08-31' as_of_date, 'A' company_id, '100' mrr,'no change' category
UNION ALL
SELECT '2021-09-30' as_of_date, 'A' company_id, '120' mrr,'expansion' category
UNION ALL
SELECT '2021-10-31' as_of_date, 'A' company_id, '90' mrr,'contraction' category
UNION ALL
SELECT '2021-11-30' as_of_date, 'A' company_id, '0' mrr, 'churn' category
),
The MRR dataset (above CTE data) doesn’t store company info once it churns (stops paying subscription or cancels the service). The approach I'm using is capable of capturing any churn which happens in the start of the quarter or the end of quarter but it doesn’t if churn happens within the quarter. My SQL:
step_1
AS(
SELECT *,
LAG(mrr,1) OVER (PARTITION BY company_id ORDER BY as_of_date) prev_m1_mrr,
LAG(mrr,2) OVER (PARTITION BY company_id ORDER BY as_of_date) prev_m2_mrr,
LAG(mrr,3) OVER (PARTITION BY company_id ORDER BY as_of_date) quarter_start_mrr,
FROM data
)
SELECT *
FROM step_1
CROSS JOIN quarter_end_date q
WHERE q.q_end_date = step_1.as_of_date
ORDER BY q.q_end_date DESC
Output:
q_end_date
Company_id
mrr
category
prev_m1_mrr
prev_m2_mrr
quarter_start_mrr
2021-10-31
A
90
contraction
120
100
100
2021-07-31
A
100
expansion
70
90
120
2021-04-30
A
120
expansion
100
100
null
Expected Output:
q_end_date
Company_id
mrr
category
prev_m1_mrr
prev_m2_mrr
quarter_start_mrr
2022-01-31
A
null
churn
0
0
90
2021-10-31
A
90
contraction
120
100
100
2021-07-31
A
100
expansion
70
90
120
2021-04-30
A
120
expansion
100
100
null

I am assuming that data of all companies are in the same table, otherwise the query will be straightforward. I added a few values with a second company ‘B’. The query works, but it will give ‘Churn’ as the category whenever data is not present in the entire quarter for that company.That also includes the period when the company did not even start using the service. That can be filtered out later with simple queries based on start date if needed.
Second thing is I added another CTE for the list of companies.
WITH
quarter_end_date AS
(
SELECT '2021-04-30' q_end_date
UNION ALL
SELECT '2021-07-31' q_end_date
UNION ALL
SELECT '2021-10-31' q_end_date
UNION ALL
SELECT '2022-01-31' q_end_date
),
companies as
(SELECT 'A'as company_id
union all
SELECT 'B'as company_id
),
data AS
(
SELECT '2021-02-28' as_of_date, 'A' company_id, '100' mrr,'new' category
UNION ALL
SELECT '2021-03-31' as_of_date, 'A' company_id, '100' mrr,'no change' category
UNION ALL
SELECT '2021-04-30' as_of_date, 'A' company_id, '120' mrr,'expansion' category
UNION ALL
SELECT '2021-05-31' as_of_date, 'A' company_id, '90' mrr,'contraction' category
UNION ALL
SELECT '2021-06-30' as_of_date, 'A' company_id, '70' mrr,'contraction' category
UNION ALL
SELECT '2021-07-31' as_of_date, 'A' company_id, '100' mrr,'expansion' category
UNION ALL
SELECT '2021-08-31' as_of_date, 'A' company_id, '100' mrr,'no change' category
UNION ALL
SELECT '2021-09-30' as_of_date, 'A' company_id, '120' mrr,'expansion' category
UNION ALL
SELECT '2021-10-31' as_of_date, 'A' company_id, '90' mrr,'contraction' category
UNION ALL
SELECT '2021-11-30' as_of_date, 'A' company_id, '0' mrr, 'churn' category
union all
SELECT '2021-10-31' as_of_date, 'B' company_id, '100' mrr,'no change' category
UNION ALL
SELECT '2021-11-30' as_of_date, 'B' company_id, '120' mrr,'expansion' category
UNION ALL
SELECT '2021-12-31' as_of_date, 'B' company_id, '90' mrr,'contraction' category
UNION ALL
SELECT '2022-01-31' as_of_date, 'B' company_id, '100' mrr, 'expansion' category
),
step_1
AS(
SELECT *,
LAG(mrr,1) OVER (PARTITION BY company_id ORDER BY as_of_date) prev_m1_mrr,
LAG(mrr,2) OVER (PARTITION BY company_id ORDER BY as_of_date) prev_m2_mrr,
LAG(mrr,3) OVER (PARTITION BY company_id ORDER BY as_of_date) quarter_start_mrr,
FROM data
)
SELECT q_end_date,c.company_id,
mrr,IFNULL(category,'churn') as category,IFNULL(prev_m1_mrr,'0') as prev_m1_mrr, IFNULL(prev_m2_mrr,'0') as prev_m2_mrr,IFNULL(quarter_start_mrr,lead(mrr) over (partition by c.company_id order by q_end_date desc)) as quarter_start_mrr
FROM companies c cross join quarter_end_date q left join step_1 s on q.q_end_date = s.as_of_date and c.company_id=s.company_id
ORDER BY company_id,q.q_end_date DESC
Output:

Related

BigQuery: How to calculate the running count of distinct visitors for each last 2 days

I want to calculate unique user count in last 2 days for each Date.
First Query:- I tried with CASE statement give me user count for that day which is not expected result, even I tried with window function.
I know one alternate solution through self join (already mention as second query) which give me correct answer what I' expecting, but I want to do it in a single query.
Reason to do in a single query want to reduce processed data size, if I make self join it will read complete table twice, and the original table size is multi TB.
SELECT
(CASE WHEN dt BETWEEN DATE_SUB(dt, INTERVAL 1 DAY) AND dt THEN
CONCAT(CAST(DATE_SUB(dt, INTERVAL 1 DAY) AS STRING), '::', CAST(dt AS STRING)) END) AS Date_range,
COUNT(DISTINCT (CASE WHEN dt BETWEEN DATE_SUB(dt, INTERVAL 1 DAY) AND dt THEN Visitor_Name END)) AS Visitor_Count
FROM
(SELECT DATE('2018-01-01') AS dt, 'A' AS Visitor_Name
UNION ALL
SELECT '2018-01-01' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-01' AS dt, 'C' AS Visitor_Name
UNION ALL
SELECT '2018-01-01' AS dt, 'D' AS Visitor_Name
UNION ALL
SELECT '2018-01-02' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-02' AS dt, 'C' AS Visitor_Name
UNION ALL
SELECT '2018-01-02' AS dt, 'E' AS Visitor_Name
UNION ALL
SELECT '2018-01-03' AS dt, 'A' AS Visitor_Name
UNION ALL
SELECT '2018-01-03' AS dt, 'P' AS Visitor_Name
UNION ALL
SELECT '2018-01-04' AS dt, 'A' AS Visitor_Name
UNION ALL
SELECT '2018-01-04' AS dt, 'C' AS Visitor_Name
UNION ALL
SELECT '2018-01-05' AS dt, 'D' AS Visitor_Name
UNION ALL
SELECT '2018-01-05' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-05' AS dt, 'B' AS Visitor_Name
SELECT '2018-01-06' AS dt, 'P' AS Visitor_Name)
GROUP BY Date_range
ORDER BY Date_range;
Solution:
SELECT
(CASE WHEN a.dt BETWEEN DATE_SUB(b.dt, INTERVAL 1 DAY) AND b.dt THEN
CONCAT(CAST(DATE_SUB(b.dt, INTERVAL 1 DAY) AS STRING), '::', CAST(b.dt AS STRING)) END) AS Date_range,
COUNT(DISTINCT (CASE WHEN a.dt BETWEEN DATE_SUB(b.dt, INTERVAL 1 DAY) AND b.dt THEN a.Visitor_Name END)) AS Visitor_Count
FROM
(SELECT DATE('2018-01-01') AS dt, 'A' AS Visitor_Name
UNION ALL
SELECT '2018-01-01' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-01' AS dt, 'C' AS Visitor_Name
UNION ALL
SELECT '2018-01-01' AS dt, 'D' AS Visitor_Name
UNION ALL
SELECT '2018-01-02' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-02' AS dt, 'C' AS Visitor_Name
UNION ALL
SELECT '2018-01-02' AS dt, 'E' AS Visitor_Name
UNION ALL
SELECT '2018-01-03' AS dt, 'A' AS Visitor_Name
UNION ALL
SELECT '2018-01-03' AS dt, 'P' AS Visitor_Name
UNION ALL
SELECT '2018-01-04' AS dt, 'A' AS Visitor_Name
UNION ALL
SELECT '2018-01-04' AS dt, 'C' AS Visitor_Name
UNION ALL
SELECT '2018-01-05' AS dt, 'D' AS Visitor_Name
UNION ALL
SELECT '2018-01-05' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-05' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-06' AS dt, 'P' AS Visitor_Name) AS a
INNER JOIN
(SELECT DATE('2018-01-01') AS dt, 'A' AS Visitor_Name
UNION ALL
SELECT '2018-01-01' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-01' AS dt, 'C' AS Visitor_Name
UNION ALL
SELECT '2018-01-01' AS dt, 'D' AS Visitor_Name
UNION ALL
SELECT '2018-01-02' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-02' AS dt, 'C' AS Visitor_Name
UNION ALL
SELECT '2018-01-02' AS dt, 'E' AS Visitor_Name
UNION ALL
SELECT '2018-01-03' AS dt, 'A' AS Visitor_Name
UNION ALL
SELECT '2018-01-03' AS dt, 'P' AS Visitor_Name
UNION ALL
SELECT '2018-01-04' AS dt, 'A' AS Visitor_Name
UNION ALL
SELECT '2018-01-04' AS dt, 'C' AS Visitor_Name
UNION ALL
SELECT '2018-01-05' AS dt, 'D' AS Visitor_Name
UNION ALL
SELECT '2018-01-05' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-05' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-06' AS dt, 'P' AS Visitor_Name) AS b
ON (a.dt <= b.dt)
GROUP BY Date_range
ORDER BY Date_range;
You can accomplish this by "multiplying" the records before aggregating. That is, give each user a record for each date that the user should count.
Here is an example:
with t as (
SELECT DATE('2018-01-01') AS dt, 'A' AS Visitor_Name
UNION ALL
SELECT '2018-01-01' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-01' AS dt, 'C' AS Visitor_Name
UNION ALL
SELECT '2018-01-01' AS dt, 'D' AS Visitor_Name
UNION ALL
SELECT '2018-01-02' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-02' AS dt, 'C' AS Visitor_Name
UNION ALL
SELECT '2018-01-02' AS dt, 'E' AS Visitor_Name
UNION ALL
SELECT '2018-01-03' AS dt, 'A' AS Visitor_Name
UNION ALL
SELECT '2018-01-03' AS dt, 'P' AS Visitor_Name
UNION ALL
SELECT '2018-01-04' AS dt, 'A' AS Visitor_Name
UNION ALL
SELECT '2018-01-04' AS dt, 'C' AS Visitor_Name
UNION ALL
SELECT '2018-01-05' AS dt, 'D' AS Visitor_Name
UNION ALL
SELECT '2018-01-05' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-05' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-06' AS dt, 'P' AS Visitor_Name
)
select dt, count(distinct visitor_name) as num_visitors
from (select distinct date_add(dt, interval inc day) as dt, visitor_name
from t CROSS JOIN
(select 0 as inc UNION ALL
SELECT 1
) x
) t
group by t.dt
order by t.dt;
Below is for BigQuery Standard SQL
#standardSQL
SELECT CONCAT(CAST(DATE_SUB(dt, INTERVAL 1 DAY) AS STRING), '::', CAST(dt AS STRING)) Date_range,
ANY_VALUE((SELECT COUNT(DISTINCT visitor) FROM UNNEST(arr_visitors) visitor)) AS Visitor_Count
FROM (
SELECT dt,
ARRAY_AGG(visitor_name) OVER(ORDER BY UNIX_DATE(dt) RANGE BETWEEN 1 PRECEDING AND CURRENT ROW) AS arr_visitors
FROM `project.dataset.your_table`
)
GROUP BY Date_range
You can test / play with it using dummy data from your question as below
#standardSQL
WITH `project.dataset.your_table` AS (
SELECT DATE('2018-01-01') AS dt, 'A' AS Visitor_Name UNION ALL
SELECT '2018-01-01', 'B' UNION ALL
SELECT '2018-01-01', 'C' UNION ALL
SELECT '2018-01-01', 'D' UNION ALL
SELECT '2018-01-02', 'B' UNION ALL
SELECT '2018-01-02', 'C' UNION ALL
SELECT '2018-01-02', 'E' UNION ALL
SELECT '2018-01-03', 'A' UNION ALL
SELECT '2018-01-03', 'P' UNION ALL
SELECT '2018-01-04', 'A' UNION ALL
SELECT '2018-01-04', 'C' UNION ALL
SELECT '2018-01-05', 'D' UNION ALL
SELECT '2018-01-05', 'B' UNION ALL
SELECT '2018-01-05', 'B' UNION ALL
SELECT '2018-01-06', 'P'
)
SELECT CONCAT(CAST(DATE_SUB(dt, INTERVAL 1 DAY) AS STRING), '::', CAST(dt AS STRING)) Date_range,
ANY_VALUE((SELECT COUNT(DISTINCT visitor) FROM UNNEST(arr_visitors) visitor)) AS Visitor_Count
FROM (
SELECT dt,
ARRAY_AGG(visitor_name) OVER(ORDER BY UNIX_DATE(dt) RANGE BETWEEN 1 PRECEDING AND CURRENT ROW) AS arr_visitors
FROM `project.dataset.your_table`
)
GROUP BY Date_range
ORDER BY Date_range
with result
Row Date_range Visitor_Count
1 2017-12-31::2018-01-01 4
2 2018-01-01::2018-01-02 5
3 2018-01-02::2018-01-03 5
4 2018-01-03::2018-01-04 3
5 2018-01-04::2018-01-05 4
6 2018-01-05::2018-01-06 3

SQL how to count how many months have passed since certain status

I have a table with project, period and status with their periodic values. My goal is to show how many months have passed since certain project was last in 'Approved status' (or show what period this status was changed) in the period_leaving_approved_status column. for example, for periods 201005 to 201008 - i would need to show '201005' values, but for periods 201011-201012 - '201011' values. I managed to mark the line where status changes, but i don't know how to apply the condition for the following rows. My example query :
with subq as (
select 123 as project, 201002 as period, 'Approved' as status from dual
union all
select 123 as project, 201003 as period, 'Approved' as status from dual
union all
select 123 as project, 201004 as period, 'Approved' as status from dual
union all
select 123 as project, 201005 as period, 'Pending Close' as status from dual
union all
select 123 as project, 201006 as period, 'Pending Close' as status from dual
union all
select 123 as project, 201007 as period, 'Closed' as status from dual
union all
select 123 as project, 201008 as period, 'Closed' as status from dual
union all
select 123 as project, 201009 as period, 'Approved' as status from dual
union all
select 123 as project, 201010 as period, 'Approved' as status from dual
union all
select 123 as project, 201011 as period, 'Closed' as status from dual
union all
select 123 as project, 201012 as period, 'Closed' as status from dual
union all
select 123 as project, 201101 as period, 'Approved' as status from dual
union all
select 123 as project, 201102 as period, 'Approved' as status from dual
union all
select 123 as project, 201112 as period, 'Approved' as status from dual
union all
select 123 as project, 201301 as period, 'Pending Close' as status from dual
union all
select 123 as project, 201302 as period, 'Closed' as status from dual
union all
select 123 as project, 201203 as period, 'Closed' as status from dual
)
select project,
period,
status,
case when lag(status, 1, null) OVER (ORDER BY period)='Approved'
AND lag(status, 1, null) OVER (ORDER BY period) NOT IN(status) then period end as period_leaving_approved_status
from subq
I would approach this using aggregation:
select project,
max(case when status = 'Approved' then period end) as ApprovedPeriod
from subq;
If you want the time span then something like this:
select project,
months_between(max(case when status = 'Approved' then period end),
sysdate) as monthsSinceApproved,
max(case when status = 'Approved' then period end) as ApprovedPeriod
from subq
group by project;
EDIT:
If you want this information cumulatively on all rows, then use window functions:
select s.*,
max(case when status = 'Approved' then period end) over
(partition b project order by period) as LastApprovedPeriod
from subq s;

Transposing rows into columns

I have following data
with sample_data as (select to_date('05/01/2015', 'dd/mm/yyyy') dt, '1' Period, 'A' code from dual union all
select to_date('05/01/2015', 'dd/mm/yyyy') dt, '2' Period, 'A' code from dual union all
select to_date('05/01/2015', 'dd/mm/yyyy') dt, '3' Period, 'P' code from dual union all
select to_date('05/01/2015', 'dd/mm/yyyy') dt, '4' Period, 'A' code from dual union all
select to_date('05/01/2015', 'dd/mm/yyyy') dt, '5' Period, 'P' code from dual
)
select * from sample_data
That gives me simple query results as below
DT PERIOD CODE
2015-01-05 00:00:00 1 A
2015-01-05 00:00:00 2 A
2015-01-05 00:00:00 3 P
2015-01-05 00:00:00 4 A
2015-01-09 00:00:00 5 P
I would like to transpose the results like this -
DATE ATTENDANCE
2015-01-05 12345
AAPAP
How can I do that?
Thanks a bunch!
This is an Oracle SQL question and not PL/SQL!
That could be something like:
select to_char(dt,'YYYY-MM-DD') date,listagg(period) within group (order by period)||chr(13)||chr(10)||listagg(code) within group (order by period) attendance
from (
with sample_data as (select to_date('05/01/2015', 'dd/mm/yyyy') dt, '1' Period, 'A' code from dual union all
select to_date('05/01/2015', 'dd/mm/yyyy') dt, '2' Period, 'A' code from dual union all
select to_date('05/01/2015', 'dd/mm/yyyy') dt, '3' Period, 'P' code from dual union all
select to_date('05/01/2015', 'dd/mm/yyyy') dt, '4' Period, 'A' code from dual union all
select to_date('05/01/2015', 'dd/mm/yyyy') dt, '5' Period, 'P' code from dual
) select * from sample_data
) group by to_char(dt,'YYYY-MM-DD')
Note that the function listagg only can be used from Oracle 11.2. If you are on an earlier version you can use xmlagg, but thats a bit more clotted.

How to get summary based on another table group summary?

Business Rules: Get total cust_points for customer only if total(trans_amount) for a trans_code grouping > 0.
For customer #1, the summary at date_code level (code 10) is > 0 so cust_points total = 70.
For customer #2 only code 20 group totals > 0 so total only 75 total cust_points
Here's my query:
with customers as
(select '1' as cust_id, 10 as date_code, 30 as cust_points from dual union all
select '1' as cust_id, 10 as date_code, 40 as cust_points from dual union all
select '1' as cust_id, 20 as date_code, 22 as cust_points from dual union all --These points should not total because trans_amount sum for code 20 is less than 0
select '1' as cust_id, 40 as date_code, 33 as cust_points from dual union all -- These points should not total because there is not trans_amounts > 0 for date_code
select '2' as cust_id, 10 as date_code, 20 as cust_points from dual union all
select '2' as cust_id, 20 as date_code, 65 as cust_points from dual union all
select '2' as cust_id, 20 as date_code, 10 as cust_points from dual
),
transactions_row as
(
select '1' as cust_id, '10' as trans_code, 10.00 as trans_amount from dual union all
select '1' as cust_id, '20' as trans_code, -15.00 as trans_amount from dual union all
select '1' as cust_id, '20' as trans_code, -20.00 as trans_amount from dual union all
select '1' as cust_id, '20' as trans_code, -10.00 as trans_amount from dual union all
select '1' as cust_id, '30' as trans_code, 30.00 as trans_amount from dual union all
select '1' as cust_id, '20' as trans_code, -20.00 as trans_amount from dual union all
select '2' as cust_id, '10' as trans_code, -50.00 as trans_amount from dual union all
select '2' as cust_id, '20' as trans_code, 20.00 as trans_amount from dual
)
select cust_id,
sum(cust_points)
from customers
where cust_id in
(
select cust_id
from (
select cust_id, trans_code, sum(trans_amount)
from transactions_row
group by cust_id, trans_code
having sum(trans_amount) > 0
)
)
group by cust_id
Desired Results
CUST_ID CUST_POINTS
1 70 /* (30 because total trans_amount for tran_code(10) > 0 +
40 because total trans_amount for tran_code(10) > 0) */
2 75 /* Do not include the 20 points because total trans_amt for 10 < 0 */
Here's one way using exists:
select cust_id,
sum(cust_points)
from customers c
where exists (
select 1
from transactions_row tr
where tr.trans_code = c.date_code
and tr.cust_id = c.cust_id
group by tr.trans_code, tr.cust_id
having sum(tr.trans_amount) > 0
)
group by cust_id
SQL Fiddle Demo

How do I write an SQL to get a cumulative value and a monthly total in one row?

Say, I have the following data:
select 1 id, date '2007-01-16' date_created, 5 sales, 'Bob' name from dual union all
select 2 id, date '2007-04-16' date_created, 2 sales, 'Bob' name from dual union all
select 3 id, date '2007-05-16' date_created, 6 sales, 'Bob' name from dual union all
select 4 id, date '2007-05-21' date_created, 4 sales, 'Bob' name from dual union all
select 5 id, date '2013-07-16' date_created, 24 sales, 'Bob' name from dual union all
select 6 id, date '2007-01-17' date_created, 15 sales, 'Ann' name from dual union all
select 7 id, date '2007-04-17' date_created, 12 sales, 'Ann' name from dual union all
select 8 id, date '2007-05-17' date_created, 16 sales, 'Ann' name from dual union all
select 9 id, date '2007-05-22' date_created, 14 sales, 'Ann' name from dual union all
select 10 id, date '2013-07-17' date_created, 34 sales, 'Ann' name from dual
I want to get results like the following:
Name Total_cumulative_sales Total_sales_current_month
Bob 41 24
Ann 91 34
In this table, for Bob, his total sales is 41 starting from the beginning. And for this month which is July, his sales for this entire month is 24. Same goes for Ann.
How do I write an SQL to get this result?
Try this way:
select name, sum(sales) as Total_cumulative_sales ,
sum(
case trunc(to_date(date_created), 'MM')
when trunc(sysdate, 'MM') then sales
else 0
end
) as Total_sales_current_month
from tab
group by name
SQL Fiddle Demo
More information
Trunc
Case Statement
SELECT Name,
SUM(Sales) Total_sales,
SUM(CASE WHEN MONTH(date_created) = MONTH(GetDate()) AND YEAR(date_created) = YEAR(GetDate()) THEN Sales END) Total_sales_current_month
GROUP BY Name
Should work, but there's probably a more elegant way to specify "in the current month".
This should work for sales over a number of years. It will get the cumulative sales over any number of years. It won't produce a record if there are no sales in the latest month.
WITH sales AS
(select 1 id, date '2007-01-16' date_created, 5 sales, 'Bob' sales_name from dual union all
select 2 id, date '2007-04-16' date_created, 2 sales, 'Bob' sales_name from dual union all
select 3 id, date '2007-05-16' date_created, 6 sales, 'Bob' sales_name from dual union all
select 4 id, date '2007-05-21' date_created, 4 sales, 'Bob' sales_name from dual union all
select 5 id, date '2013-07-16' date_created, 24 sales, 'Bob' sales_name from dual union all
select 6 id, date '2007-01-17' date_created, 15 sales, 'Ann' sales_name from dual union all
select 7 id, date '2007-04-17' date_created, 12 sales, 'Ann' sales_name from dual union all
select 8 id, date '2007-05-17' date_created, 16 sales, 'Ann' sales_name from dual union all
select 9 id, date '2007-05-22' date_created, 14 sales, 'Ann' sales_name from dual union all
select 10 id, date '2013-07-17' date_created, 34 sales, 'Ann' sales_name from dual)
SELECT sales_name
,total_sales
,monthly_sales
,mon
FROM (SELECT sales_name
,SUM(sales) OVER (PARTITION BY sales_name ORDER BY mon) total_sales
,SUM(sales) OVER (PARTITION BY sales_name,mon ORDER BY mon) monthly_sales
,mon
,max_mon
FROM ( SELECT sales_name
,sum(sales) sales
,mon
,max_mon
FROM (SELECT sales_name
,to_number(to_char(date_created,'YYYYMM')) mon
,sales
,MAX(to_number(to_char(date_created,'YYYYMM'))) OVER (PARTITION BY sales_name) max_mon
FROM sales
ORDER BY 2)
GROUP BY sales_name
,max_mon
,mon
)
)
WHERE max_mon = mon
;