Sum one column and subtract over second column - sql-server

I want to display the subtraction of two columns. From the first column I need to get sum all value and substract with each value from the second column.
This is the table structure:
id | name | col1 | col2 | date
------------------------------------
432| xxx | 0 | 15 |2015-11-17
432| yyy | 10 | 30 |2015-11-19
432| zzz | 60 | 40 |2015-11-20
433| aaa | 0 | 60 |2015-11-17
433| bbb | 80 | 20 |2015-11-19
433| ccc | 60 | 10 |2015-11-20
Formula should go:
sum(col1) = 70 =>>> WHERE ID = 432
70 - col2 col3
-------------------------
=> 70 - 15 = 55
=> 70 - (30 + 15) = 25
=> 70 - (40 + 45) = -15
---------------------------
sum(col1) = 140 ===>> WHERE ID = 433
140 - col2 col3
-------------------------
=> 140 - 60 = 80
=> 140 - (60 + 20) = 60
=> 140 - (10 + 80) = 50
result is col3 and Output should be like as
id | name | col1 | col2 | col3 | date
-------------------------------------------
432| xxx | 0 | 15 | 55 | 2015-11-17
432| yyy | 10 | 30 | 25 | 2015-11-19
432| zzz | 60 | 40 | -15 | 2015-11-20
433| aaa | 0 | 60 | 80 | 2015-11-17
433| bbb | 80 | 20 | 60 | 2015-11-19
433| ccc | 60 | 10 | 50 | 2015-11-20
EDIT: What if I need the values ​​vary depending on the group as a 432 and 433 id column.

Schema Info
DECLARE #TEST TABLE
(
id INT,
name VARCHAR(10),
col1 INT,
col2 int
)
INSERT INTO #TEST VALUES
(432,'xxx',0, 15 ),
(432,'yyy',10, 30 ),
(432,'zzz',60, 40 ),
(433,'aaa',0, 60 ),
(433,'bbb',80, 20 ),
(433,'ccc',60, 10 )
Query
SELECT T.id ,
T.name ,
T.col1 ,
T.col2 ,
SUM(T.col1) OVER( PARTITION BY T.id ORDER BY T.id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
- SUM(T.col2) OVER ( PARTITION BY T.id ORDER BY T.id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS col3
FROM #TEST T;
Results
id | name | col1 | col2 | col3 |
---------------------------------
432 | xxx | 0 | 15 | 55 |
432 | yyy | 10 | 30 | 25 |
432 | zzz | 60 | 40 | -15 |
433 | aaa | 0 | 60 | 80 |
433 | bbb | 80 | 20 | 60 |
433 | ccc | 60 | 10 | 50 |
SQL Fiddle

This should work:
declare #total int = (select sum(col1) from Table)
select id, name, col1, col2, #total - (select sum(col2) from Table where date <= T.date) as col3, date from Table T
I was assuming you want to substract every time the previous total (based on the date). I hope this is OK.

You can use simple select query with cross apply
SELECT ID
,NAME
,COL1
,COL2
,A.C1 - (
SUM(COL2) OVER (
ORDER BY ID
)
) AS COL3
FROM TABLE1 T1
CROSS APPLY (
SELECT SUM(COL1) AS C1
FROM TABLE1 T2
) A

You can use two subqueries in SELECT fields list.
With the first you'll get a sum of all rows of your table named yourtable, in the second you'll get a sum of all rows before the current. So you can subtract two values.
Try this:
SELECT T.id, T.name, T.col1, T.col2,
ISNULL(
(SELECT SUM(T2.col1) FROM yourtable T2)
,0) -
ISNULL(
(SELECT SUM(T3.col2) FROM yourtable T3
WHERE T3.id <= T.id)
,0) as col3,
t.date
FROM yourtable T
Go on Sql fiddle example
EDIT
SELECT T.id, T.name, T.col1, T.col2,
ISNULL(
(SELECT SUM(T2.col1) FROM yourtable T2 where T2.id = T.id)
,0) -
ISNULL(
(SELECT SUM(T3.col2) FROM yourtable T3
WHERE T3.id = T.id AND T3.date <= T.date)
,0) as col3,
t.date
FROM yourtable T
Go on Sql Fiddle edited example
Pay attention: A deep edit can be a different question. Two queries are differents
Pay attention: it's no good a field named ID with repeated values

Related

How to use last_value with group by with count in SQL Server?

I have table like:
name | timeStamp | previousValue | newValue
--------+---------------+-------------------+------------
Mark | 13.12.2020 | 123 | 155
Mark | 12.12.2020 | 123 | 12
Tom | 14.12.2020 | 123 | 534
Mark | 12.12.2020 | 123 | 31
Tom | 11.12.2020 | 123 | 84
Mark | 19.12.2020 | 123 | 33
Mark | 17.12.2020 | 123 | 96
John | 22.12.2020 | 123 | 69
John | 19.12.2020 | 123 | 33
I'd like to mix last_value, count (*) and group to get this result:
name | count | lastValue
--------+-----------+-------------
Mark | 5 | 33
Tom | 2 | 534
John | 2 | 69
This part:
select name, count(*)
from table
group by name
returns table:
name | count
--------+---------
Mark | 5
Tom | 2
John | 2
but I have to add the last value for each name.
How to do it?
Best regards!
LAST_VALUE is a windowed function, so you'll need to get that value first, and then aggregate:
WITH CTE AS(
SELECT [name],
[timeStamp], --This is a poor choice for a column's name. timestamp is a (deprecated) synonym of rowversion, and a rowversion is not a date and time value
previousValue,
newValue,
LAST_VALUE(newValue) OVER (PARTITION BY [name] ORDER BY [timeStamp] ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS lastValue
FROM dbo.YourTable)
SELECT [Name],
COUNT(*) AS [count],
lastValue
FROM CTE
GROUP BY [Name],
lastValue;
I got a solution that works, but here's another one:
SELECT
[name], COUNT([name]), [lastValue]
FROM (
SELECT
[name], FIRST_VALUE([newValue]) OVER (PARTITION BY [name] ORDER BY TimeStamp DESC ROWS UNBOUNDED PRECEDING) AS [lastValue]
FROM [table]
) xyz GROUP BY [name], [lastValue]
Keep well!

SQL - How to combine rows

I have the following table which looks at calls and attendances. I got this by using union all on a 'calls' and 'attendances' tables and then used row number on the ID and ordered by dates.
Table1:
Type | ID | Call/AttendanceDate | RowNum
------------|----|---------------------|--------
Attendance | 12 | 2018-09-16 10:11:00 | 82
Call | 12 | 2018-09-18 14:11:47 | 83
Call | 12 | 2018-10-02 17:26:13 | 84
Call | 12 | 2018-10-05 14:58:31 | 85
Attendance | 12 | 2018-10-13 01:41:00 | 86
Call | 12 | 2018-10-13 02:39:12 | 87
Call | 12 | 2018-10-13 04:31:22 | 88
Attendance | 12 | 2018-10-13 14:29:00 | 89
Call | 12 | 2018-10-13 14:59:19 | 90
Attendance | 12 | 2018-10-15 15:50:00 | 91
The code I used for this is:
WITH CTE1 AS
(
SELECT 'Call' as [Type], ID, CallDate AS Date1
FROM CallsTable
UNION ALL
SELECT 'Attendance' as [Type], ID, AttendanceDate AS Date2
FROM AttendanceTable]
)
,CTE2 AS
(
SELECT [Type], Date1, ID, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Date1 ASC) AS RowNum
FROM CTE1
)
--------------------------------OUTPUT--------------------------------
SELECT a.[Type], a.ID, a.Date1, a.RowNum
FROM CTE2 a
JOIN CTE2 b
ON a.ID= b.ID
AND a.RowNum = b.RowNum + 1
WHERE a.ID = '12'
ORDER BY ID, RowNum
I want to modify this to look like the below output, so that whenever an attendance follows a call, it should be in the same row.
Table2:
Type | ID | CallDate | RowNum | Type | AttendanceDate | RowNum
------|----|------------------|--------|------------|------------------|--------
NULL | 12 | NULL | NULL | Attendance | 16/09/2018 10:11 | 82
Call | 12 | 18/09/2018 14:11 | 83 | NULL | NULL | NULL
Call | 12 | 02/10/2018 17:26 | 84 | NULL | NULL | NULL
Call | 12 | 05/10/2018 14:58 | 85 | Attendance | 13/10/2018 01:41 | 86
Call | 12 | 13/10/2018 02:39 | 87 | NULL | NULL | NULL
Call | 12 | 13/10/2018 04:31 | 88 | Attendance | 13/10/2018 14:29 | 89
Call | 12 | 13/10/2018 14:59 | 90 | Attendance | 15/10/2018 15:50 | 91
Is this possible? What code could I use?
Use FULL JOIN
SELECT
*
FROM
(SELECT * FROM CTE2 WHERE Type = 'CALL') A
FULL JOIN
(SELECT * FROM CTE2 WHERE Type = 'ATTENDANCE') B
ON A.ID = B.ID AND A.RowNum = B.RowNum - 1
You can use APPLY :
SELECT C.[Type], C.ID, C.CallDate, C.RowNum,
(CASE WHEN C2.RowNum - C.RowNum = 1 THEN C2.[TYPE] end) [TYPE],
(CASE WHEN C2.RowNum - C.RowNum = 1 THEN C2.CallDate end) AttendanceDate,
(CASE WHEN C2.RowNum - C.RowNum = 1 THEN C2.RowNum end) RowNum
FROM CTE2 C OUTER APPLY
(SELECT TOP (1) C2.*
FROM CTE2 C2
WHERE C2.ID = C.ID AND C2.[Type] = 'Attendance' AND C2.RowNum > C.RowNum
ORDER BY C2.RowNum
) C2
WHERE C.ID = 12 AND C.[Type] = 'Call';
Not as elegant, but works for me, a table valued function
alter FUNCTION GetCallActivity()
RETURNS #activityTable TABLE
(
call_type varchar(16),
call_id int,
call_date datetime,
call_rownum int,
atnd_type varchar(16),
atnd_id int,
atnd_date datetime,
atnd_rownum int
)
AS
BEGIN
-- initialize the return table
insert into #activityTable
(call_type, call_id, call_date, call_rownum )
select a.type, a.id, a.activity_date, a.rownum
from stack_calls a
where a.type = 'Call'
order by a.activity_date;
-- match to the attendence recs to the call recs
update #activityTable
set atnd_type = b.type,
atnd_id = b.id,
atnd_date = b.activity_date,
atnd_rownum = b.rownum
from stack_calls b
join #activityTable a
on b.rownum = a.call_rownum + 1
where b.type = 'Attendance';
-- deal with the edge cases
insert into #activityTable
( atnd_type, atnd_id, atnd_date, atnd_rownum )
select x.type,
x.id,
x.activity_date,
x.rownum
from
(
select a.type,
a.id,
a.activity_date,
a.rownum,
lag(a.type, 1) over (order by a.activity_date) as prev_type
from stack_calls a
where a.type = 'Attendance'
) x
where x.prev_type is null
RETURN
END
GO

Grouping by a same range of multiple values with sum and counts in SQL

I want to group different colons in same range by row. Example:
Amount1 | Amount2
------------------------
20,00 | 30,00
35,00 | 32,00
12,00 | 51,00
101,00 | 100,00
Here result should be;
Range |TotalAmount1 |TotalAmount2 | CountAmount1 | CountAmount2 | RateOfCountAmount1
-----------------------------------------------------------------------------
0-50 | 67,00 | 62,00 | 3 | 1 | %75
50-100 | 0,00 | 151,00 | 0 | 2 | %0
100+ | 101,00 | 0,00 | 1 | 0 | %25
Total | 168,00 | 213,00 | 4 | 3 | %100
Here is the example : http://sqlfiddle.com/#!9/05fd3
you can query like this
;with cte as (
select case when amount1 < 50 then '0-50'
when amount1 between 50.01 and 100 then '50-100'
when amount1 > 100 then '100+' end as rngamt1,
case when amount2 < 50 then '0-50'
when amount2 between 5.01 and 100 then '50-100'
when amount2 > 100 then '100+' end as rngamt2,
* from amounts
), cte2 as (select coalesce(rngamt1, rngamt2) as [Range], isnull(a.TotalAmount1,0) as TotalAmount1, isnull(b.TotalAmount2, 0) as TotalAmount2, isnull( a.TotalCount1 , 0) as TotalCount1, isnull(b.TotalCount2, 0) as Totalcount2 from
(select rngamt1, sum(amount1) TotalAmount1, count(amount1) TotalCount1 from cte c
group by rngamt1) a
full join
(select rngamt2, sum(amount2) TotalAmount2, count(amount2) TotalCount2 from cte c
group by rngamt2) b
on a.rngamt1 = b.rngamt2
)
select *, (TotalCount1 * 100 )/sum(TotalCount1) over () as RateCount1
from cte2
union
select 'Total' as [Range], sum(TotalAmount1) as TotalAmount1, sum(totalAmount2) as TotalAmount2,
sum(TotalCount1) as TotalCount1, sum(Totalcount2) as TotalCount2, (sum(TotalCount1)*100)/Sum(TotalCount1) as RateCount1 from cte2

How to find first positive value and third consecutive positive values in SQL?

I have tried using ROW_NUMBER but haven't quite got it. Any ideas on the best way to achieve this.
I am looking to find:
- What month did they first cash flow.
- What month did they average 3 months cash flow.
Sample Data:
Office ,Balance & Year month
------------------------------
| Office | Balance | YrMo |
| 12 | 111 | 201510 |
| 12 | 222 | 201511 |
| 12 | -444 | 201512 |
| 12 | -777 | 201601 |
| 12 | 555 | 201602 |
| 12 | 666 | 201603 |
| 12 | -888 | 201604 |
| 12 | 777 | 201605 |
| 40 | -555 | 201510 |
| 40 | -200 | 201511 |
| 40 | 0 | 201512 |
| 40 | 100 | 201601 |
| 40 | -555 | 201602 |
| 40 | 666 | 201603 |
| 40 | 777 | 201604 |
| 40 | 888 | 201605 |
| 40 | 999 | 201606 |
The first Positive Balances would be:
-office 12 , Balance 111 , YrMo 201510
-office 40 , Balance 100 , YrMo 201601
The first month the office averaged 3 positive balance:
-office 40 , Balance 999 , YrMo 201606
Here is the #test table script:
IF OBJECT_ID('tempdb..#test') IS NOT NULL
DROP TABLE #test
GO
CREATE TABLE #test (office INT , Balance INT, YrMo INT ) ;
INSERT INTO #test VALUES (12 , 111 , 201510) ;
INSERT INTO #test VALUES (12 , 222 , 201511) ;
INSERT INTO #test VALUES (12 , -444 , 201512) ;
INSERT INTO #test VALUES (12 , -777 , 201601) ;
INSERT INTO #test VALUES (12 , 555 , 201602) ;
INSERT INTO #test VALUES (12 , 666 , 201603) ;
INSERT INTO #test VALUES (12 , -888 , 201604) ;
INSERT INTO #test VALUES (12 , 777 , 201605) ;
INSERT INTO #test VALUES (40 , -555 , 201510) ;
INSERT INTO #test VALUES (40 , -200 , 201511) ;
INSERT INTO #test VALUES (40 , 0 , 201512) ;
INSERT INTO #test VALUES (40 , 100 , 201601) ;
INSERT INTO #test VALUES (40 , -555 , 201602) ;
INSERT INTO #test VALUES (40 , 666 , 201603) ;
INSERT INTO #test VALUES (40, 777 , 201604) ;
INSERT INTO #test VALUES (40 , 888 , 201605) ;
INSERT INTO #test VALUES (40 , 999 , 201606) ;
Thanks in advance
;with cteFirst as (
Select *
,FirstPos=Row_Number() over (Partition By Office Order By YrMo,Balance) from #Test Where Balance>0
),
cteCons as (
Select *
,TestCons=Lag(IIf(IIf(sign(balance)=1,1,0)=1,1,0),1,0) over (Partition By Office Order By YrMo)
+Lag(IIf(IIf(sign(balance)=1,1,0)=1,1,0),2,0) over (Partition By Office Order By YrMo)
+Lag(IIf(IIf(sign(balance)=1,1,0)=1,1,0),3,0) over (Partition By Office Order By YrMo)
from #Test
)
Select *,Status='First Positive' from cteFirst where FirstPos=1
Union All
Select *,Status='3 Cons' from cteCons where TestCons=3
Return
office Balance YrMo FirstPos Status
12 111 201510 1 First Positive
40 100 201601 1 First Positive
40 999 201606 3 3 Consequtive
I added another example. This one traps gaps in Dates.
If you want to see all the flags and how the data progresses, remove
the -- before Select * from cteFinal Order by Office,YrMo
I added another office which has 3 consecutive positive balances, but the months are NOT (no June). Notice Office 99 fails to meet the consecutive months criteria
office Balance YrMo
99 199 201605
99 299 201607
99 399 201608
The updated query is as follows
;with cteBase as (
Select *
,RowNr = Row_Number() over (Partition By Office Order By Office,YrMo,Balance)
,MthSeq = case when cast(YrMo as int)-Lag(YrMo,1,YrMo-1) over (Partition By Office Order By YrMo) in (1,89) then 1 else 0 end
,IsPos = IIf(Balance>0,1,null)
from #Test
)
,cteFinal as (
Select *
,PosRowNr = min(RowNr*IsPos) over (Partition By Office Order By RowNr)
,TestCons = MthSeq * (
Lag(IIf(IIf(sign(balance)=1,1,0)=1,1,0),1,0) over (Partition By Office Order By YrMo)
+Lag(IIf(IIf(sign(balance)=1,1,0)=1,1,0),2,0) over (Partition By Office Order By YrMo)
+Lag(IIf(IIf(sign(balance)=1,1,0)=1,1,0),3,0) over (Partition By Office Order By YrMo)
)
From cteBase
)
--Select * from cteFinal Order by Office,YrMo
Select Office
,Balance
,YrMo
,Status = IIf(RowNr=PosRowNr,'First Positive','')+IIf(TestCons=3,'Consecutive Months','')
From cteFinal
Where TestCons=3 or RowNr=PosRowNr
Order by Status Desc,Office,YrMo
The Results are
Office Balance YrMo Status
12 111 201510 First Positive
40 100 201601 First Positive
99 199 201605 First Positive
40 999 201606 Consecutive Months

The highest value from list-distinct

Can anyone help me with query, I have table
vendorid, agreementid, sales
12001 1004 700
5291 1004 20576
7596 1004 1908
45 103 345
41 103 9087
what is the goal ?
when agreemtneid >1 then show me data when sales is the highest
vendorid agreementid sales
5291 1004 20576
41 103 9087
Any ideas ?
Thx
Well you could try using a CTE and ROW_NUMBER something like
;WITH Vals AS (
SELECT *, ROW_NUMBER() OVER(PARTITION BY AgreementID ORDER BY Sales DESC) RowID
FROM MyTable
WHERE AgreementID > 1
)
SELECT *
FROM Vals
WHERE RowID = 1
This will avoid you returning multiple records with the same sale.
If that was OK you could try something like
SELECT *
FROM MyTable mt INNER JOIN
(
SELECT AgreementID, MAX(Sales) MaxSales
FROM MyTable
WHERE AgreementID > 1
) MaxVals ON mt.AgreementID = MaxVals.AgreementID AND mt.Sales = MaxVals.MaxSales
SELECT TOP 1 WITH TIES *
FROM MyTable
ORDER BY DENSE_RANK() OVER(PARTITION BY agreementid ORDER BY SIGN (SIGN (agreementid - 2) + 1) * sales DESC)
Explanation
We break table MyTable into partitions by agreementid.
For each partition we construct a ranking or its rows.
If agreementid is greater than 1 ranking will be equal to ORDER BY sales DESC.
Otherwise ranking for every single row in partition will be the same: ORDER BY 0 DESC.
See how it looks like:
SELECT *
, SIGN (SIGN (agreementid - 2) + 1) * sales AS x
, DENSE_RANK() OVER(PARTITION BY agreementid ORDER BY SIGN (SIGN (agreementid - 2) + 1) * sales DESC) AS rnk
FROM MyTable
+----------+-------------+-------+-------+-----+
| vendorid | agreementid | sales | x | rnk |
+----------|-------------|-------+-------+-----+
| 0 | 0 | 3 | 0 | 1 |
| -1 | 0 | 7 | 0 | 1 |
| 0 | 1 | 3 | 0 | 1 |
| -1 | 1 | 7 | 0 | 1 |
| 41 | 103 | 9087 | 9087 | 1 |
| 45 | 103 | 345 | 345 | 2 |
| 5291 | 1004 | 20576 | 20576 | 1 |
| 7596 | 1004 | 1908 | 1908 | 2 |
| 12001 | 1004 | 700 | 700 | 3 |
+----------+-------------+-------+-------+-----+
Then using TOP 1 WITH TIES construction we leave only rows where rnk equals 1.
you can try like this.
SELECT TOP 1 sales FROM MyTable WHERE agreemtneid > 1 ORDER BY sales DESC
I really do not know the business logic behind agreement_id > 1. It looks to me you want the max sales (with ties) by agreement id regardless of vendor_id.
First, lets create a simple sample database.
-- Sample table
create table #sales
(
vendor_id int,
agreement_id int,
sales_amt money
);
-- Sample data
insert into #sales values
(12001, 1004, 700),
(5291, 1004, 20576),
(7596, 1004, 1908),
(45, 103, 345),
(41, 103, 9087);
Second, let's solve this problem using a common table expression to get a result set that has each row paired with the max sales by agreement id.
The select statement just applies the business logic to filter the data to get your answer.
-- CTE = max sales for each agreement id
;
with cte_sales as
(
select
vendor_id,
agreement_id,
sales_amt,
max(sales_amt) OVER(PARTITION BY agreement_id) AS max_sales
from
#sales
)
-- Filter by your business logic
select * from cte_sales where sales_amt = max_sales and agreement_id > 1;
The screen shot below shows the exact result you wanted.

Resources