How to make Row_number() based on condition? - sql-server

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;

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;

Update null values by value in same column

I have a table in MS SQL Server, where are some null values in column "value"
Group ID Value
A 1 10
A 2
A 3
A 4 40
B 1
B 2 20
B 3 30
B 4
I want to update null values by not null in the same group with with the first higher ID, or if there is not any higher in same group, first lower. So the result should look like this.
Group ID Value
A 1 10
A 2 40
A 3 40
A 4 40
B 1 20
B 2 20
B 3 30
B 4 30
Thanks!
You can use windowed version of SUM function in order to determine islands of NULL valued records along with the record having the higher ID in the same group:
SELECT [Group], ID, Value,
SUM(CASE WHEN Value IS NULL THEN 0 ELSE 1 END) OVER
(PARTITION BY [Group] ORDER BY ID DESC) AS grp
FROM mytable
Output:
Group ID Value grp
-----------------------
A 4 40 1
A 3 30 2
A 2 NULL 2
A 1 NULL 2
B 4 40 1
B 3 NULL 1
B 2 20 2
B 1 10 3
You can now wrap the above query in a CTE and use another CTE to do the update:
;WITH CTE AS (
SELECT [Group], ID, Value,
SUM(CASE WHEN Value IS NULL THEN 0 ELSE 1 END) OVER
(PARTITION BY [Group] ORDER BY ID DESC) AS grp
FROM mytable
), ToUpdate AS (
SELECT [Group], ID, Value,
MAX(Value) OVER (PARTITION BY [Group], grp) AS group_value
FROM CTE
)
UPDATE ToUpdate
SET Value = group_value
WHERE Value IS NULL
Demo here
Edit:
The above query doesn't handle the edge case where the very last record within a Group slice is NULL. To handle this case as well you can use the following query:
;WITH CTE AS (
SELECT [Group], ID, Value,
SUM(CASE WHEN Value IS NULL THEN 0 ELSE 1 END) OVER
(PARTITION BY [Group] ORDER BY ID DESC) AS grp,
SUM(CASE WHEN Value IS NULL THEN 0 ELSE 1 END) OVER
(PARTITION BY [Group] ORDER BY ID) AS grp2
FROM mytable
), ToUpdate AS (
SELECT [Group], ID, Value,
MAX(Value) OVER (PARTITION BY [Group], grp) AS group_value,
MAX(Value) OVER (PARTITION BY [Group], grp2) AS group_value2
FROM CTE
)
UPDATE ToUpdate
SET Value = COALESCE(group_value, group_value2)
WHERE Value IS NULL
Demo here
Please try this-
DATA GENERATION
DECLARE #T TABLE
(
GroupCd CHAR(1),
Id INT,
Value INT
)
INSERT INTO #T
VALUES('A',1,10),
('A',2,NULL),
('A',3,NULL),
('A',4,40),
('B',1,NULL),
('B',2,20),
('B',3,30),
('B',4,NULL)
SOLUTION
UPDATE a
SET a.Value = b.Value
FROM #T a
INNER JOIN
(
SELECT a.GroupCd,a.Id,Coalesce(a.Value,z.Value,z1.Value) Value
FROM #T a
OUTER APPLY
(
SELECT TOP 1 Value
FROM #T b
WHERE a.GroupCd = b.GroupCd
AND b.Value IS NOT NULL AND a.Id < b.Id
ORDER BY Id
)z
OUTER APPLY
(
SELECT TOP 1 Value
FROM #T b
WHERE a.GroupCd = b.GroupCd
AND b.Value IS NOT NULL AND a.Id > b.Id
ORDER BY Id DESC
)z1
)b ON a.GroupCd = b.GroupCd AND a.Id = b.Id
SELECT * FROM #T
OUTPUT
GroupCd Id Value
------- ----------- -----------
A 1 10
A 2 40
A 3 40
A 4 40
B 1 20
B 2 20
B 3 30
B 4 30
(8 rows affected)
You Can try This simple Method
DECLARE #T TABLE
(
GroupCd CHAR(1),
Id INT,
Value INT
)
INSERT INTO #T
VALUES('A',1,NULL),
('A',2,NULL),
('A',3,30),
('A',4,40),
('B',1,10),
('B',2,20),
('B',3,NULL),
('B',4,40)
SELECT
*,
NewVal = COALESCE(Value,(SELECT TOP 1 Value FROM #T WHERE GroupCd = T.GroupCd AND Id > T.Id AND Value IS NOT NULL ORDER BY Id ASC))
FROM #T T
My Result
update MY_TABLE set [value] = [newValue] from (
select [Group] [newGroup],
[Value] [newValue]
from (
select [Group], [Value],
row_number() over (partition by [group] order by [Id] desc) [rn]
from MY_TABLE
where [Value] is not null
) [a] where [rn] = 1
) where [Group] = [newGroup] and [Value] is null

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

check all the values of a column

select all the departments having students with even roll number
Dept No Roll No Student Name
1 1 lee
1 2 scott
2 2 scott
2 4 smith
1 4 smith
This should result in DEpt no 2 as it has only students with roll number divisible by 2
Another(imo easy and lightweight) way is using NOT EXISTS and DISTINCT:
SELECT DISTINCT [Dept No]
FROM dbo.TableName t
WHERE NOT EXISTS
(
SELECT 1 FROM dbo.TableName t2
WHERE t.[Dept No] = t2.[Dept No]
AND t2.[Roll No] % 2 = 1
)
Demo
If there is no odd number all must be even.
You can use GROUP BY with HAVING like this.
Query
SELECT [Dept No]
FROM departments
GROUP BY [Dept No]
HAVING SUM(CASE WHEN [Roll No] % 2 = 0 THEN 1 ELSE 0 END) > 1
AND SUM(CASE WHEN [Roll No] % 2 = 1 THEN 1 ELSE 0 END) = 0
Explanation
The query returns the departments if there a rollno which is even using SUM(CASE WHEN [Roll No] % 2 = 0 THEN 1 ELSE 0 END) > 1. If there is any rollno with odd roll no, SUM(CASE WHEN [Roll No] % 2 = 1 THEN 1 ELSE 0 END) will return non zero sum and that department will be excluded.
declare #t table (Dept int,Rno int,Student varchar(10))
insert into #t (Dept,Rno,Student)values (1,1,'lee'),(1,2,'scott'),(2,2,'scott'),(2,4,'smith'),(1,4,'smith')
SELECT Dept,Rno,Student
FROM (SELECT ROW_NUMBER () OVER (ORDER BY Rno DESC) row_number, Dept,Rno,Student
FROM #t) a WHERE (row_number%2) = 0

How to get all numbers between a range

I have a table as below
Id RFrom RTo
.... ....... .....
1 10 14
1 22 25
2 100 102
2 176 180
I want to get all numbers between each RFrom and RTo for each Id. My expected result is as follows
Id NUMS
.... ......
1 10
1 11
1 12
1 13
1 14
1 22
1 23
1 24
1 25
2 100
2 101
2 102
2 176
2 177
2 178
2 179
2 180
Do I have to use cursor to achieve this?
Here is your sample table
SELECT * INTO #TEMP FROM
(
SELECT 1 ID, 10 RFROM, 14 RTO
UNION ALL
SELECT 1, 22, 25
UNION ALL
SELECT 2, 100, 102
UNION ALL
SELECT 2, 176, 180
)TAB
You need to use recursion for each Id to get the result
;WITH CTE AS
(
SELECT ID,RFROM RFROM1,RTO RTO1
FROM #TEMP
UNION ALL
SELECT T.ID,RFROM1+1,RTO1
FROM #TEMP T
JOIN CTE ON CTE.ID = T.ID
WHERE RFROM1 < RTO1
)
SELECT DISTINCT ID,RFROM1 NUMS
FROM CTE
SQL FIDDLE
Another option would be to use a numbers table with a join -- recursion can be time consuming.
There are several options to create a numbers table (I'd recommend creating a permanent one), but here's a temp one created with a common-table-expression:
with numberstable as (
select top 10000 row_number() over(order by t1.number) as number
from master..spt_values t1
cross join master..spt_values t2
)
select yt.id,
nt.number
from yourtable yt
join numberstable nt on nt.number between yt.rfrom and yt.rto
SQL Fiddle Demo
Create a tally table using stacked CTE which will have better performance when compared to recursive CTE
declare #min int
select #min= min(RFrom) from yourtable
;WITH e1(n) AS
(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), -- 10
e2(n) AS (SELECT 1 FROM e1 CROSS JOIN e1 AS b), -- 10*10
e3(n) AS (SELECT 1 FROM e1 CROSS JOIN e2) -- 10*100
SELECT b.id,
a.n
FROM yourtable b
JOIN (SELECT n = Row_number()OVER (ORDER BY n)+ #min-1
FROM e3)a
ON a.n BETWEEN b.RFrom AND b.RTo
ORDER BY n;
Check here for info

Resources