Subtraction for two rows - sql-server

I have following table named table1 in SQL Server.
id value
1 10
2 100
3 20
4 40
5 50
When i execute query following query it gives me result of 110 which is expected
SELECT SUM(value) from table1 where id in (1,2)
What i want is opposite of SUM means the output should be 90 or -90.
i know this can be done by writing following query
select ((SELECT value from table1 where id in (1)) - (SELECT value from table1 where id in (2)) )
but is there any simplified way to do this(something like SUM function).

Fiddle demo using Sum() with Case:
Declare #SubId int =1
--To get -90 or +90 change the value of #SubId from 1 to 2
Select Sum(Case When Id = #SubId Then value Else -1*Value End) Total
From Table1
Where Id in (1,2);

Depending on whether you want result to be non-negative or non-positive, you can switch MIN and MAX in the following statement:
select max(value) - min(value)
from table1
where id in (1,2)

Related

SQL Server, subset a table and create new column based on condition

If I have a table in SQL as:
id
code
1
6
1
8
1
4
2
3
2
7
2
4
3
7
3
6
3
7
What I need to do, logically is:
Get the top row of each group when grouped by id, ordered by code
Create a new column to show if the code column contained a 7 anywhere, within the group
Desired result:
id
code
c7
1
4
N
2
3
Y
3
6
Y
I think it needs a "CASE WHEN" statement in the SELECT, but have not worked it out. What query can I execute to get this?
Seems like you can use a MIN and a conditional aggregate:
SELECT id,
MIN(Code) AS Code,
CASE WHEN COUNT(CASE code WHEN 7 THEN 1 END) > 0 THEN 'Y' ELSE 'N' END AS C7
FROM dbo.YourTable
GROUP BY id;
There is probably a better way to do this, but what comes to mind is that first you have to partition the table to get the top one based on whatever that criteria is, then join back against itself to find the ones with the 7
declare #table1 table (id int not null, code int not null)
insert into #table1 (id, code)
values
(1,6),
(1,8),
(1,4),
(2,3),
(2,7),
(2,4),
(3,7),
(3,6),
(3,7)
select id, code, c7
from (
select t.id ,t.code
,(CASE WHEN c.id is null then 'N' else 'Y' END) as c7
,ROW_NUMBER() OVER (PARTITION BY t.id order by t.code) AS p
from #table1 t
left outer join (
select id, code, 'Y' as c7
from #table1
where code = 7) c on c.id = t.id
) sorted
where sorted.p = 1

Create new incremental grouping column based on if logic from group, rank, and category columns

I'm trying to sum totals together that goes beyond a basic "group by" or "case" statement.
Here's an example datasets:
Amt Cust_id Ranking PlanType
10 1 1 Term
6 1 2 Variable
8 1 3 Variable
7 1 4 Variable
12 1 5 Term
6 1 6 Variable
10 1 7 Variable
The objective is to return the max sum where the plan type is 'Variable' and
the Ranking numbers are adjacent to each other.
So the answer to the example would be the sum of rows 2-4 which returns 21.
The answer is not the sum of all variable plan types, because row 5 is a 'Term' which breaks it apart.
So I'd like to end with a dataset like below to handle multiple groups of customers:
Amt Cust_ID
21 1
30 2
45 3
Here's where I'm stuck which returns wrong answer:
Create Table #tb (Amt INT, Cust_id TINYINT, Ranking INT, PlanType
VARCHAR(10))
INSERT INTO #tb
VALUES (10,1,1,'Term'),
(6,1,2,'Variable'),
(8,1,3,'Variable'),
(7,1,4,'Variable'),
(12,1,5,'Term'),
(6,1,6,'Variable'),
(10,1,7,'Variable'),
(10,2,1,'Term'),
(6,2,2,'Variable'),
(7,2,4,'Variable'),
(12,2,5,'Term'),
(6,2,6,'Variable'),
(50,2,7,'Variable')
select
( SELECT SUM(Amt) FROM #tb as t2
WHERE t2.Cust_ID=t1.Cust_ID AND t2.Ranking<=t1.Ranking AND
t2.PlanType='Variable') RollingAmt
,Cust_ID, Ranking, Amt, PlanType
from #tb as t1
order by Cust_ID, Ranking
The query runs a rolling sum ordered by "Ranking" where PlanType = 'Variable'. Unfortunately it runs a rolling sum of all "Variable"'s together. I need it to not do that.
If it runs into a PlanType "Term" it needs to start over its sum within each group.
In order to do this you need to use a gaps-and-islands technique to generate a "group id" based on consecutive runs of the same PlanType, then you can sum and sort based on that new group id.
Try this:
DECLARE #data TABLE (Amt INT, Cust_id TINYINT, Ranking INT, PlanType VARCHAR(10))
INSERT INTO #data
VALUES (10,1,1,'Term'),
(6,1,2,'Variable'),
(8,1,3,'Variable'),
(7,1,4,'Variable'),
(12,1,5,'Term'),
(6,1,6,'Variable'),
(10,1,7,'Variable'),
(10,2,1,'Term'),
(6,2,2,'Variable'),
(7,2,4,'Variable'),
(12,2,5,'Term'),
(6,2,6,'Variable'),
(50,2,7,'Variable')
;WITH X AS
(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY Cust_id,PlanType ORDER BY Ranking)
- ROW_NUMBER() OVER(PARTITION BY Cust_id ORDER BY Ranking) groupID /* Assign a groupID to consecutive runs of PlanTypes by Cust_id */
FROM #data
), Y AS
(
SELECT *, SUM(Amt) OVER(PARTITION BY Cust_id,groupID) AS AmtSum /* Sum Amt by Cust/groupID */
FROM X
WHERE PlanType='Variable'
), Z AS
(
SELECT *, ROW_NUMBER() OVER(PARTITION BY Cust_id ORDER BY AmtSum DESC) AS RN /* Assign a row number (1) to highest AmtSum by Cust */
FROM Y
)
SELECT AmtSum, Cust_id
FROM Z
WHERE RN=1 /* Only select RN=1 to get highest value by cust_id/groupId */
If you are curious about how this all works, you can comment the last SELECT and do SELECT * FROM X then SELECT * FROM Y etc, to see what each step does along the way; but only one SELECT can follow the entire CTE structure.

Calculate minimum total value and delete all other rows from Table

Here's my table.
Table MyTable
-------------
ID Distance1 Cost1 Distance2 Cost2 Distance3 Cost3
1 711.9 6196.90432379846 NULL NULL NULL NULL
2 672.4 7316.33 NULL NULL 103.5 900.941 8217.271
3 787.7 8570.9 252 2193.59 NULL NULL
What I want is, find out row which has minimum total (Cost1+Cost2+Cost3). Keep that row and delete everything else.
So far I have achieved this. This gives me row which has minimum total value.
select TOP 1 *, ISNULL(Cost1, 0 )+ISNULL(Cost2, 0 )+ISNULL(Cost3, 0 ) as TotalCost from MyTable order by TotalCost
I also want to delete other rows. Is there anyway I can do this in one statement.
Use CTE and Row_Number window function to delete
;with cte as
(
select Row_number()over(order by ISNULL(Cost1, 0)+ISNULL(Cost2, 0 )+ISNULL(Cost3, 0)) rn,*
from MyTable
)
Delete from cte where rn > 1

Get X and Y coordinates of each cell from a table and transform into a new table in sql server

I'm trying to build a query for a matrix table which has a schema like this:
ID 1 2 3
----------- ----------- ----------- -----------
1 13 32 55
2 30 75 129
I want to get the position of a cell according to its coordinate (row number and column number) to create a new table that has the fields row_num, col_num and value
In the example given, this query should return:
row_num col_num value
------- ------- -----------
1 1 13
2 1 30
1 2 32
2 2 75
1 3 55
2 3 129
The query must obtain the value of each cell and return its position X and Y.
I have tried different approach without success. I tried to use UNPIVOT, but it is not showing me the correct information.
Any suggestions are greatly appreciated!
UPDATED:
I added a column whit row number
You need to unpivot the data and generate row number. Here is one way using CROSS APPLY
select Row_number()over(partition by col_num order by ID) as row_num,
col_num,
value
from yourtable
cross apply(values ([1],'1'),([2],'2'),([3],'3')) tc (value,col_num)
To do this using unpivot try this way
select Id,col_num,value
from Yourtable
unpivot
(
value
for col_num in ([1], [2], [3])
) u;
Assuming that you do have a column that specifies the ordering, you can do the calculation as:
select dense_rank() over (order by ??) as row_num,
v.col_num, v.val
from matrix m cross apply
(values (m.col1, 1), (m.col2, 2), (m.col3, 3)
) v(val, col_num);
SQL tables represent unordered sets. The ?? is for whatever column specifies the ordering. If it is already row_num, then you don't need the dense_rank().
For the Updated Question
Declare #YourTable table (ID int,[1] int,[2] int,[3] int)
Insert Into #YourTable values
(1,13,32,55),
(2,30,75,129)
Select A.ID as row_nu,
,B.*
From #YourTable A
Cross Apply (
values (1,A.[1])
,(2,A.[2])
,(3,A.[3])
) B (col_num,value)
Order by B.col_num,A.ID
Returns
row_num col_num value
1 1 13
2 1 30
1 2 32
2 2 75
1 3 55
2 3 129
EDIT - As requested UnPivot
Select ID as row_num ,col_num,value
From #Yourtable
UnPivot (Value for col_num in ([1], [2], [3]) ) B
Order By 2,1

List all negative numbers and minimum positive number from table

This an example table and I want to get all negative numbers and minimum positive number (0 included) for per id
declare #tbl table(id INT, value decimal, someData varchar(10))
insert into #tbl
values(1,-3,123),(1,-2,234),(1,-1,345),(1,3,456),(1,4,567),(2,-4,678),(2,-2,789),(2,1,890),(2,2,135),(3,-5,246),(3,10,357)
select * from #tbl where value < 0 union
select id, min(value),someData from #tbl WHERE value > 0 group by id,somedata
I am trying to find a solution by separating minuses and pluses. but because of someData I cannot group by them as I need.
desired result is:
1 -3 123
1 -2 234
1 -1 345
1 3 456
2 -4 678
2 -2 789
2 1 890
3 -5 246
3 10 357
Also its a long working query so I dont want to make double select. Is it possible to make it in one select?
This should work:
;With separated as (
select *,ROW_NUMBER() OVER (PARTITION BY SIGN(value),id ORDER BY value) as rn
from #tbl
)
select * from separated where SIGN(value) < 0 or rn=1
You haven't said what should happen if value is 0 so the above may or may not be correct if your data contains some.
SIGN is a little-used but occasionally useful function in SQL Server, returning -1, 0 or +1 and allowing us to easily partition the groups of numbers into 3 groups.
Using a union to combine the conditions is likely easiest. But to select the row with the minimum positive value requires an sub-query, and the assumption that ID is a unique identifier:
select id,value,somdata from #tbl where value < 0
union
select id,value,somdata from #tbl
where value = (select min(value) from #tbl where value > 0)
(That won't include rows with a zero value, but the question doesn't say which side they should fall.)
Attack it as a single select with an OR where clause, using a subquery to get the minimum positive number:
select * from #tbl
where value < 0
or value = (
select min(value)
from #tbl
where value > 0)
using Window function u can get minimum values. and use UNION to combine the results. TRY THIS..
;WITH CTE
AS (SELECT Row_number() OVER (PARTITION BY ID ORDER BY value) RN, *
FROM #tbl
WHERE value > 0)
SELECT *
FROM #tbl
WHERE value < 0
UNION ALL
SELECT ID,value,someData
FROM CTE
WHERE RN = 1

Resources