How to get running sum of a column in sql server - sql

Hi I have a column with name Qty from table Bills
i want a column that show the running sum of Qty column like this :
Qty Run_Sum
1 1
2 3
3 6
4 10
5 15
Suggest me some appropriate method to make running some thankx

if you RDBMS supports window function,
for SQL Server 2012
SELECT Qty,
SUM(Qty) OVER (ORDER BY Qty) AS CumulativeTOTAL
FROM tableName
SQLFiddle Demo
for SQL Server 2008
SELECT a.Qty, (SELECT SUM(b.Qty)
FROM TableName b
WHERE b.Qty <= a.Qty)
FROM TableName a
ORDER BY a.Qty;
SQLFiddle Demo

SQLFiddle demo
SELECT Qty,
SUM(Qty) OVER (ORDER BY Qty) Run_Sum
FROM t ORDER BY Qty
For SQLServer prior to 2012:
select Qty,
(select sum(Qty) from t where Qty<=t1.Qty)
from t t1 order by Qty
SQLFiddle demo
Or also you can do it without subquery:
select t1.Qty, sum(t2.Qty)
from t t1
join t t2 on (t1.Qty>=t2.Qty)
group by t1.Qty
order by t1.Qty
SQLFiddle demo

Here's a sample using Oracle/analytical functions:
select id, qty, sum(qty) over(order by id asc) run_sum
from test;
http://www.sqlfiddle.com/#!4/3d149/1

Check this
DECLARE #TEMP table
(
ID int IDENTITY(1,1),
QUANTITY int
)
INSERT INTO #TEMP
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4 UNION ALL
SELECT 8 UNION ALL
SELECT 7 UNION ALL
SELECT 5 UNION ALL
SELECT 1
SELECT t.QUANTITY AS Qty, SUM(t1.QUANTITY) AS Run_Sum
FROM #TEMP t
INNER JOIN #TEMP t1
ON t1.ID <= t.ID
GROUP BY t.ID, t.QUANTITY
ORDER BY t.ID

;with cte as (
select top 1 Qty, Qty as RunningSum
from Bills
order by Qty
union all
select t.Qty, cte.RunningSum + t.Qty
from cte
inner join Bills t on cte.Qty + 1 = t.Qty
)
select * from cte

#mahmud:
See what this gives
DECLARE #Bills table
(
QUANTITY int
)
INSERT INTO #Bills
SELECT 2 UNION ALL
SELECT 6 UNION ALL
SELECT 7 UNION ALL
SELECT 1 UNION ALL
SELECT 3 UNION ALL
SELECT -5 UNION ALL
SELECT 5 UNION ALL
select 1
;with cte as (
select top 1 QUANTITY, QUANTITY as RunningSum
from #Bills
order by QUANTITY
union all
select t.QUANTITY, cte.RunningSum + t.QUANTITY
from cte
inner join #Bills t on cte.QUANTITY + 1 = t.QUANTITY
)
select * from cte

Related

How to find max of counts of a group where count is calculated using a function in SQL?

I have a table made up of customer_id and keyword_id. There are multiple occurance of different combinations of customer_id and keyword_id, and I want to find the highest occurring keyword_id for each customer_id. How should I do that?
Customer_ID . Keyword_ID
1 a
1 a
1 a
1 b
1 b
2 c
2 c
2 c
2 d
Expected Result
Customer_ID . Max_Keyword_ID . Count
1 a 3
2 c 3
You can make use of count and dense_rank to get your expected output. Get the rank =1 to make sure that you are getting the rows where you have maximum occurrences of a given output.
with cte as (
select 1 as customer_id, 'a' as Keyword_ID union all
select 1 as customer_id, 'a' as Keyword_ID union all
select 1 as customer_id, 'a' as Keyword_ID union all
select 1 as customer_id, 'b' as Keyword_ID union all
select 1 as customer_id, 'b' as Keyword_ID union all
select 2 as customer_id, 'c' as Keyword_ID union all
select 2 as customer_id, 'c' as Keyword_ID union all
select 2 as customer_id, 'c' as Keyword_ID union all
select 2 as customer_id, 'd' as Keyword_ID)
SELECT customer_id, Keyword_ID, [COUNT] FROM (
select customer_id, Keyword_ID, count(1) [COUNT],
dENSE_RANK() OVER (PARTITION BY customer_id ORDER BY COUNT(1) DESC) RANKED from cte C
group by customer_id, Keyword_ID ) Z
WHERE Z.RANKED = 1
Output:
customer_id Keyword_ID COUNT
1 a 3
2 c 3
You can try below - using correlated subquery
with cte as
(
select Customer_ID,Keyword_ID,count(Keyword_ID) as cnt
from tablename
group by Customer_ID,Keyword_ID
)
select * from cte a where cnt in (select max(cnt) from cte b where a.Customer_ID=b.Customer_ID )
You can try the following query
select Customer_ID,Keyword_ID,Count(Keyword_ID) as Count from tab group by
Customer_ID,Keyword_ID
Having Count(Keyword_ID)=(
SELECT MAX(mycount)
FROM (
SELECT Keyword_ID, COUNT(Keyword_ID) mycount
FROM tab
GROUP BY Keyword_ID) checkMaxValue)
Click here to view the reference
Another way of doing it using ROW_NUMBER() with PARTITION BY Customer_ID column.
You can try like following.
select *
from
(
select *, row_number() over(partition by Customer_ID order by ct desc) rn
from
(
select Customer_ID , Keyword_ID, count(*) ct
from YOURTABLE
GROUP BY Customer_ID , Keyword_ID
) t
) t1
where rn=1

SQL windows function partition by null values

I have a data-set:
year id
NULL 123
NULL 124
NULL 125
1932 126
1932 127
1933 128
1933 129
1934 130
I would like to create a running count, where I have the group of year with NULL values as one group and the other group with non-null values, namely.
year count
NULL 3
1932 2
1933 4
1934 5
I have tried to do this by union of two windows function data set, namely:
select distinct year,
count(id) over (order by year asc)
from data
where year is null
union
select distinct year,
count(id) over (order by year asc)
from data
where year is not null;
I was wondering if there is a cleaner way of doing this such as:
select distinct year,
count(id) over (partition by <whether year is null condition> order by year
asc)
from data;
MY sql version is db2.
try this:
DECLARE #tab TABLE(year INT, id INT)
INSERT INTO #tab VALUES( NULL,123)
INSERT INTO #tab VALUES(NULL,124)
INSERT INTO #tab VALUES(NULL,125)
INSERT INTO #tab VALUES(1932,126)
INSERT INTO #tab VALUES(1932,127)
INSERT INTO #tab VALUES(1933,128)
INSERT INTO #tab VALUES(1933,129)
INSERT INTO #tab VALUES(1934,130)
SELECT D.year, MAX(D.RN)Count
FROM(
SELECT year,SUM(1) OVER(PARTITION BY CASE WHEN year IS NULL THEN 1 ELSE 0 END ORDER BY id) RN FROM #tab
)D
GROUP BY D.year
Output:
year Count
NULL 3
1932 2
1933 4
1934 5
Union all will get your required output, its hard way to achieve this but yet would get output
declare #table table (year int, id int)
insert #table
(year,id)
select
NULL , 123 union all
select NULL , 124 union all
select NULL , 125 union all
select 1932 , 126 union all
select 1932 , 127 union all
select 1933 , 128 union all
select 1933 , 129 union all
select 1934 , 130
select Runningtotal, year from
(
select SUM(count) over (order by year) RunningTotal ,year from
(
select count(*) count,year from #table group by year ) x
where year is not null
union all
select SUM(count) over (order by year) Runningtotal ,year from
(
select count(*) count,year from #table group by year ) x
where year is null
) y order by year
Here is an option which does not use analytic functions:
SELECT DISTINCT t1.col,
CASE WHEN t1.col IS NULL
THEN
(SELECT COUNT(*) FROM data t2 WHERE t1.year IS NULL AND t2.year IS NULL)
ELSE
(SELECT COUNT(t2.id) FROM data t2
WHERE t1.year = t2.year OR (t2.id <= t1.id AND t2.year IS NOT NULL))
END cnt
FROM data t1;
Method 1 :
select distinct year, f3.NB
from tmpxx f1
left outer join lateral
(
select count(*) NB from tmpxx f2
where f1.year is null and f2.year is null or
f1.year>=f2.year
) f3 on 1=1
Method 2:
select distinct year,
( select count(*) NB from tmpxx f2
where f1.year is null and f2.year is null or f1.year>=f2.year
) nb
from tmpxx f1

How to display duplicate items based on the quantity in SQL?

I have a table with the field content :
item qty
----- -----
tea 2
I want to display as below
item qty
---- ------
tea 1
tea 1
How to create SQL query like above ?
SAMPLE TABLE
CREATE TABLE #TEMP(ITEM VARCHAR(10),QTY INT)
INSERT INTO #TEMP
SELECT 'TEA',2
UNION ALL
SELECT 'COFFEE',3
If you have more than 1 type of item and you want to get the number of items, you can do with the following query.
QUERY
;WITH CTE AS
(
-- You will get each ITEM from your table
SELECT ITEM, QTY, 1 NEWQTY
FROM #TEMP
UNION ALL
-- Here it will loop and shows the repeated values of each ITEM
SELECT C1.ITEM,C1.QTY,NEWQTY + 1
FROM CTE C1
JOIN #TEMP T1 ON C1.item= T1.ITEM
WHERE C1.NEWQTY < T1.qty
)
SELECT ITEM,
1 QTY
FROM CTE
ORDER BY ITEM
Click here to view result
Use Recursive CTE
;WITH cte
AS (SELECT item,qty,1 num from yourtable
UNION ALL
SELECT item,
qty,
num + 1
FROM cte
WHERE num < qty)
SELECT item,
1 as Qty
FROM cte
or use Tally table. It will have a better performance when compared to Recursive CTE
;WITH e1(n) AS
(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), -- 10
e2(n)
AS (SELECT 1 FROM e1 CROSS JOIN e1 AS b) -- 10*10
--e3(n) AS (SELECT 1 FROM e2 CROSS JOIN e2 AS b), -- 100*100
SELECT a.item,
1 AS qty
FROM yourtable a
JOIN (SELECT n = Row_number()OVER (ORDER BY n)
FROM e2) b
ON b.n <= a.qty;
Note : Based on the Quantity you may have to increase the cross join of CTE's
Check here for more info

Is it possible to write a sql query that is grouped based on a running total of a column?

It would be easier to explain with an example. Suppose I wanted to get at most 5 items per group.
My input would be a table looking like this:
Item Count
A 2
A 3
A 3
B 4
B 4
B 5
C 1
And my desired output would look like this:
Item Count
A 5
A>5 3
B 4
B>5 9
C 1
An alternative output that I could also work with would be
Item Count RunningTotal
A 2 2
A 3 5
A 3 8
B 4 4
B 4 8
B 5 13
C 1 1
I can use ROW_NUMBER() to get the top X records in each group, however my requirement is to get the top X items for each group, not X records. My mind is drawing a blank as to how to do this.
declare #yourTable table (item char(1), [count] int)
insert into #yourTable
select 'A', 2 union all
select 'A', 3 union all
select 'A', 3 union all
select 'B', 4 union all
select 'B', 4 union all
select 'B', 5 union all
select 'C', 1
;with cte(item, count, row) as (
select *, row_number() over ( partition by item order by item, [count])
from #yourTable
)
select t1.Item, t1.Count, sum(t2.count) as RunningTotal from cte t1
join cte t2 on t1.item = t2.item and t2.row <= t1.row
group by t1.item, t1.count, t1.row
Result:
Item Count RunningTotal
---- ----------- ------------
A 2 2
A 3 5
A 3 8
B 4 4
B 4 8
B 5 13
C 1 1
Considering the clarifications from your comment, you should be able to produce the second kid of output from your post by running this query:
select t.Item
, t.Count
, (select sum(tt.count)
from mytable tt
where t.item=tt.item and (tt.creating_user_priority < t.creating_user_priority or
( tt.creating_user_priority = t.creating_user_priority and tt.created_date < t.createdDate))
) as RunningTotal
from mytable t
declare #yourTable table (item char(1), [count] int)
insert into #yourTable
select 'A', 2 union all
select 'A', 3 union all
select 'A', 3 union all
select 'B', 4 union all
select 'B', 4 union all
select 'B', 5 union all
select 'C', 1
;with cte(item, count, row) as (
select *, row_number() over ( partition by item order by item, [count])
from #yourTable
)
select t1.row, t1.Item, t1.Count, sum(t2.count) as RunningTotal
into #RunTotal
from cte t1
join cte t2 on t1.item = t2.item and t2.row <= t1.row
group by t1.item, t1.count, t1.row
alter table #RunTotal
add GrandTotal int
update rt
set GrandTotal = gt.Total
from #RunTotal rt
left join (
select Item, sum(Count) Total
from #RunTotal rt
group by Item) gt
on rt.Item = gt.Item
select Item, max(RunningTotal)
from #RunTotal
where RunningTotal <= 5
group by Item
union
select a.Item + '>5', total - five
from (
select Item, max(GrandTotal) total
from #RunTotal
where GrandTotal > 5
group by Item
) a
left join (
select Item, max(RunningTotal) five
from #RunTotal
where RunningTotal <= 5
group by Item
) b
on a.Item = b.Item
I've updated the accepted answer and got your desired result.
SELECT Item, SUM(Count)
FROM mytable t
GROUP BY Item
HAVING SUM(Count) <=5
UNION
SELECT Item, 5
FROM mytable t
GROUP BY Item
HAVING SUM(Count) >5
UNION
SELECT t2.Item + '>5', Sum(t2.Count) - 5
FROM mytable t2
GOUP BY Item
HAVING SUM(Count) > 5
ORDER BY 1, 2
select 'A' as Name, 2 as Cnt
into #tmp
union all select 'A',3
union all select 'A',3
union all select 'B',4
union all select 'B',4
union all select 'B',5
union all select 'C',1
select Name, case when sum(cnt) > 5 then 5 else sum(cnt) end Cnt
from #tmp
group by Name
union
select Name+'>5', sum(cnt)-5 Cnt
from #tmp
group by Name
having sum(cnt) > 5
Here is what I have so far. I know it's not complete but... this should be a good starting point.
I can get your second output by using a temp table and an update pass:
DECLARE #Data TABLE
(
ID INT IDENTITY(1,1) PRIMARY KEY
,Value VARCHAR(5)
,Number INT
,Total INT
)
INSERT INTO #Data (Value, Number) VALUES ('A',2)
INSERT INTO #Data (Value, Number) VALUES ('A',3)
INSERT INTO #Data (Value, Number) VALUES ('A',3)
INSERT INTO #Data (Value, Number) VALUES ('B',4)
INSERT INTO #Data (Value, Number) VALUES ('B',4)
INSERT INTO #Data (Value, Number) VALUES ('B',5)
INSERT INTO #Data (Value, Number) VALUES ('C',1)
DECLARE
#Value VARCHAR(5)
,#Count INT
UPDATE #Data
SET
#Count = Total = CASE WHEN Value = #Value THEN Number + #Count ELSE Number END
,#Value = Value
FROM #Data AS D
SELECT
Value
,Number
,Total
FROM #Data
There may be better ways, but this should work.

SQL Server query without using loops

I have a Payment table that looks a little like this:
Id (int identity)
CustomerId (int)
PaymentDate (SmallDateTime)
Now I want to write a query that will find those customers that have made three payments within a period of three months. Given the following data:
Id CustomerId PaymentDate (YYYY-MM-DD)
------------------------------------------
1 1 2010-01-01
2 1 2010-02-01
3 1 2010-03-01
4 1 2010-06-01
5 2 2010-04-01
6 2 2010-05-01
7 2 2010-06-01
8 2 2010-07-01
I would like to produce the following result:
CustomerId LastPaymentDateInPeriod
-------------------------------------
1 2010-03-01
2 2010-07-01
Where LastPaymentDateInPeriod is the PaymentDate with the highest value within a three-month period. If there is more than one three-month period for a given customer it would have to return the highest value from the most recent period (this is what I tried to illustrate for customer 2 in the above example). Note that three payments on three consecutive days would also meet the criteria. The payments just have to fall within a three-month period.
I know how to do this with a cursor and a lot of smaller queries but this is slow (and, I've come to understand, should only be a last resort). So do any of you SqlServer geniuses know how to do this with a query?
Thanks in advance.
This should do the job:
select
CustomerID,
max(LastPaymentDateInPeriod) as LastPaymentDateInPeriod
from
(
select
LastPaymentInPeriod.CustomerID,
LastPaymentInPeriod.PaymentDate as LastPaymentDateInPeriod
from Payment LastPaymentInPeriod
inner join Payment RelatedPayment on
LastPaymentInPeriod.CustomerID = RelatedPayment.CustomerID and
LastPaymentInPeriod.PaymentDate > RelatedPayment.PaymentDate and
datediff(m, RelatedPayment.PaymentDate, LastPaymentInPeriod.PaymentDate) < 3
group by
LastPaymentInPeriod.CustomerID,
LastPaymentInPeriod.PaymentDate
having
count(*) > 1
) as PaymentPeriods
group by
CustomerID
update: I've tested this now and it seems to work for #Martin's data
update2: If it's a requirement that Jan 31 and Apr 1 should be considered as less than 3 months apart then the DATEDIFF function call can be replaced with something like this:
create function fn_monthspan
(
#startdate datetime,
#enddate datetime
)
returns int
as
begin
return datediff(m, #startdate, #enddate) - case when datepart(d, #startdate) > datepart(d, #enddate) then 1 else 0 end
end
Bit of a rushed job as I'm off out.
declare #T TABLE
(
Id int,
CustomerId int,
PaymentDate SmallDateTime
)
insert into #T
SELECT 1, 1,'2010-01-01' UNION ALL
SELECT 2, 1,'2010-02-01' UNION ALL
SELECT 3, 1,'2010-03-01' UNION ALL
SELECT 4, 1,'2010-06-01' UNION ALL
SELECT 5, 2,'2010-04-01' UNION ALL
SELECT 6, 2,'2010-05-01' UNION ALL
SELECT 7, 2,'2010-06-01' UNION ALL
SELECT 8, 2,'2010-07-01'
;with CTE1 AS
(
SELECT Id, CustomerId, PaymentDate, ROW_NUMBER() OVER (PARTITION BY CustomerId ORDER BY PaymentDate) RN
FROM #T
), CTE2 AS
(
SELECT C1.Id, C1.CustomerId, MAX(C2.PaymentDate) AS LastPaymentDateInPeriod
FROM CTE1 C1 LEFT JOIN CTE1 C2 ON C1.CustomerId = C2.CustomerId AND C2.RN BETWEEN C1.RN AND C1.RN + 2 and C2.PaymentDate <=DATEADD(MONTH,3,C1.PaymentDate)
GROUP BY C1.Id, C1.CustomerId
HAVING COUNT(*)=3
)
SELECT CustomerId, MAX(LastPaymentDateInPeriod) LastPaymentDateInPeriod
FROM CTE2
GROUP BY CustomerId
This gives you all three payments within a 3 month span.
;
WITH CustomerPayments AS
(
SELECT 1 Id, 1 CustomerId, Convert (DateTime, '2010-01-01') PaymentDate
UNION SELECT 2, 1, '2010-02-01'
UNION SELECT 3, 1, '2010-03-01'
UNION SELECT 4, 1, '2010-06-01'
UNION SELECT 5, 2, '2010-04-01'
UNION SELECT 6, 2, '2010-05-01'
UNION SELECT 7, 2, '2010-06-01'
UNION SELECT 8, 2, '2010-07-01'
UNION SELECT 9, 3, '2010-07-01'
UNION SELECT 10, 3, '2010-07-01'
),
FirstPayment AS
(
SELECT Id, CustomerId, PaymentDate
FROM CustomerPayments
where Id IN
(
SELECT Min (Id) Id
FROM CustomerPayments
Group by CustomerId
)
),
SecondPayment AS
(
SELECT Id, CustomerId, PaymentDate
FROM CustomerPayments
where Id IN
(
SELECT Min (Id) Id
FROM CustomerPayments
WHERE ID NOT IN
(
SELECT ID
from FirstPayment
)
Group by CustomerId
)
),
ThirdPayment AS
(
SELECT Id, CustomerId, PaymentDate
FROM CustomerPayments
where Id IN
(
SELECT Min (Id) Id
FROM CustomerPayments
WHERE ID NOT IN
(
SELECT ID
from FirstPayment
UNION
SELECT ID
from SecondPayment
)
Group by CustomerId
)
)
SELECT *
FROM
FirstPayment FP
Left JOIN SecondPayment SP
ON FP.CustomerId = SP.CustomerId
Left JOIN ThirdPayment TP
ON SP.CustomerId = TP.CustomerId
WHERE 1=1
AND SP.PaymentDate IS NOT NULL
AND TP.PaymentDate IS NOT NULL
AND ABS (DATEDIFF (mm, SP.PaymentDate, TP.PaymentDate)) <3
I thought of:
select customerId,max(PaymentDate) from payment where customerId in
(select case when count(*)<3 then null else customerId end as customerId from payment
where paymentdate>dateadd(month,-3,getdate()) group by customerId)
group by customerId;