How can I get cumulative sum using SQL? - sql

I have a table of individual sales, for which I would like to summarize into two columns, with a monthly total in one and a cumulative sum in another.
Company A and Company B are subsidiary company under the same parent company, thus, need to be considered as one for calculating cumulative income.
I tried this code and output is following:
SUM(INCOME) OVER(PARTITION BY COMPANY ORDER BY MONTH ROWS UNBOUNDED PRECEDING) AS CUMULATIVE
Company Month Income Cumulative
Company A 1 20 20
Company B 1 0 20
Company C 1 20 20
Company A 2 20 40
Company B 2 0 40
But I want to return 0 when Company B has 0 income for cumulative
Company Month Income Cumulative
Company A 1 20 20
Company B 1 0 0
Company C 1 20 20
Company A 2 20 40
Company B 2 0 0
How can I return 0 for cumulative when either company A or company B has income of 0?!

You have a bad design for the prupose you want and need
adding an identidy field for sorting purposes and converting your short name into numbers, you can achieve it with window function.
A column for the year is also necessary as you can't sort when you got two janissary for the same company
CREATE TABLE tabl1 (
id_num int IDENTITY(1,1),
Company VARCHAR(9),
Month VARCHAR(3),
Income INTEGER
);
INSERT INTO tabl1
(Company, Month, Income)
VALUES
('Company A', 'Jan', '20'),
('Company B', 'Jan', '0'),
('Company A', 'Feb', '20'),
('Company B', 'Feb', '0'),
('Company C', 'Jan', '20');
5 rows affected
SELECT
Company, Month, Income,
SUM(Income) OVER(PARTITION BY company ORDER BY
CASE WHEN Month= 'Jan' THEN 1
WHEN Month = 'Feb' THEN 2
WHEN Month = 'Dec' THEN 12
END) as Cumulative
FROM
tabl1
ORDER BY id_num
Company
Month
Income
Cumulative
Company A
Jan
20
20
Company B
Jan
0
0
Company A
Feb
20
40
Company B
Feb
0
0
Company C
Jan
20
20
fiddle
So a better would be
CREATE TABLE tabl1 (
id_num int IDENTITY(1,1),
Company VARCHAR(9),
Month VARCHAR(3),
[Year] int,
Income INTEGER
);
INSERT INTO tabl1
(Company, Month,[Year], Income)
VALUES
('Company A', 'Jan',2023, '20'),
('Company B', 'Jan',2023, '0'),
('Company A', 'Feb',2023, '20'),
('Company B', 'Feb',2023, '0'),
('Company C', 'Jan',2023, '20');
5 rows affected
SELECT
Company, Month,[Year], Income,
SUM(Income) OVER(PARTITION BY company ORDER BY
CASE WHEN Month= 'Jan' THEN 1
WHEN Month = 'Feb' THEN 2
WHEN Month = 'Dec' THEN 12
END, YEAR ) as Cumulative
FROM
tabl1
ORDER BY id_num
Company
Month
Year
Income
Cumulative
Company A
Jan
2023
20
20
Company B
Jan
2023
0
0
Company A
Feb
2023
20
40
Company B
Feb
2023
0
0
Company C
Jan
2023
20
20
fiddle

Related

Fetch conditional rows in SQL server

I need a query like below. ApplicationID and InvoiceNumber columns show purchases made. Negative values in the Revenue rows indicate shopping refund. The ApplicationID column does not change when the purchase is refunded, but the InvoiceNumber column changes for the refund. I determine the returns according to the price totals of different InvoiceNumbers in the same ApplicationID equal to zero. For example, customer A bought 4 products that InvoiceNumber=AA in ApplicationID=11 shopping, but refund 2 of them (InvoiceNumber=BB). I want to get the remaining rows after the refunds are extracted. So in this example, rows 1-2 and 5-6 will eliminate each other for ApplicationID=11 and only rows 3-4 will remain. In addition, ApplicationID=22 and ApplicationID=33 rows will also come as it does not contain refunds. Finally, rows 3,4,7, 8 and 9 will get. How do I do this?
CustomerCode ApplicationID InvoiceNumber Date Revenue
A 11 AA 1.01.2020 150
A 11 AA 2.01.2020 200
A 11 AA 1.01.2020 250
A 11 AA 1.01.2020 300
A 11 BB 5.01.2020 -150
A 11 BB 5.01.2020 -200
A 22 CC 7.02.2020 500
A 22 DD 7.02.2020 700
A 11 AA 2.01.2020 800
I wrote the result I want. I want to subtract zero sum of revenue according to CustomerCode and ApplicationID and fetch all other columns
example code:
select a.CustomerCode,a.ApplicationID from Table a
group by CustomerCode,a.ApplicationID
having SUM(Revenue)>0
My desired result:
CustomerCode ApplicationID InvoiceNumber Date Revenue
A 11 AA 1.01.2020 250
A 11 AA 1.01.2020 300
A 22 CC 7.02.2020 500
A 22 DD 7.02.2020 700
A 11 AA 2.01.2020 800
I think you've gone down a route of needing to sum your results to remove certain rows from your data but that's not necessarily the case.
You can use a LEFT JOIN back to itself joining on CustomerCode, ApplicationID and Revenue = -Revenue; this effectively finds "purchase" rows that have an associated "refund" row (and vice versa). You can then just filter them off with your WHERE clause
Here's the code I used
DROP TABLE IF EXISTS #Orders
CREATE TABLE #Orders (CustomerCode VARCHAR(1), ApplicationID INT, InvoiceNumber VARCHAR(2), [Date] DATE, Revenue INT)
INSERT INTO #Orders (CustomerCode, ApplicationID, InvoiceNumber, Date, Revenue)
VALUES ('A', 11, 'AA', '2020-01-01', 150),
('A', 11, 'AA', '2020-01-02', 200),
('A', 11, 'AA', '2020-01-01', 250),
('A', 11, 'AA', '2020-01-01', 300),
('A', 11, 'BB', '2020-01-05', -150),
('A', 11, 'BB', '2020-01-05', -200),
('A', 22, 'CC', '2020-01-07', 500),
('A', 22, 'DD', '2020-01-07', 700),
('A', 11, 'AA', '2020-01-02', 800)
SELECT O.CustomerCode, O.ApplicationID, O.InvoiceNumber, O.Date, O.Revenue
FROM #Orders AS O
LEFT JOIN #Orders AS O2 ON O2.ApplicationID = O.ApplicationID AND O2.CustomerCode = O.CustomerCode AND O.Revenue = -O2.Revenue
WHERE O2.ApplicationID IS NULL
And this is the output:
CustomerCode ApplicationID InvoiceNumber Date Revenue
A 11 AA 2020-01-01 250
A 11 AA 2020-01-01 300
A 22 CC 2020-01-07 500
A 22 DD 2020-01-07 700
A 11 AA 2020-01-02 800

SQL turn rows into columns

Hello is it possible to turn row values into columns.
I am using ORACLE SQL and I want to take the month and turn it into columns with the kpi value as shown below.
I tried partitions and merge statements but nothing seems to work.
I would really appreciate some help.
Thank you in advance.
Input data:
department
year
month
kpi
value
A
2000
1
sales
5000
A
2000
1
revenue per client
120
A
2000
2
sales
6000
A
2000
2
revenue per client
140
Desired Output:
department
year
kpi
1
2
A
2000
sales
5000
6000
A
2000
revenue per client
120
140
You can use pivot to do so:
Schema and insert statements:
create table mytable (department varchar(20),year int,month int,kpi varchar(50),value int);
insert into mytable values('A', 2000, 1, 'sales' ,5000);
insert into mytable values('A', 2000, 1, 'revenue per client', 120);
insert into mytable values('A', 2000, 2, 'sales' ,6000);
insert into mytable values('A', 2000, 2, 'revenue per client', 140);
Query:
select * from (
select department,year,month,kpi,value
from mytable
)
pivot
(
max(value)
for month in (1,2)
)
Output:
DEPARTMENT
YEAR
KPI
1
2
A
2000
revenue per client
120
140
A
2000
sales
5000
6000
db<fiddle here
You can use conditional aggrwegation:
select department, year, kpi,
max(case when month = 1 then value end) as month_1,
max(case when month = 2 then value end) as month_2
from t
group by department, year, kpi;

Ways to find Percentage increase in sales Month over Month

I want to find the percentage increase in Month over month sales amount using SQL Server. I want to find % MoM increase in sales by using self join and also using partition with rows unbounded preceding. I do not want to use lag(). Can anyone let me know about the the ways to generate this solution.
Here is my table.
create table growth_new(slno bigint,mon varchar(30),sales_amount bigint)
insert into growth_new values(1, 'Jan', 5000)
insert into growth_new values(2, 'Feb', 12000)
insert into growth_new values(3, 'Mar', 32000)
insert into growth_new values(4, 'Apr', 20000)
Slno Mon sales_amount
1 Jan 5000
2 Feb 12000
3 Mar 32000
4 Apr 20000
You can use lag(). If slno orders the rows, then:
select gn.*,
(gn.sales_amount * 1.0 / lag(gn.sales_amount) over (order by slno)) - 1 as increase
from growth_new gn;
A self-join doesn't really make sense for this problem. But if you really needed to with this data structure:
with gn as (
select gn.*, convert(date, month + ' 2000') as mm
from growth_new
)
select gn.*,
(gn.sales_amount * 1.0 / gnprev.sales_amount) - 1
from gn left join
gn gnprev
on gnprev.mm = dateadd(month, -1, gn.mm);
You should, however, really fix the data so the month is in a reasonable format.
If you don't want to use the LEAD or LAG, you can use the following:
I assumed that you can compare using the ids, otherwise, you can have a table to store the months Ids
selecT g.*, growth = 100*cast(iif(p.sales_amount is null,0,(g.sales_amount-p.sales_amount)*1.0/p.sales_amount) as money)
from growth_new g
left join growth_new p on p.slno=g.slno-1
the output is:
slno mon sales_amount growth
1 Jan 5000 0.00
2 Feb 12000 140.00
3 Mar 32000 166.67
4 Apr 20000 -37.50
Hope this helps you
You could use the lag function really unless you want to try other alternatives. Also as mentioned above your month format is not ideal and not scalable at all.
WITH growth_new(slno ,mon ,sales_amount)
AS (SELECT 1, 'Jan', 5000 UNION
SELECT 2, 'Feb', 12000 UNION
SELECT 3, 'Mar', 32000 UNION
SELECT 4, 'Apr', 20000
)
SELECT cur.*, prev.mon as prev_month,
ISNULL(prev.sales_amount,0) AS prev_month_sales_amount,
[%MoM Change] = ((cur.sales_amount -
ISNULL(prev.sales_amount,0))/CAST(prev.sales_amount as float))*100
FROM growth_new cur
LEFT JOIN growth_new prev ON prev.slno = cur.slno - 1
slno mon sales_amount prev_month prev_month_sales_amount %MoM Change
1 Jan 5000 NULL 0 NULL
2 Feb 12000 Jan 5000 140
3 Mar 32000 Feb 12000 166.666666666667
4 Apr 20000 Mar 32000 -37.5

SQL Server query for new and repeat orders per month

I am working in SQL Server 2008 R2 and having a hard time gathering new vs repeat customer orders.
I have data in this format:
OrderID OrderDate Customer OrderAmount
-----------------------------------------------
1 1/1/2017 A $10
2 1/2/2017 B $20
3 1/3/2017 C $30
4 4/1/2017 C $40
5 4/2/2017 D $50
6 4/3/2017 D $60
7 1/6/2018 B $70
Here's what we want:
New defined as: customer has not placed any orders in any prior months.
Repeat defined as: customer has placed an order in a prior month (even if many years ago).
This means that if a new customer places multiple orders in her first month, they would all be considered "new" customer orders. And orders placed in subsequent months would all be considered "repeat" customer orders.
We want to get New orders (count and sum) and Repeat orders (count and sum) per year, per month:
Year Month NewCount NewSum RepeatCount RepeatSum
-----------------------------------------------------------------------------
2017 1 3 (A,B,C) $60 (10+20+30) 0 $0
2017 4 2 (D,D) $110 (50+60) 1 (C) $40 (40)
2018 1 0 $0 1 (B) $70 (70)
(The info in () parenthesis is not part of the result; just putting it here for clarity)
The SQL is easy to write for any single given month, but I don't know how to do it when gathering years worth of months at a time...
If there is a month with no orders of any kind then NULL or 0 values for the year:month would be preferred.
You can use dense_rank to find new and old customers. This query returns your provided output
declare #t table (OrderID int, OrderDate date, Customer char(1), OrderAmount int)
insert into #t
values (1, '20170101', 'A', 10)
, (2, '20170102', 'B', 20), (3, '20170103', 'C', 30)
, (4, '20170401', 'C', 40), (5, '20170402', 'D', 50)
, (6, '20170403', 'D', 60), (7, '20180106', 'B', 70)
select
[year], [month], NewCount = isnull(sum(case when dr = 1 then 1 end), 0)
, NewSum = isnull(sum(case when dr = 1 then OrderAmount end), 0)
, RepeatCount = isnull(sum(case when dr > 1 then 1 end), 0)
, RepeatSum = isnull(sum(case when dr > 1 then OrderAmount end), 0)
from (
select
*, [year] = year(OrderDate), [month] = month(OrderDate)
, dr = dense_rank() over (partition by Customer order by dateadd(month, datediff(month, 0, OrderDate), 0))
from
#t
) t
group by [year], [month]
Output
year month NewCount NewSum RepeatCount RepeatSum
----------------------------------------------------------
2017 1 3 60 0 0
2018 1 0 0 1 70
2017 4 2 110 1 40
You must get combination of each year in the table with all months at first if you want to display months without orders. Then join with upper query
select
*
from
(select distinct y = year(OrderDate) from #t) t
cross join (values (1), (2), (3), (4), (5), (6), (7), (8), (9), (10), (11), (12)) q(m)
First, start by summarizing the data with one record per customer per month.
Then, you can use a self-join or similar construct to get the information you need:
with cm as (
select customer, dateadd(day, 1 - day(orderdate), orderdate) as yyyymm
sum(orderamount) as monthamount, count(*) as numorders
from orders
group by customer
)
select year(cm.yyyymm) as yr, month(cm.yyyymm) as mon,
sum(case when cm.num_orders > 0 and cm_prev.customer is null then 1 else 0 end) as new_count,
sum(case when cm.num_orders > 0 and cm_prev.customer is null then monthamount else 0 end) as new_amount,
sum(case when cm.num_orders > 0 and cm_prev.customer > 0 then 1 else 0 end) as repeat_count,
sum(case when cm.num_orders > 0 and cm_prev.customer > 0 then monthamount else 0 end) as repeat_amount
from cm left join
cm cm_prev
on cm.customer = cm_prev.customer and
cm.yyyymm = dateadd(month, 1, cm_prev.yyyymm)
group by year(cm.yyyymm), month(cm.yyyymm)
order by year(cm.yyyymm), month(cm.yyyymm);
This would be a bit easier in SQL Server 2012, where you can use lag().

I want a select query result in tabular format like summary report

for examaple
month1 month2 month3 total
district1 5 2 9 16
district2 1 0 11 12
.
.
total 260 150 140 550
here final total is not much important. but at least i need to show count per district per month.
SELECT Districts_mst.district_name,COUNT(Payments.PaymentId)users ,DATEPART(M,payments.saveon)Month
FROM Payments
JOIN Subsciber ON Payments.SubId =Subsciber.SubId
JOIN districts_mst ON districts_mst.district_id = Subsciber.District
where lang_id=1
group by district_name, DATEPART(M,payments.saveon)
which give me list like.....
district_name users Month
dist0 1 1
dist1 1 11
dist2 3 11
dist3 1 11
dist4 3 11
dist5 1 12
dist6 1 12
In SQL Server 2008 you can handle this task pretty easily with a PIVOT query. The following example relies on getting your data into the following format (which it looks like you have done already):
Name Month Value
---------- ------- -----
District 1 Month 1 10
District 1 Month 2 5
District 1 Month 3 6
District 2 Month 1 1
District 2 Month 2 2
District 2 Month 3 3
District 3 Month 1 8
District 3 Month 2 6
District 3 Month 3 11
If you can do that, then your PIVOT query should look something like this:
DECLARE #myTable AS TABLE([Name] VARCHAR(20), [Month] VARCHAR(20), [Value] INT)
INSERT INTO #myTable VALUES ('District 1', 'Month 1', 10)
INSERT INTO #myTable VALUES ('District 1', 'Month 2', 5)
INSERT INTO #myTable VALUES ('District 1', 'Month 3', 6)
INSERT INTO #myTable VALUES ('District 2', 'Month 1', 1)
INSERT INTO #myTable VALUES ('District 2', 'Month 2', 2)
INSERT INTO #myTable VALUES ('District 2', 'Month 3', 3)
INSERT INTO #myTable VALUES ('District 3', 'Month 1', 8)
INSERT INTO #myTable VALUES ('District 3', 'Month 2', 6)
INSERT INTO #myTable VALUES ('District 3', 'Month 3', 11)
SELECT [Name], [Month 1], [Month 2], [Month 3], [NameTotalValue] AS [Total]
FROM
(
SELECT [Name], [Month], [Value],
SUM([Value]) OVER (PARTITION BY [Name]) as [NameTotalValue]
FROM #myTable
UNION
SELECT 'Total', [Month], SUM([Value]), (SELECT SUM([Value]) FROM #myTable)
FROM #myTable
GROUP BY [Month]
) t
PIVOT
(
SUM([Value]) FOR [Month] IN ([Month 1], [Month 2], [Month 3])
) AS pvt
ORDER BY pvt.[Name]
In this example, I used the SUM([Value]) OVER PARTITION to get the sums for each District, and then I did a UNION to add a totals row to the bottom. The results look like this:
Name Month 1 Month 2 Month 3 Total
----------- ------- ------- ------- -----
District 1 10 5 6 21
District 2 1 2 3 6
District 3 8 6 11 25
Total 19 13 20 52
One thing you'll notice about this approach is that you have to know the column names you want at the top of the table ahead of time. That's easy to do if you're setting up the report to run for a full year, but is trickier if the number of columns is going to change. If you're going to allow the users to specify a custom date range (i.e., 07/2011-10/2011 or 06/2011-11/2011), then one way handle that requirement is to build the PIVOT query using dynamic SQL and then execute it with sp_executesql.