List all negative numbers and minimum positive number from table - sql-server

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

Related

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.

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

Binary operator OR in TSQL?

What I 'm trying to achieve is to count occurrences in a sort of time line, considering overlapping events as a single one, starting from a field like this and using TSQL:
Pattern (JSON array of couple of values indicating
the start day and the duration of the event)
----------------------------------------------------
[[0,22],[0,24],[18,10],[30,3]]
----------------------------------------------------
For this example the result expected should be 30
What i need is a TSQL function to obtain this number...
Even If I'm not sure it's the right path to follow, I'm trying to simulate a sort of BINARY OR between rows of my dataset.
After some trying I managed to turn my dataset into something like this:
start | length | pattern
----------------------------------------------------
0 | 22 | 1111111111111111111111
0 | 24 | 111111111111111111111111
18 | 10 | 000000000000000001111111111
30 | 3 | 000000000000000000000000000000111
----------------------------------------------------
But now I dont' know how to proceed in TSQL =)
a solution as i said could be a binary OR between the "pattern" fields to obtain something like this:
1111111111111111111111...........
111111111111111111111111.........
000000000000000001111111111......
000000000000000000000000000000111
--------------------------------------
111111111111111111111111111000111
Is it possible to do it in TSQL?
Maybe i'm just complicating things here do you have other ideas?
DO NOT forget I just need the result number!!!
Thank you all
Only the total days that an event occurs needs to be returned.
But I was wondering how hard it would be to actually calculate that binary OR'd pattern.
declare #T table (start int, length int);
insert into #T values
(0,22),
(0,24),
(18,10),
(30,3);
WITH
DIGITS as (
select n
from (values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) D(n)
),
NUMBERS as (
select (10*d2.n + d1.n) as n
from DIGITS d1, DIGITS d2
where (10*d2.n + d1.n) < (select max(start+length) from #T)
),
CALC as (
select N.n, max(case when N.n between IIF(T.start>0,T.start,1) and IIF(T.start>0,T.start,1)+T.length-1 then 1 else 0 end) as ranged
from #T T
cross apply NUMBERS N
group by N.n
)
select SUM(c.ranged) as total,
stuff(
(
select ranged as 'text()'
from CALC
order by n
for xml path('')
),1,1,'') as pattern
from CALC c;
Result:
total pattern
30 11111111111111111111111111100111
Depending on your input date, you should be able to do something like the following to calculate your Days With An Event.
The cte is used to generate a table of dates, the start and end of which are defined by the two date variables. These would be best suited as data driven from your source data. If you have to use numbered date values, you could simply return incrementing numbers instead of incrementing dates:
declare #Events table (StartDate date
,DaysLength int
)
insert into #Events values
('20160801',22)
,('20160801',24)
,('20160818',10)
,('20160830',3)
declare #StartDate date = getdate()-30
,#EndDate date = getdate()+30
;with Dates As
(
select DATEADD(day,1,#StartDate) as Dates
union all
select DATEADD(day,1, Dates)
from Dates
where Dates < #EndDate
)
select count(distinct d.Dates) as EventingDays
from Dates d
inner join #Events e
on(d.Dates between e.StartDate and dateadd(d,e.DaysLength-1,e.StartDate)
)
option(maxrecursion 0)

Accessing prior rows and divide its value by current row

I have the rows below, and i want to access prior row and divide its value by current row. For every row, i need to calculate the Vi value, this Vi value is equal to Vi-1/Vi which means that:
Given the table
Table T
id value out
1 100
2 200
3 10
4 50
I want to generate these values
V1 = 100
V2= 100/200 = 0.5
V3 = 0.5/10 = 0.05
V4 = 0.05/50 = 0.001
So at the end i want the following output:
id value out
1 100 100
2 200 0.5
3 10 0.05
4 50 0.001
I tried using the aggregate function SUM with OVER(), but i do not know how to solve this problem as i need to divide and not sum the value
SELECT id, value, SUM(value) OVER(ORDER BY id ROWS BETWEEN
1 PRECEDING AND 1 PRECEDING ) / value as out
FROM T
Sample data:
CREATE TABLE t(
id INT,
value INT
);
INSERT INTO t VALUES
(1, 100), (2, 200), (3, 10), (4, 50);
Unfortunately, SQL do not have Product, but it should be simple to use cte. The performance should be not bad if id was indexed
DECLARE #T table (id int identity(1,1) primary key, value int)
INSERT #T VALUES (100), (200), (10), (50)
;WITH cte AS
(
SELECT id, value, CAST(value AS decimal(20,4)) AS out FROM #T WHERE id = 1
UNION ALL SELECT T.id, T.value, CAST(cte.out / T.value AS decimal(20,4)) FROM cte INNER JOIN #T T ON cte.id = T.id - 1
)
SELECT * FROM cte

Subtraction for two rows

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)

Resources