Grouping in SQL Server - sql-server

I have data similar to below. I need to group the status column based on the occurrence of data.
Id Status Value
1 K 1
2 K 3
3 K 2
4 B 2
5 B 3
6 K 6
7 J 5
8 J 2
I want data as below
Status Value
K 6
B 5
K 6
J 7
I need the cumulative sum of the value column.In the status column if the data is same consecutively, then I need to add the value columns. I cannot apply group by. In the example given K is repeated twice, because they are not consecutive.
I have tried below query, but it doesn't work as required.
select Status,
(select sum(value)
from table t2
where
t2.Status = t.Status and
t2.SNO <= t.SNO
) as total
from table t;

This is a Gaps and Islands Question
I tackle these by using the incrementing Id and combing this with ROW_NUMBER window function
--Using a CTE just to replicate the sample data
;WITH cteX (Id,Status,Value)
AS(
SELECT 1,'K', 1 UNION ALL
SELECT 2,'K', 3 UNION ALL
SELECT 3,'K', 2 UNION ALL
SELECT 4,'B', 2 UNION ALL
SELECT 5,'B', 3 UNION ALL
SELECT 6,'K', 6 UNION ALL
SELECT 7,'J', 5 UNION ALL
SELECT 8,'J', 2
)
SELECT
Grp = Id - ROW_NUMBER()OVER( PARTITION BY X.Status ORDER BY X.Id)
, X.Id
, X.Status
, X.Value
FROM
cteX X
ORDER BY
X.Id
This gives this result set, note the Grp column
Grp Id Status Value
------- ------- ------- -------
0 1 K 1
0 2 K 3
0 3 K 2
3 4 B 2
3 5 B 3
2 6 K 6
6 7 J 5
6 8 J 2
Then combine with a CTE or derived table you can get your expected output
--Using a CTE just to replicate the sample data
;WITH cteX (Id,Status,Value)
AS(
SELECT 1,'K', 1 UNION ALL
SELECT 2,'K', 3 UNION ALL
SELECT 3,'K', 2 UNION ALL
SELECT 4,'B', 2 UNION ALL
SELECT 5,'B', 3 UNION ALL
SELECT 6,'K', 6 UNION ALL
SELECT 7,'J', 5 UNION ALL
SELECT 8,'J', 2
)
SELECT Y.Status
, Value = SUM(Y.Value)
FROM
(
SELECT TOP 100 PERCENT
Grp = Id - ROW_NUMBER()OVER( PARTITION BY X.Status ORDER BY X.Id)
, X.Id
, X.Status
, X.Value
FROM
cteX X
ORDER BY
X.Id
) Y
GROUP BY
Y.Grp, Y.Status
Output
Status Value
------- -------
B 5
J 7
K 6
K 6
Update Question include "Preserve order" solution
Just include an Order by MIN(Id)
--Using a CTE just to replicate the sample data
;WITH cteX (Id,Status,Value)
AS(
SELECT 1,'K', 1 UNION ALL
SELECT 2,'K', 3 UNION ALL
SELECT 3,'K', 2 UNION ALL
SELECT 4,'B', 2 UNION ALL
SELECT 5,'B', 3 UNION ALL
SELECT 6,'K', 6 UNION ALL
SELECT 7,'J', 5 UNION ALL
SELECT 8,'J', 2
)
SELECT
Y.[Status]
,[Value] = SUM(Y.[Value])
FROM
(
SELECT
Grp = Id - ROW_NUMBER()OVER( PARTITION BY X.[Status] ORDER BY X.Id)
, X.Id
, X.[Status]
, X.[Value]
FROM
cteX X
) Y
GROUP BY
Y.Grp, Y.[Status]
ORDER BY
MIN(Y.Id) --preserve the Status Order
Output
Status Value
------- -------
K 6
B 5
K 6
J 7

Related

Compare two tables and retrieve data

I have 2 tables in SQL Server and I want to compare them. I want to take 'NEEDED_AMOUNT' and 'min. 'ID'. I tried the following:
SELECT S_ID, NEEDED_AMOUNT, ID
FROM (
select T1.S_ID
, T2.NEEDED_AMOUNT
, T1.ID
from T1
INNER JOIN T2 MSD ON T1.S_ID = T2.S_ID
) TABLE1
GROUP BY S_ID, NEEDED_AMOUNT, ID
To explain this for example: in T1 table I have S_ID as '1' and its amount '20' and '30'. Also in T2 I have request for S_ID and I need '40' amount. So in T1 table how can I reach 40? I must take first row '20' amount and I split second row '30' to '20'. Below you can see what I want the output.
So here are the tables.
I can call this table T1 (ID is primary key and auto inc.):
ID AMOUNT S_ID
1 20 1
2 30 1
3 10 2
4 20 3
5 5 3
and I can call this table T2:
S_ID NEEDED_AMOUNT DATE
1 40 01.01.2020
2 5 02.01.2020
3 20 03.01.2020
So my output will be like this:
S_ID NEEDED_AMOUNT ID
1 20 1
1 20 2
2 5 3
3 20 4
Thanks for any opinion
I would use recursive approach for this :
with cte as (
select id, amount, s_id, needed_amount,
(case when amount = needed_amount then 1 else cnt end) as cnt
from (select t1.*, t2.needed_amount,
row_number() over (partition by t1.s_id order by t1.id) as seq,
count(*) over (partition by t1.s_id) as cnt
from t1 inner join
t2
on t2.s_id = t1.s_id
) t
where seq = 1
), cte1 as (
select c.needed_amount / c.cnt as amount, c.s_id, 1 as start, c.cnt
from cte c
union all
select amount, s_id, start + 1, cnt
from cte1 c1
where start < cnt
)
select s_id, amount, row_number() over (order by s_id) as id
from cte1;

How to make Row_number() based on condition?

I have list of sample data. Using this I need new column which having sequence number. But condition of this sequence number is if consecutively InRange column value 1 then only it generate sequence number.In between if InRange value 0 then again sequence number start from 1 and so on.
Below query which I have created but not return expected result.
CREATE TABLE #Result (ID INT,Value INT,InRange BIT)
INSERT INTO #Result
SELECT 1 ,211,0
UNION SELECT 2 ,205,1
UNION SELECT 3 ,214,0
UNION SELECT 4 ,202,1
UNION SELECT 5 ,204,1
UNION SELECT 6 ,203,1
UNION SELECT 7 ,209,0
UNION SELECT 8 ,216,0
UNION SELECT 9 ,205,1
UNION SELECT 10 ,224,0
Query:
SELECT *
,CASE WHEN InRange=1 THEN ROW_NUMBER()OVER(Order by Id asc) ELSE 0 END AS ExpectedColumn
FROM #Result
Expected result.
ID Value InRange ExpectedColumn
1 211 0 0
2 205 1 1
3 214 0 0
4 202 1 1
5 204 1 2
6 203 1 3
7 209 0 0
8 216 0 0
9 205 1 1
10 224 0 0
This is a gaps and islands problem, with the islands being each group of records to which you want to assign its own row number sequence. One straightforward way to handle this uses the difference in row numbers method:
WITH cte1 AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY ID) rn1
FROM #Result
WHERE InRange = 1
),
cte2 AS (
SELECT t1.*,
ROW_NUMBER() OVER (ORDER BY t1.ID) - t2.rn1 AS diff
FROM #Result t1
LEFT JOIN cte1 t2
ON t1.ID = t2.ID
)
SELECT ID, Value, InRange,
CASE WHEN InRange <> 0
THEN ROW_NUMBER() OVER (PARTITION BY diff ORDER BY ID)
ELSE 0 END AS ExpectedColumn
FROM cte2
ORDER BY ID;
Demo
with grouped_data as (
select
*,
count(case when InRange = 0 then 1 else null end) over(order by ID rows between unbounded preceding and current row) as group_number
from #Result
)
select
ID,
Value,
InRange,
row_number() over(partition by group_number order by ID) - 1 as expected_column
from grouped_data
order by ID;

Return rows that have a negative equivalent

I have a non-normalized table with several columns. I would like to return all columns that have a positive number along with a negative number of the same value.
Example:
ID | Value
-------------
1 | 10
1 | -10
3 | 15
3 | 15
4 | -1
5 | 4
Current Output:
ID | Values
-------------
1 | 10
1 | -10
3 | 15
3 | 15
Desired Output:
ID | Value
-------------
1 | 10
1 | -10
I have made a windows function as seen below that will select absolute values that are the same, but this includes pairs where there are a positive number.
select Count(*) Over (Partition By DVN, [Tran Date], [Reference Number],Description,Vendor, Abs([Maintenance Expense])) As cnt , *
From WorkTemp.dbo.Customer2700Combine
Where [Maintenance Expense] Is Not Null
Order By 1 Desc,DVN, [Tran Date], [Reference Number],Description,Vendor, Abs([NonRental Total])
Not sure if your requirement is by [ID], looking at your example, description and desired output, this is how I would do it:
DROP TABLE IF EXISTS #sopg;
SELECT [ID],
[VALUE]
INTO #sopg
FROM
(
SELECT 1 AS ID,
10 AS VALUE
UNION
SELECT 1 AS ID,
-10 AS VALUE
UNION
SELECT 3 AS ID,
15 AS VALUE
UNION
SELECT 3 AS ID,
15 AS VALUE
UNION
SELECT 4 AS ID,
-1 AS VALUE
UNION
SELECT 5 AS ID,
4 AS VALUE
) x;
-- Assuming that one ID can only have maximum 2 rows (like your example above) and want this by ID
SELECT s.[ID],
s.[VALUE]
FROM #sopg s
INNER JOIN
(
SELECT ID,
SUM(VALUE) SumZero
FROM #sopg
GROUP BY ID
HAVING SUM(VALUE) = 0
) SumZero ON SumZero.ID = s.ID
-- Another way, assuming that ID can have more than 2 rows and different values
DROP TABLE IF EXISTS #sopg2;
SELECT [ID],
[VALUE]
INTO #sopg2
FROM
(
SELECT 1 AS ID,
10 AS VALUE
UNION
SELECT 1 AS ID,
-10 AS VALUE
UNION
SELECT 1 AS ID,
-9 AS VALUE
UNION
SELECT 3 AS ID,
15 AS VALUE
UNION
SELECT 3 AS ID,
15 AS VALUE
UNION
SELECT 4 AS ID,
-1 AS VALUE
UNION
SELECT 5 AS ID,
4 AS VALUE
) x
SELECT a.[ID],
a.[VALUE]
FROM #sopg2 a
INNER JOIN #sopg b ON b.ID = a.ID AND a.VALUE = -b.VALUE

how to select rows where column value has changed

I have a table in which I have few columns like below:
Cusnbr Name LoadNumber
1 Z 10
1 Z 9
1 Z 8
1 C 7
1 C 6
1 C 5
1 B 4
1 B 3
1 A 2
1 A 1
it is just for one cusnbr there are million of cusnbr like this..
I want output like below
Cusnbr Name LoadNumber
1 C 7
1 B 4
1 A 2
For that I write below query in sql server 2008:
;With x as
(
Select * ,rn=Row_number() over (order by cusnbr,loadnumber) from table
)
select x.* from x left outer join x as y on x.rn=y.rn+1
and x.name<>y.name where y.name is not null
but I am not getting the desired output in the above code I am getting last Z also which I don't want and I am getting irregular data not in the correct form in which I want
Any help will be appreciated !!
like this I want but not able to get the desired output
I use this example
Though the question is not clear to me , Guessing from the output I have tried out Dense Rank . I guessed you want the record with highest LoadNumber with the same name .
Select * from cteTrial where LoadNumber in (
Select MAX(x.LoadNumber) as LoadNumber from (
Select cusnbr , name , LoadNumber , DENSE_RANK() over (order by Name desc )
as Dense from cteTrial) as x group by x.Dense
)
If you can use CTE it will produce better performance .
i written the code as per expected
;With cte(Cusnbr , Name , LoadNumber)
AS
(
SELECT 1,'Z', 10 Union all
SELECT 1,'Z', 9 Union all
SELECT 1,'Z', 8 Union all
SELECT 1,'C', 7 Union all
SELECT 1,'C', 6 Union all
SELECT 1,'C', 5 Union all
SELECT 1,'B', 4 Union all
SELECT 1,'B', 3 Union all
SELECT 1,'A', 2 Union all
SELECT 1,'A', 1
)
SELECT cusnbr,
NAME,
loadnumber
FROM (SELECT *,
Row_number()
OVER(
partition BY NAME
ORDER BY loadnumber DESC) AS RNk,
Row_number()
OVER(
ORDER BY (SELECT 1)) - 1 AS RNO
FROM (SELECT *
FROM cte)dt)DT2
WHERE DT2.rnk = 1
AND rno > 0
ORDER BY NAME DESC
Result
cusnbr NAME loadnumber
-------------------------
1 C 7
1 B 4
1 A 2

How to uniquely number subnodes of a parent

I have a table like so
ID Node ParentID
1 A 0
2 B 1
3 C 1
4 D 2
5 E 2
6 F 3
7 G 3
8 H 3
9 I 4
10 J 4
11 K 10
12 L 11
I need a query to generate a 'position' field with the order that a node appears within its parent. Example below
ID Node ParentID Positon
1 A 0 0
2 B 1 0
3 C 1 1
4 D 2 0
5 E 2 1
6 F 3 0
7 G 3 1
8 H 3 2
9 I 4 0
10 J 4 1
11 K 10 0
12 L 11 0
select *
, row_number() over (partition by ParentID order by ID) - 1 as Position
from YourTable
As an update query:
update yt
set Position = nr
from (
select *
, row_number() over (partition by ParentID order by ID) - 1 rn
from YourTable
) yt
To update position in the original table join it to already suggested statement, either as sub-query or CTE:
;with cte (ID, Pos)
as (
select ID, row_number() over (partition by ParentID order by ID) - 1
from [Table]
)
update T
set T.Position = cte.Pos
from [Table] T
join cte on cte.ID = T.ID

Resources