Summing Sales Based On Different Year Cases - sql

| Product ID | YearBought | Sales | Min_Year | Max_Year |
| 1 | 2016 | $20 | 2011 | 2016 |
| 2 | 2016 | $10 | 2016 | 2018 |
| 2 | 2017 | $30 | 2016 | 2018 |
| 3 | 2017 | $5 | 2015 | 2019 |
| 3 | 2018 | $10 | 2015 | 2019 |
| 3 | 2018 | $20 | 2015 | 2019 |
| 3 | 2019 | $5 | 2015 | 2019 |
| 3 | 2019 | $30 | 2015 | 2019 |
| 4 | 2018 | $5 | 2018 | 2020 |
| 4 | 2019 | $10 | 2018 | 2020 |
| 4 | 2020 | $20 | 2018 | 2020 |
Min_Year = the year the product was first introduced
Max_Year + 1 = Product drop off year
Above is a sample of the table I'm working with. Trying to find:
the sum of sales new products brought in the year they were first introduced
the sum of "dropped sales" aka the sum of sales from products the year after they dropped off (had no sales). (Ex. Product brought in $15 in 2018 but had no sales in 2019, want to show $15 as dropped sales in 2019)
Expected Output:
| YearBought | New Product Sales | Dropped Product Sales |
| 2016 | $10 | |
| 2017 | | $20 |
| 2018 | $5 | |
| 2019 | | |
| 2020 | | $35 |
was thinking something like this but it's not working. any help would be appreciated!
select
YearBought,
sum(case when yearbought=min_year then sales else 0 end) as NewSales,
sum(case when yearbought=max_year+1 then sales else 0 end) as DropSales
from
#t
group by
yearbought

Aggregate the dropped product sales separately from the new product sales, then join the aggregations. You can do this with subqueries, or, as I have done below, with common table expressions.
with
droppedProds as (
select droppedYear = yearBought + 1,
foregoneSales = sum(sales)
from #t t
where YearBought = Max_Year
group by YearBought
),
newSales as (
select YearBought,
sales = sum(sales),
newSales = sum(case when yearBought = min_year then sales end)
from #t t
group by YearBought
)
select YearBought,
n.sales,
n.newSales,
d.foregoneSales
from newSales n
left join droppedProds d on n.yearBought = d.droppedYear
order by YearBought;
Results in:
+------------+-------+----------+---------------+
| YearBought | sales | newSales | foregoneSales |
|------------|-------|----------|---------------|
| 2016 | 30 | 10 | |
| 2017 | 35 | | 20 |
| 2018 | 35 | 5 | |
| 2019 | 45 | | |
| 2020 | 20 | | 35 |
+------------+-------+----------+---------------+

You could list the dates first, then join it with the original table and do conditional aggregation:
select
y.yearbought,
sum(case when t.yearbought = t.min_year then sales end) new_product_sales,
sum(case when t.yearbought = t.max_year then sales end) dropped_product_sales
from (select distinct yearbought from #t) y
inner join #t on y.yearbought in (t.min_year, t.max_year + 1)
group by y.yearbought

I think your problem is that the years are staggered.
with n as (
select year_bought as y, sum(sales) as sales,
sum(case when year_bought = min_year then sales end) as ns
from T group by year_bought
), d as (
select year_bought + 1 as y,
sum(case when year_bought = max_year then sales end) as ds
from T group by year_bought
)
select y, sales,
ns as newSales, coalesce(dropped, 0) as foregoneSales
from n left outer join p on p.y = n.y;
or use lead()
select year_bought, sum(Sales) as Sales,
sum(case when year_bought = min_year then sales end) as newSales,
coalesce(lead(sum(case when year_bought = max_year then sales end))
over (order by year_bought), 0) as foregoneSales
from T
group by year_bought;

Related

SQL Find max no of consecutive months over a period of last 12 Months

I am trying to write a query in sql where I need to find the max no. of consecutive months over a period of last 12 months excluding June and July.
so for example I have an initial table as follows
+---------+--------------+-----------+------------+
| id | Payment | amount | Date |
+---------+--------------+-----------+------------+
| 1 | CJ1 | 70000 | 11/3/2020 |
| 1 | 1B4 | 36314000 | 12/1/2020 |
| 1 | I21 | 119439000 | 1/12/2021 |
| 1 | 0QO | 9362100 | 2/2/2021 |
| 1 | 1G0 | 140431000 | 2/23/2021 |
| 1 | 1G | 9362100 | 3/2/2021 |
| 1 | g5d | 9362100 | 4/6/2021 |
| 1 | rt5s | 13182500 | 4/13/2021 |
| 1 | fgs5 | 48598 | 5/18/2021 |
| 1 | sd8 | 42155 | 5/25/2021 |
| 1 | wqe8 | 47822355 | 7/20/2021 |
| 1 | cbg8 | 4589721 | 7/27/2021 |
| 1 | jlk8 | 4589721 | 8/3/2021 |
| 1 | cxn9 | 4589721 | 10/5/2021 |
| 1 | qwe | 45897210 | 11/9/2021 |
| 1 | mmm | 45897210 | 12/16/2021 |
+---------+--------------+-----------+------------+
I have written below query:
SELECT customer_number, year, month,
payment_month - lag(payment_month) OVER(partition by customer_number ORDER BY year, month) as previous_month_indicator,
FROM
(
SELECT DISTINCT Month(date) as month, Year(date) as year, CUSTOMER_NUMBER
FROM Table1
WHERE Month(date) not in (6,7)
and TO_DATE(date,'yyyy-MM-dd') >= DATE_SUB('2021-12-31', 425)
and customer_number = 1
) As C
and I get this output
+-----------------+------+-------+--------------------------+
| customer_number | year | month | previous_month_indicator |
+-----------------+------+-------+--------------------------+
| 1 | 2020 | 11 | null |
| 1 | 2020 | 12 | 1 |
| 1 | 2021 | 1 | -11 |
| 1 | 2021 | 2 | 1 |
| 1 | 2021 | 3 | 1 |
| 1 | 2021 | 4 | 1 |
| 1 | 2021 | 5 | 1 |
| 1 | 2021 | 8 | 3 |
| 1 | 2021 | 10 | 2 |
| 1 | 2021 | 11 | 1 |
+-----------------+------+-------+--------------------------+
What I want is to get a view like this
Expected output
+-----------------+------+-------+--------------------------+
| customer_number | year | month | previous_month_indicator |
+-----------------+------+-------+--------------------------+
| 1 | 2020 | 11 | 1 |
| 1 | 2020 | 12 | 1 |
| 1 | 2021 | 1 | 1 |
| 1 | 2021 | 2 | 1 |
| 1 | 2021 | 3 | 1 |
| 1 | 2021 | 4 | 1 |
| 1 | 2021 | 5 | 1 |
| 1 | 2021 | 8 | 1 |
| 1 | 2021 | 9 | 0 |
| 1 | 2021 | 10 | 1 |
| 1 | 2021 | 11 | 1 |
+-----------------+------+-------+--------------------------+
As June/July does not matter, after May, August should be considered as consecutive month, and since in September there was no record it appears as 0 and breaks the consecutive months chain.
My final desired output is to get the max no of consecutive months in which transactions were made which in above case is 8 from Nov-2020 to Aug-2021
Final Desired Output:
+-----------------+-------------------------+
| customer_number | Max_consecutive_months |
+-----------------+-------------------------+
| 1 | 8 |
+-----------------+-------------------------+
CTEs can break this down a little easier. In the code below, the payment_streak CTE is the key bit; the start_of_streak field is first marking rows that count as the start of a streak, and then taking the maximum over all previous rows (to find the start of this streak).
The last SELECT is only comparing these two dates, computing how many months are between them (excluding June/July), and then finding the best streak per customer.
WITH payments_in_context AS (
SELECT customer_number,
date,
lag(date) OVER (PARTITION BY customer_number ORDER BY date) AS prev_date
FROM Table1
WHERE EXTRACT(month FROM date) NOT IN (6,7)
),
payment_streak AS (
SELECT
customer_number,
date,
max(
CASE WHEN (prev_date IS NULL)
OR (EXTRACT(month FROM date) <> 8
AND (date - prev_date >= 62
OR MOD(12 + EXTRACT(month FROM date) - EXTRACT(month FROM prev_date),12)) > 1))
OR (EXTRACT(month FROM date) = 8
AND (date - prev_date >= 123
OR EXTRACT(month FROM prev_date) NOT IN (5,8)))
THEN date END
) OVER (PARTITION BY customer_number ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
as start_of_streak
FROM payments_in_context
)
SELECT customer_number,
max( 1 +
10*(EXTRACT(year FROM date) - EXTRACT(year FROM start_of_streak))
+ (EXTRACT(month FROM date) - EXTRACT(month FROM start_of_streak))
+ CASE WHEN (EXTRACT(month FROM date) > 7 AND EXTRACT(month FROM start_of_streak) < 6)
THEN -2
WHEN (EXTRACT(month FROM date) < 6 AND EXTRACT(month FROM start_of_streak) > 7)
THEN 2
ELSE 0 END
) AS max_consecutive_months
FROM payment_streak
GROUP BY 1;
You can use a recursive cte to generate all the dates in the twelve month timespan for each customer id, and then find the maximum number of consecutive dates excluding June and July in that interval:
with recursive cte(id, m, c) as (
select cust_id, min(date), 1 from payments group by cust_id
union all
select c.id, c.m + interval 1 month, c.c+1 from cte c where c.c <= 12
),
dts(id, m, f) as (
select c.id, c.m, c.c = 1 or exists
(select 1 from payments p where p.cust_id = c.id and extract(month from p.date) = extract(month from (c.m - interval 1 month)) and extract(year from p.date) = extract(year from (c.m - interval 1 month)))
from cte c where extract(month from c.m) not in (6,7)
),
result(id, f, c) as (
select d.id, d.f, (select sum(d.id = d1.id and d1.m < d.m and d1.f = 0)+1 from dts d1)
from dts d where d.f != 0
)
select r1.id, max(r1.s)-1 from (select r.id, r.c, sum(r.f) s from result r group by r.id, r.c) r1 group by r1.id

How to get balance from two SQL table debit and credit dynamically

Customer table
id | name | customerid
1 | Philip James | ac1001
2 | Frank Mathew | ac1002
Credit table
id| year | customer | amount
1 | 2020 | ac1001 | 1000
2 | 2020 | ac1001 | 1000
3 | 2020 | ac1001 | 1000
4 | 2020 | ac1001 | 1000
5 | 2019 | ac1001 | 1000
6 | 2019 | ac1001 | 2000
7 | 2020 | ac1002 | 2000
8 | 2020 | ac1002 | 2000
Debit table
id| year | customer| amount
1 | 2020 | ac1001 | 1000
2 | 2020 | ac1001 | 1000
3 | 2020 | ac1001 | 1000
4 | 2020 | ac1001 | 1000
5 | 2019 | ac1001 | 2000
6 | 2019 | ac1001 | 2000
7 | 2020 | ac1002 | 2000
8 | 2020 | ac1002 | 2000
I am trying to get the balance for each customer dynamically in respect to the year, i tried using this;
SELECT debit.year,customers.name,customers.customerid,SUM(debit.amount),SUM(credit.amount),
SUM(COALESCE((debit.amount),0)-COALESCE((credit.amount),0))AS balance FROM
customers
RIGHT JOIN credit ON customers.customerid=credit.customer
RIGHT JOIN debit ON customers.customerid=debit.customer GROUP BY customers.customerid,debit.year
Query Result
year| customer | sum(debit)| sum(credit)| Balance
2020 | ac1001 | 48000 | 42000 | 6000
2020 | ac1002 | 8000 | 6000 | 2000
But what i need is this table below, thank you
Expected Result
year| customer | sum(debit)| sum(credit)| Balance
2019 | ac1001 | 4000 | 3000 | 1000
2020 | ac1001 | 4000 | 4000 | 0
2020 | ac1002 | 4000 | 4000 | 0
union the two tables and then aggregate. You can use a cumulative sum to calculate the balance:
select year, customer, sum(amount) as amount_in_year,
sum(sum(amount)) over (partition by customer order by year) as end_of_year_balance
from ((select id, year, customer, amount
from credit
) union all
(select id, year, customer, - amount
from debit
)
) cd
group by year, customer;
EDIT:
For the revised question:
select year, customer, sum(credit) as sum_credit, sum(debit) as sum_debit,
sum(sum(credit - debit)) over (partition by customer order by year) as end_of_year_balance
from ((select id, year, customer, amount as credit, 0 as debit
from credit
) union all
(select id, year, customer, 0 as credit, amount as debit
from debit
)
) cd
group by year, customer;

Join two columns as a date in sql

I am currently working with a report through Microsoft Query and I ran into this problem where I need to calculate the total amount of money for the past year.
The table looks like this:
Item Number | Month | Year | Amount |
...........PAST YEARS DATA...........
12345 | 1 | 2019 | 10 |
12345 | 2 | 2019 | 20 |
12345 | 3 | 2019 | 15 |
12345 | 4 | 2019 | 12 |
12345 | 5 | 2019 | 11 |
12345 | 6 | 2019 | 12 |
12345 | 7 | 2019 | 12 |
12345 | 8 | 2019 | 10 |
12345 | 9 | 2019 | 10 |
12345 | 10 | 2019 | 10 |
12345 | 11 | 2019 | 10 |
12345 | 12 | 2019 | 10 |
12345 | 1 | 2020 | 10 |
12345 | 2 | 2020 | 10 |
How would you calculate the total amount from 02-2019 to 02-2020 for the item number 12345?
Assuming that you are running SQL Server, you can recreate a date with datefromparts() and use it for filtering:
select sum(amount)
from mytable
where
itemnumber = 12345
and datefromparts(year, month, 1) >= '20190201'
and datefromparts(year, month, 1) < '20200301'
You can use this also
SELECT sum(amount) as Amount
FROM YEARDATA
WHERE ( Month >=2 and year = '2019')
or ( Month <=2 and year = '2020')
and ItemNumber = '12345'

Get data for fiscal year from table without date columns

I'm trying to create a query (purpose: manual DB testing) that would get the rows of the previous/current/next Fiscal Year and then the SUM(turnover)
Given
(1) the below table,
and
(2) Fiscal Year (FY) = March to February
When Previous FY -- Then 2 rows: 2016/1 to 2016/2
When Current FY -- Then 12 rows: from 2016/3 to 2017/2 (year/month)
When Future FY -- Then 1 row: 2017/3
+--------------+---------------+----------+
| Year (num) | Month (num) | Turnover |
+--------------+---------------+----------+
| 2016 | 1 | 1000 |
+--------------+---------------+----------+
| 2016 | 2 | 2000 |
+--------------+---------------+----------+
| 2016 | 3 | 3000 |
+--------------+---------------+----------+
| 2016 | 4 | 4000 |
+--------------+---------------+----------+
| 2016 | 5 | 2000 |
+--------------+---------------+----------+
| 2016 | 6 | 1000 |
+--------------+---------------+----------+
| 2016 | 7 | 2000 |
+--------------+---------------+----------+
| 2016 | 8 | 1000 |
+--------------+---------------+----------+
| 2016 | 9 | 2000 |
+--------------+---------------+----------+
| 2016 | 10 | 3000 |
+--------------+---------------+----------+
| 2016 | 11 | 4000 |
+--------------+---------------+----------+
| 2016 | 12 | 5000 |
+--------------+---------------+----------+
| 2017 | 1 | 6000 |
+--------------+---------------+----------+
| 2017 | 2 | 2000 |
+--------------+---------------+----------+
| 2017 | 3 | 1000 |
+--------------+---------------+----------+
The best solution I came up with is the below query and change the Year values to switch between years. It feels hacky to me because of creating an extra solumn with sysdate and checking for NOT NULL. Is there a more elegant way?
WITH CTE AS (
SELECT
CASE
WHEN Month BETWEEN 3 AND 12 AND Year = 2016
THEN sysdate
WHEN Month BETWEEN 1 AND 2 AND Year = 2017
THEN sysdate
END case_statement_date,
year, month, turnover, FROM Table
)
SELECT sum(turnover) FROM CTE
WHERE case_statement_date IS NOT NULL
;
Is this what you want?
select year + (case when month >= 3 then 0 else -1 end) as fiscal_year,
sum(turnover)
from t
group by year + (case when month >= 3 then 0 else -1 end) ;

Query count with multiple table group by month return zero if month is null

I need some help one getting the project count per month for all of the projectTypes per region. I've tried the following statement but it didn't return what I need.
The problem with the statement below is that it only returns the projectType only if there a count for that month. I need to return zero if the month doesn't have a count.
Any help is much appreciated.
SELECT r.region, pt.projectType, count(p.id) as totalCount, p.postedOn as monthCount
FROM region r cross join ProjectTypes pt left join projects p on p.regionID = r.id and pt.id = p.TypeID
WHERE year(p.postedOn) = '2012'
group by r.region, pt.projectType, p.postedOn
order by r.region
Please the sample here: http://sqlfiddle.com/#!3/6680f/18
regions:
-------------------------
id | region |
-------------------------
1 | East |
-------------------------
2 | MidWest |
-------------------------
3 | West |
-------------------------
Project Type:
-------------------------
id | projectType |
-------------------------
1 | Web Desgin |
-------------------------
2 | Database |
-------------------------
3 | Development |
-------------------------
Projects:
-------------------------------------------------------------------------
id | projectName | regionID | projectTypeID | postedOn |
-------------------------------------------------------------------------
1 | Project 1 | 1 | 2 | 2012-09-02 |
-------------------------------------------------------------------------
2 | Project 2 | 2 | 2 | 2012-09-02 |
-------------------------------------------------------------------------
3 | Project 3 | 1 | 1 | 2012-09-02 |
-------------------------------------------------------------------------
4 | Project 4 | 3 | 2 | 2012-09-02 |
-------------------------------------------------------------------------
5 | Project 5 | 3 | 1 | 2012-10-02 |
-------------------------------------------------------------------------
6 | Project 6 | 3 | 2 | 2012-10-02 |
-------------------------------------------------------------------------
7 | Project 7 | 3 | 3 | 2012-10-02 |
-------------------------------------------------------------------------
8 | Project 8 | 2 | 3 | 2012-10-02 |
-------------------------------------------------------------------------
9 | Project 9 | 1 | 2 | 2012-10-02 |
-------------------------------------------------------------------------
10 | Project 10 | 1 | 2 | 2012-10-02 |
-------------------------------------------------------------------------
Desired Results:
---------------------------------------------------------
Region | project Type | totalCount | monthCount |
---------------------------------------------------------
East | Web Desgin | 1 | September |
---------------------------------------------------------
East | Database | 1 | September |
---------------------------------------------------------
East | Development | 0 | September |
---------------------------------------------------------
Midwest | Web Desgin | 0 | September |
---------------------------------------------------------
Midwest | Database | 1 | September |
---------------------------------------------------------
Midwest | Development | 0 | September |
---------------------------------------------------------
West | Web Desgin | 0 | September |
---------------------------------------------------------
West | Database | 1 | September |
---------------------------------------------------------
West | Development | 0 | September |
---------------------------------------------------------
East | Web Desgin | 0 | October |
---------------------------------------------------------
East | Database | 2 | October |
---------------------------------------------------------
East | Development | 0 | October |
---------------------------------------------------------
Midwest | Web Desgin | 0 | October |
---------------------------------------------------------
Midwest | Database | 0 | October |
---------------------------------------------------------
Midwest | Development | 1 | October |
---------------------------------------------------------
West | Web Desgin | 1 | October |
---------------------------------------------------------
West | Database | 1 | October |
---------------------------------------------------------
West | Development | 1 | October |
---------------------------------------------------------
The WITH block defines a virtual table of months (or rather, the first days of each month) required. This extends your CROSS JOIN to give you all the months.
;WITH months(startdate) AS (
SELECT CAST('20120901' AS date)
UNION ALL
SELECT dateadd(m,1,startdate)
FROM months
WHERE startdate < '20121001'
)
SELECT r.region,
pt.projectType,
count(p.id) totalCount,
DATENAME(Month,m.startdate) monthCount
FROM region r
CROSS JOIN ProjectTypes pt
CROSS JOIN months m
LEFT JOIN projects p ON p.regionID = r.id
AND pt.id = p.TypeID
AND p.postedOn >= m.startdate
AND p.postedOn < dateadd(m,1,m.startdate)
GROUP BY r.region, pt.projectType, m.startdate
ORDER BY m.startdate, region, projecttype
OPTION (maxrecursion 0);
I applied this to your fiddle
For SQL Server 2000, or for performance really, create the table Months as a proper table and fill it with months from 1999 through the year 2169, e.g.
CREATE TABLE Months (
startdate datetime -- the first day of month
primary key
);
insert Months
select DateAdd(M,Number,'19990101')
from master..spt_values
where type='P'
GO
Then, just choose the month range you need using the condition against the months table, i.e. as below:
SELECT r.region,
pt.projectType,
count(p.id) totalCount,
DATENAME(Month,m.startdate) monthCount
FROM region r
CROSS JOIN ProjectTypes pt
JOIN months m on m.startdate between '20120901' and '20121001'
LEFT JOIN projects p ON p.regionID = r.id
AND pt.id = p.TypeID
AND p.postedOn >= m.startdate
AND p.postedOn < dateadd(m,1,m.startdate)
GROUP BY r.region, pt.projectType, m.startdate
ORDER BY m.startdate, region, projecttype;
Updated SQL Fiddle
Hi My answer take all months which are in your table and make sets.
SELECT
r.region
,pt.projectType
,(SELECT Count(*)
FROM Projects pp
WHERE pt.id = pp.TypeID
AND r.id = pp.regionID
AND DATENAME(MONTH, p.postedOn)=DATENAME(MONTH, pp.postedOn)) AS COUNT
,DATENAME(MONTH, p.postedOn) AS monthName
FROM ProjectTypes pt, region r,
(SELECT DISTINCT postedOn FROM Projects )p
ORDER BY r.region, DATENAME(MONTH, p.postedOn)
SQLFiddle example