Summing Columns - sql

This is my sample table
create table #t (id int,qty int)
insert into #t values(1,100),(2,200),(3,150),(4,50)
I am trying to get this
id qty total_qty
1 100 100
2 200 300
3 150 450
4 50 500
this is not working
select a.id,a.qty,sum(b.qty) as total_qty from #t

You want a cumulative sum. In SQL Server 2012, you can do this directly:
select a.id, a.qty, sum(a.qty) over (order by a.id) as total_qty
from #t a;
In earlier versions of SQL Server, you can do this with a correlated subquery or non-equijoin. Below is an example:
select t.id, t.qty,
(select sum(t2.qty) from #t t2 where t2.id <= t.id) as total_qty
from #t t;
EDIT:
The expression sum(a.qty) over (order by a.id) is a window function that expresses a cumulative sum. The over is saying "do something for each row". What it does for each row is take the sum of a.qty. But, not just any sum, a sum that takes the ordering into account. The order by is what makes this a cumulative sum. (Without the order by clase, sum(a.qty) over () would return the sum of from all the rows, as an additional column in each row.)

Use a join !
select a.id,a.qty,sum(b.qty) as total_qty from #t a cross join #t b where b.id<=a.id
group by a.id,a.qty
order by a.id
see Demo

Related

How to minus current and previous value in SQL Server

Have one table, need to minus one column previous and current amount. Table value is below, need to write syntax for Cal-Amount column
Id Amount Cal-Amount
1 100 0
2 200 0
3 400 0
4 500 0
Cal-Amount calculation formula with sample value
Id Amount Cal-Amount
1 100 (0-100)=100
2 200 (100-200)=100
3 400 (200-400)=200
4 500 (400-500)=100
Need SQL syntax to minus column current and previous value
LAG is one option if you are using SQL Server 2012 or later:
SELECT
Id,
Amount,
LAG(Amount, 1, 0) OVER (ORDER BY Id) - Amount AS [Cal-Amount]
FROM yourTable;
If you are using an earlier version of SQL Server, then we can use a self join:
SELECT
Id,
Amount,
COALESCE(t2.Amount, 0) - t1.Amount AS [Cal-Amount]
FROM yourTable t1
LEFT JOIN yourTable t2
ON t1.Id = t2.Id + 1;
But note that the self join option might only work if the Id values are continuous. LAG is probably the most efficient way to do this, and is also robust to non sequential Id values, so long as the order is correct.
Well, Tim beat me to the lag(), so here's the old-school using join:
select t.Id,t.Amount,t.Amount-isnull(t2.Amount,0) AS [Cal-Amount]
from yourtable t
left join yourtable t2 on t.id=t2.id+1
SQL Server 2012 or newer:
Select
ID, Amount, [Cal-Amount] = Amount - LAG(Amount, 1, 0) OVER (ORDER BY Id)
From
table
or
Select
current.ID, Current.Amount, Current.Amount - Isnull(Prior.Amount, 0)
from
table current
left join
table prior on current.id - 1 = prior.id
You can use the LAG function if your SQL Server >= 2012
declare #t table (id int, amount1 int)
insert into #t
values (1, 100), (2, 200), (3, 400), (4, 500)
select
*, amount1 - LAG(amount1, 1, 0) over (order by id) as CalAmount
from
#t
You can also use apply :
select t.*, t.Amount - coalesce(tt.Amount, 0) as CalAmount
from table t outer apply (
select top (1) *
from table t1
where t1.id < t.id
order by t1.id desc
) tt;

aggregation according to different conditions on same column

I have a table #tbl like below, i need to write a query like if there are more than 3 records availble
for particular cid then avg(val of particular cid ) for particular cid should be dispalyed against each id and if there are less than
3 records availble for particular cid then avg(val of all records availble).
Please suggest.
declare #tbl table(id int, cid int, val float )
insert into #tbl
values(1,100,20),(2,100,30),(3,100,25),(4,100,31),(5,100,50),
(6,200,30),(7,200,30),(8,300,90)
Your description is not clear, but I believe you need windowed functions:
WITH cte AS (
SELECT *, COUNT(*) OVER(PARTITION BY cid) AS cnt
FROM #tbl
)
SELECT id, (SELECT AVG(val) FROM cte) AS Av
FROM cte
WHERE cnt <=3
UNION ALL
SELECT id, AVG(val) OVER(PARTITION BY cid) AS Av
FROM cte
WHERE cnt > 3
ORDER BY id;
DBFiddle Demo
EDIT:
SELECT id,
CASE WHEN COUNT(*) OVER(PARTITION BY cid) <= 3 THEN AVG(val) OVER()
ELSE AVG(val) OVER(PARTITION BY cid)
END
FROM #tbl
ORDER BY id;
DBFiddle Demo2
You can try with the following. First calculate the average for each Cid depending in it's number of occurences, then join each Cid with the Id to display all table.
;WITH CidAverages AS
(
SELECT
T.cid,
Average = CASE
WHEN COUNT(1) >= 3 THEN AVG(T.val)
ELSE (SELECT AVG(Val) FROM #tbl) END
FROM
#tbl AS T
GROUP BY
T.cid
)
SELECT
T.*,
C.Average
FROM
#tbl AS T
INNER JOIN CidAverages AS C ON T.cid = C.cid
Given the clarifications in comments, I am thinking this is the intention
declare #tbl table(id int, cid int, val float )
insert into #tbl
values(1,100,20),(2,100,30),(3,100,25),(4,100,31),(5,100,50),
(6,200,30),(7,200,30),(8,300,90);
select distinct
cid
, case
when count(*) over (partition by cid) > 3 then avg(val) over (partition by cid)
else avg (val) over (partition by 1)
end as avg
from #tbl;
http://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=fdf4c4457220ec64132de7452a034976
cid avg
100 31.2
200 38.25
300 38.25
There are a number of aspects of a query like this that when run at scale though are going to be pretty bad on the query plan, I'd want to test this at a larger scale and tune before using.
The description was not clear on what happened if it was exactly 3, it mentions 'more than 3' and 'less than 3' - within this code the 'more than' was used to determine which category it was in, and less than interpreted to mean 'less than or equal to 3'

SUM Column in SQL

I have a table in SQL Server, and I need to sum a column, like the example below:
CREATE TABLE B
(
ID int,
Qty int,
)
INSERT INTO B VALUES (1,2)
INSERT INTO B VALUES (2,7)
INSERT INTO B VALUES (3,2)
INSERT INTO B VALUES (4,11)
SELECT *, '' AS TotalQty FROM B
ORDER BY ID
In this example what I need is the column TotalQty give me the values like:
2
9
11
22
How can it be achieved?
You can use SUM in a co-related subquery or CROSS APPLY like this
Co-related Subquery
SELECT ID,(SELECT SUM(Qty) FROM B WHERE B.id <= C.id) FROM B as C
ORDER BY ID
Using CROSS APPLY
SELECT ID,D.Qty FROM B as C
CROSS APPLY
(
SELECT SUM(Qty) Qty
FROM B WHERE B.id <= C.id
)AS D
ORDER BY ID
Output
1 2
2 9
3 11
4 22
If you were using SQL Server 2012 or above, SUM() with Over() clause could have been used like this.
SELECT ID, SUM(Qty) OVER(ORDER BY ID ASC) FROM B as C
ORDER BY ID
Edit
Another way to do this in SQL Server 2008 is using Recursive CTE. Something like this.
Note: This method is based on the answer by Roman Pekar on this thread Calculate a Running Total in SQL Server. Based on his observation this would perform better than co related subquery and CROSS APPLY both
;WITH CTE as
(
SELECT ID,Qty,ROW_NUMBER()OVER(ORDER BY ID ASC) as rn
FROM B
), CTE_Running_Total as
(
SELECT Id,rn,Qty,Qty as Running_Total
FROM CTE
WHERE rn = 1
UNION ALL
SELECT C1.Id,C1.rn,C1.Qty,C1.Qty + C2.Running_Total as Running_Total
FROM CTE C1
INNER JOIN CTE_Running_Total C2
ON C1.rn = C2.rn + 1
)
SELECT *
FROM CTE_Running_Total
ORDER BY Id
OPTION (maxrecursion 0)

Accumulating in SQL

I have a query with results like ID, Value. What I want is to get the values in order of their ids and also calculate the accumulated value in another column. take a look at my simplified code:
declare #TempTable Table
(
ID int,
Value int
)
insert into #TempTable values
(1, 10),
(2, -15),
(3, 12),
(4, 18),
(5, 5)
select t1.ID, t1.Value, SUM(t2.Value) AccValue from #TempTable t1
inner join #TempTable t2 on t1.ID >= t2.ID
group by t1.ID, t1.Value
order by t1.ID
Result:
ID Value AccValue
1 10 10
2 -15 -5
3 12 7
4 18 25
5 5 30
What I have come up with, is to use inner join between the result and itself for that purpose. But for huge amount of data, it's clearly a low performance issue.
Is there any other alternative to do that?
In 2012 version, you can use:
SELECT
id,
Value,
AccValue = SUM(Value) OVER (ORDER BY ID
ROWS BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW)
FROM
#TempTable ;
For previous versions of SQL-Server, see my answer in this similar question: Recursive SQL- How can I get this table with a running total?, with a cursor solution.
Even better, follow the link to the great article by #Aaron Bertrand, that has a thorough test of various methods to calculate a running total: Best approaches for running totals – updated for SQL Server 2012
You can use recursion:
;WITH x AS
(
SELECT
[ID],
[Value],
bal=[Value]
FROM Table1
WHERE [ID] = 1
UNION ALL
SELECT
y.[ID],
y.[Value],
x.bal+(y.[Value]) as bal
FROM x INNER JOIN Table1 AS y
ON y.[ID] = x.[ID] + 1
)
SELECT
[ID],
[Value],
AccValue= bal
FROM x
order by ID
OPTION (MAXRECURSION 10000);
SQL FIDDLE
The generic SQL way to do this is with a correlated subquery (at least, I think that is the cleanest way):
select t.*,
(select sum(t2.value)
from #TempTable t2
where t2.ID <= t.ID
) AccValue
from #TempTable t
SQL Server 2012 has a cumulative sum function:
select t.*,
sum(t.value) over (order by t.id) as AccValue
from #TempTable t

SQL stored procedure to add up values and stop once the maximum has been reached

I would like to write a SQL query (SQL Server) that will return rows (in a given order) but only up to a given total. My client has paid me a given amount, and I want to return only those rows that are <= to that amount.
For example, if the client paid me $370, and the data in the table is
id amount
1 100
2 122
3 134
4 23
5 200
then I would like to return only rows 1, 2 and 3
This needs to be efficient, since there will be thousands of rows, so a for loop would not be ideal, I guess. Or is SQL Server efficient enough to optimise a stored proc with for loops?
Thanks in advance. Jim.
A couple of options are.
1) Triangular Join
SELECT *
FROM YourTable Y1
WHERE (SELECT SUM(amount)
FROM YourTable Y2
WHERE Y1.id >= Y2.id ) <= 370
2) Recursive CTE
WITH RecursiveCTE
AS (
SELECT TOP 1 id, amount, CAST(amount AS BIGINT) AS Total
FROM YourTable
ORDER BY id
UNION ALL
SELECT R.id, R.amount, R.Total
FROM (
SELECT T.*,
T.amount + Total AS Total,
rn = ROW_NUMBER() OVER (ORDER BY T.id)
FROM YourTable T
JOIN RecursiveCTE R
ON R.id < T.id
) R
WHERE R.rn = 1 AND Total <= 370
)
SELECT id, amount, Total
FROM RecursiveCTE
OPTION (MAXRECURSION 0);
The 2nd one will likely perform better.
In SQL Server 2012 you will be able to so something like
;WITH CTE AS
(
SELECT id,
amount,
SUM(amount) OVER(ORDER BY id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
AS RunningTotal
FROM YourTable
)
SELECT *
FROM CTE
WHERE RunningTotal <=370
Though there will probably be a more efficient way (to stop the scan as soon as the total is reached)
Straight-forward approach :
SELECT a.id, a.amount
FROM table1 a
INNER JOIN table1 b ON (b.id <=a.id)
GROUP BY a.id, a.amount
HAVING SUM(b.amount) <= 370
Unfortunately, it has N^2 performance issue.
something like this:
select id from
(
select t1.id, t1.amount, sum( t2.amount ) s
from tst t1, tst t2
where t2.id <= t1.id
group by t1.id, t1.amount
)
where s < 370