Issue with Row_Number() Over Partition - sql

I've been trying to reset the row_number when the value changes on Column Value and I have no idea on how should i do this.
This is my SQL snippet:
WITH Sch(SubjectID, VisitID, Scheduled,Actual,UserId,RLev,SubjectTransactionID,SubjectTransactionTypeID,TransactionDateUTC,MissedVisit,FieldId,Value) as
(
select
svs.*,
CASE WHEN stdp.FieldID = 'FrequencyRegime' and svs.SubjectTransactionTypeID in (2,3) THEN
stdp.FieldID
WHEN stdp.FieldID is NULL and svs.SubjectTransactionTypeID = 1
THEN NULL
WHEN stdp.FieldID is NULL
THEN 'FrequencyRegime'
ELSE stdp.FieldID
END AS [FieldID],
CASE WHEN stdp.Value is NULL and svs.SubjectTransactionTypeID = 1
THEN NULL
WHEN stdp.Value IS NULL THEN
(SELECT TOP 1 stdp.Value from SubjectTransaction st
JOIN SubjectTransactionDataPoint STDP on stdp.SubjectTransactionID = st.SubjectTransactionID and stdp.FieldID = 'FrequencyRegime'
where st.SubjectID = svs.SubjectID
order by st.ServerDateST desc)
ELSE stdp.Value END AS [Value]
from SubjectVisitSchedule svs
left join SubjectTransactionDataPoint stdp on svs.SubjectTransactionID = stdp.SubjectTransactionID and stdp.FieldID = 'FrequencyRegime'
)
select
Sch.*,
CASE WHEN sch.Value is not NULL THEN
ROW_NUMBER() over(partition by Sch.Value, Sch.SubjectID order by Sch.SubjectID, Sch.VisitID)
ELSE NULL
END as [FrequencyCounter],
CASE WHEN Sch.Value = 1 THEN 1--v.Quantity
WHEN Sch.Value = 2 and (ROW_NUMBER() over(partition by Sch.Value, Sch.SubjectID order by Sch.SubjectID, Sch.VisitID) % 2) <> 0
THEN 0
WHEN Sch.Value = 2 and (ROW_NUMBER() over(partition by Sch.Value, Sch.SubjectID order by Sch.SubjectID, Sch.VisitID) % 2) = 0
THEN 1
ELSE NULL
END AS [DispenseQuantity]
from Sch
--left join VisitDrugAssignment v on v.VisitID = Sch.VisitID
where SubjectID = '4E80718E-D0D8-4250-B5CF-02B7A259CAC4'
order by SubjectID, VisitID
This is my Dataset:
Based on the Dataset, I am trying to reset the FrequencyCounter to 1 every time the value changes for each subject, Right now it does 50% of what I want, It is counting when the value 1 or 2 is found, but when value 1 comes again after value 2 it continues the count from where it left. I want every time the value is changes the count to also start from the beginning.

It's difficult to reproduce and test without sample data, but if you want to know how to number rows based on change in column value, next approach may help. It's probably not the best one, but at least will give you a good start. Of course, I hope I understand your question correctly.
Data:
CREATE TABLE #Data (
[Id] int,
[Subject] varchar(3),
[Value] int
)
INSERT INTO #Data
([Id], [Subject], [Value])
VALUES
(1, '801', 1),
(2, '801', 2),
(3, '801', 2),
(4, '801', 2),
(5, '801', 1),
(6, '801', 2),
(7, '801', 2),
(8, '801', 2)
Statement:
;WITH ChangesCTE AS (
SELECT
*,
CASE
WHEN LAG([Value]) OVER (PARTITION BY [Subject] ORDER BY [Id]) <> [Value] THEN 1
ELSE 0
END AS [Change]
FROM #Data
), GroupsCTE AS (
SELECT
*,
SUM([Change]) OVER (PARTITION BY [Subject] ORDER BY [Id]) AS [GroupID]
FROM ChangesCTE
)
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY [GroupID] ORDER BY [Id]) AS Rn
FROM GroupsCTE
Result:
--------------------------------------
Id Subject Value Change GroupID Rn
--------------------------------------
1 801 1 0 0 1
2 801 2 1 1 1
3 801 2 0 1 2
4 801 2 0 1 3
5 801 1 1 2 1
6 801 2 1 3 1
7 801 2 0 3 2
8 801 2 0 3 3

As per my understanding, you need DENSE_RANK as you are looking for the row number will only change when value changed. The syntax will be as below-
WITH your_table(your_column)
AS
(
SELECT 2 UNION ALL
SELECT 10 UNION ALL
SELECT 2 UNION ALL
SELECT 11
)
SELECT *,DENSE_RANK() OVER (ORDER BY your_column)
FROM your_table

Related

Indicate a row that cause an abnormal case (SQL)

I have a result as below using the following script:
SELECT
id, (2022 - age) yearId, age, [value],
CASE
WHEN LAG([value], 1, 0) OVER (PARTITION BY id ORDER BY [age]) = 0
THEN 'Base'
WHEN [value] > LAG([value], 1, -1) OVER (PARTITION BY id ORDER BY [age])
THEN 'Increasing'
WHEN [value] = LAG([value], 1, -1) OVER (PARTITION BY id ORDER BY [age])
THEN 'No Change'
ELSE 'Decreasing'
END AS [Order]
FROM Test
Values
And I manage to get a group of ids with an id causing a "flip: decreasing and then increasing or the other way around" as:
Abnormal Case
Now I want to print out the same result as above but with a column indicates the row that cause the flip, something like this (the row causes the flip should be place at the top of each partition):
Id
age
value
flip
1
4
3
1
1
0
5
0
1
1
4
0
1
2
3
0
1
3
2
0
1
5
3
0
1
6
4
0
Thank you!
Expanding your existing logic to get the previous order value then conditionally ordering
with cte as
(
SELECT
id, (2022 - age) yearId, age, [value],
CASE
WHEN LAG([value], 1, 0) OVER (PARTITION BY id ORDER BY [age]) = 0
THEN 'Base'
WHEN [value] > LAG([value], 1, -1) OVER (PARTITION BY id ORDER BY [age])
THEN 'Increasing'
WHEN [value] = LAG([value], 1, -1) OVER (PARTITION BY id ORDER BY [age])
THEN 'No Change'
ELSE 'Decreasing'
END AS [Order]
FROM T1
) ,
cte1 as
(select cte.*,concat(cte.[order], lag([order]) over (partition by id order by age)) concatlag
from cte)
select * ,
case when concatlag in('IncreasingDecreasing','DecreasingIncreasing') then 1 else 0 end
from cte1
order by
case when concatlag in('IncreasingDecreasing','DecreasingIncreasing') then 1 else 0 end desc,
age

ROW_Number with Custom Group

I am trying to have row_number based on custom grouping but I am not able to produce it.
Below is my Query
CREATE TABLE mytbl (wid INT, id INT)
INSERT INTO mytbl Values(1,1),(2,1),(3,0),(4,2),(5,3)
Current Output
wid id
1 1
2 1
3 0
4 2
5 3
Query
SELECT *, RANK() OVER(PARTITION BY wid, CASE WHEN id = 0 THEN 0 ELSE 1 END ORDER BY ID)
FROM mytbl
I would like to rank the rows based on custom condition like if ID is 0 then I have start new group until I have non 0 ID.
Expected Output
wid id RN
1 1 1
2 1 1
3 0 1
4 2 2
5 3 2
Guessing here, as we don't have much clarification, but perhaps this:
SELECT wid,
id,
COUNT(CASE id WHEN 0 THEN 1 END) OVER (ORDER BY wid ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) +1 AS [Rank]
FROM mytbl ;
If I understand you correctly, you may use the next approach. Note, that you need to have an ordering column (I assume this is wid column):
Statement:
;WITH ChangesCTE AS (
SELECT
*,
CASE WHEN LAG(id) OVER (ORDER BY wid) = 0 THEN 1 ELSE 0 END AS ChangeIndex
FROM mytbl
), GroupsCTE AS (
SELECT
*,
SUM(ChangeIndex) OVER (ORDER BY wid) AS GroupIndex
FROM ChangesCTE
)
SELECT
wid,
id,
DENSE_RANK() OVER (ORDER BY GroupIndex) AS Rank
FROM GroupsCTE
Result:
wid id Rank
1 1 1
2 1 1
3 0 1
4 2 2
5 3 2
without much clarification on the logic required, my understanding is you want to increase the Rank by 1 whenever id = 0
select wid, id,
[Rank] = sum(case when id = 0 then 1 else 0 end) over(order by wid)
+ case when id <> 0 then 1 else 0 end
from mytbl
Try this,
CREATE TABLE #mytbl (wid INT, id INT)
INSERT INTO #mytbl Values(1,1),(2,1),(3,0)
,(4,2),(5,3),(6,0),(7,4),(8,5),(9,6)
;with CTE as
(
select *,ROW_NUMBER()over(order by wid)rn
from #mytbl where id=0
)
,CTE1 as
(
select max(rn)+1 ExtraRN from CTE
)
select a.* ,isnull(ca.rn,ca1.ExtraRN) from #mytbl a
outer apply(select top 1 * from CTE b
where a.wid<=b.wid )ca
cross apply(select ExtraRN from CTE1)ca1
drop table #mytbl
Here both OUTER APPLY and CROSS APPLY will not increase cardianility estimate.It will always return only one rows.

SQL Server grouping rows

I have this data:
Id | Name | count | Group_number
------+-------+-------+--------------
1 | cdd | 50 | 0
2 | cdd | 15 | 0
3 | cdd | 0 | 0
4 | cdd | 25 | 0
5 | cdd | 11 | 0
I want a script that makes three or four groups on condition: Sum(count) for each group < 50
I want this output:
1 | cdd | 50 | 1
2 | cdd | 15 | 2
3 | cdd | 0 | 2
4 | cdd | 25 | 2
5 | cdd | 11 | 3
Assuming this has to be done for each name, you can use a recursive cte.
with rownums as (select t.*,row_number() over(partition by name order by id) as rnum from t)
,cte(rnum,id,name,cnt,runningsum,grp) as
(select rnum,id,name,cnt,cnt,1 from rownums where rnum=1
union all
select t.rnum,t.id,t.name,t.cnt
,case when c.runningsum+t.cnt > 50 then t.cnt else c.runningsum+t.cnt end
,case when c.runningsum+t.cnt > 50 then t.id else c.grp end
from cte c
join rownums t on t.rnum=c.rnum+1 and t.name=c.name
)
select id,cnt,name,dense_rank() over(partition by name order by grp) as grp
from cte
Sample Demo
Keep track of the running sum and reset it when it goes over 50. Also remember the id when the sum goes over 50. This can be used to assign group numbers.
For records where the count is less than 50 we can simply generate a grouping id by calculating a running total on the count and then divide this running total by 50. However, since some records may already have a count that is greater than or equal to 50 might generate an incorrect id. To solve this problem, we need to somehow force the generation of a new grouping id on the next record. This can be done by simply adjusting the count the next record by 50 if the current records count is 50 or greater.
The following example demonstrates how this can be done:
CREATE TABLE #Items
(
[Id] INT NOT NULL PRIMARY KEY
,[Name] VARCHAR(50) NOT NULL
,[Count] INT NOT NULL
)
INSERT INTO #Items
VALUES
(1, 'cdd', 50 ),
(2, 'cdd', 15 ),
(3, 'cdd', 0 ),
(4, 'cdd', 25 ),
(5, 'cdd', 11 );
;WITH CTE_ItemCountsAdjusted
AS
(
SELECT [Id]
,[Name]
,[Count]
,LAG([Count], 1, 0) OVER (PARTITION BY [Name] ORDER BY [Id]) AS PrevCount
,(
CASE
WHEN LAG([Count], 1, 0) OVER (PARTITION BY [Name] ORDER BY [Id]) >= 50 THEN [Count] + 50
ELSE [Count]
END
) AdjustedCount
FROM #Items
)
SELECT [Id]
,[Name]
,[Count]
,SUM([AdjustedCount]) OVER (PARTITION BY [Name] ORDER BY [Id] ROWS UNBOUNDED PRECEDING) / 50 AS [Group_number]
FROM CTE_ItemCountsAdjusted
ORDER BY [Id]
This method eliminates the need for recursive calls. Note if you need the group id to be strictly sequential (no gaps between group numbers) then you can make use of the DENSE_RANK() windowing function to achieve this as per following example:
INSERT INTO #Items
VALUES
(1, 'cdd', 50 ),
(2, 'cdd', 15 ),
(3, 'cdd', 0 ),
(4, 'cdd', 25 ),
(5, 'cdd', 11 ),
(6, 'cdd', 200 ),
(7, 'cdd', 10 );
;WITH CTE_ItemCountsAdjusted
AS
(
SELECT [Id]
,[Name]
,[Count]
,LAG([Count], 1, 0) OVER (PARTITION BY [Name] ORDER BY [Id]) AS PrevCount
,(
CASE
WHEN LAG([Count], 1, 0) OVER (PARTITION BY [Name] ORDER BY [Id]) >= 50 THEN [Count] + 50
ELSE [Count]
END
) AdjustedCount
FROM #Items
),CTE_ItemCountsWithGroupID
AS
(
SELECT [Id]
,[Name]
,[Count]
,SUM([AdjustedCount]) OVER (PARTITION BY [Name] ORDER BY [Id] ROWS UNBOUNDED PRECEDING) / 50 AS [Group_number]
FROM CTE_ItemCountsAdjusted
)
SELECT [Id]
,[Name]
,[Count]
,[Group_number]

SQL Rank() function excluding rows

Consider I have the following table.
ID value
1 100
2 200
3 200
5 250
6 1
I have the following query which gives the result as follows. I want to exclude the value 200 from rank function, but still that row has to be returned.
SELECT
CASE WHEN Value = 200 THEN 0
ELSE DENSE_RANK() OVER ( ORDER BY VALUE DESC)
END AS RANK,
ID,
VALUE
FROM #table
RANK ID VALUE
1 5 250
0 2 200
0 3 200
4 1 100
5 6 1
But I want the result as follows. How to achieve it?
RANK ID VALUE
1 5 250
0 2 200
0 3 200
2 1 100
3 6 1
If VAL column is not nullable, taking into account NULL is the last value in ORDER BY .. DESC
select *, dense_rank() over (order by nullif(val,200) desc) * case val when 200 then 0 else 1 end
from myTable
order by val desc;
There is no way to exclude Val in Dense Rank currently ,unless you filter in where clause..that is the reason ,you get below result
RANK ID VALUE
1 5 250
0 2 200
0 3 200
4 1 100
5 6 1
You will need to filter once and then do a union all
;with cte(id,val)
as
(
select 1, 100 union all
select 2, 200 union all
select 3, 200 union all
select 5, 250 union all
select 6, 1 )
select *, dense_rank() over (order by val desc)
from cte
where val<>200
union all
select 0,id,val from cte where val=200
You could split the ranking in to separate queries for the values you want to include/exclude from the ranking and UNION ALL the results like so:
Standalone executable example:
CREATE TABLE #temp ( [ID] INT, [value] INT );
INSERT INTO #temp
( [ID], [value] )
VALUES ( 1, 100 ),
( 2, 200 ),
( 3, 200 ),
( 5, 250 ),
( 6, 1 );
SELECT *
FROM ( SELECT 0 RANK ,
ID ,
value
FROM #temp
WHERE value = 200 -- set rank to 0 for value = 200
UNION ALL
SELECT DENSE_RANK() OVER ( ORDER BY value DESC ) AS RANK ,
ID ,
value
FROM #temp
WHERE value != 200 -- perform ranking on records != 200
) t
ORDER BY value DESC ,
t.ID
DROP TABLE #temp
Produces:
RANK ID value
1 5 250
0 2 200
0 3 200
2 1 100
3 6 1
You can modify the ordering at the end of the statement if required, I set it to produce your desired results.
You can also try this, too:
SELECT ISNULL(R, 0) AS Rank ,t.id ,t.value
FROM tbl1 AS t
LEFT JOIN ( SELECT id ,DENSE_RANK() OVER ( ORDER BY value DESC ) AS R
FROM dbo.tbl1 WHERE value <> 200
) AS K
ON t.id = K.id
ORDER BY t.value DESC
The solution in the original question was actually pretty close. Just adding a partition clause to the dense_rank can do the trick.
SELECT CASE
WHEN VALUE = 200 THEN 0
ELSE DENSE_RANK() OVER(
PARTITION BY CASE WHEN VALUE = 200 THEN 0 ELSE 1 END
ORDER BY VALUE DESC
)
END AS RANK
,ID
,VALUE
FROM #table
ORDER BY VALUE DESC;
The 'partition by' creates separate groups for the dense_rank such that the order is performed on these groups individually. This essentially means you create two ranks at the same time, one for the group without the 200 value and one for the group with only the 200 value. The latter one to be set to 0 in the 'case when'.
Standalone executable example:
DECLARE #table TABLE
(
ID INT NOT NULL PRIMARY KEY
,VALUE INT NULL
)
INSERT INTO #table
(
ID
,VALUE
)
SELECT 1, 100
UNION SELECT 2, 200
UNION SELECT 3, 200
UNION SELECT 5, 250
UNION SELECT 6, 1;
SELECT CASE
WHEN VALUE = 200 THEN 0
ELSE DENSE_RANK() OVER(
PARTITION BY CASE WHEN VALUE = 200 THEN 0 ELSE 1 END
ORDER BY VALUE DESC
)
END AS RANK
,ID
,VALUE
FROM #table
ORDER BY VALUE DESC;
RANK ID VALUE
1 5 250
0 2 200
0 3 200
2 1 100
3 6 1

Update sort Column linked with group

How can i change the position of one row to change the order
Best to explain with example
I have following table with statuses
Id Name StatusOrder StatusGroup
1 Open 1 1
2 Start 2 1
3 Load 3 1
4 Close 4 1
5 Begin 1 2
6 Open 2 2
7 Close 3 2
I would like to Switch from group one only Status order 2 with 3.
The jump can be more than one row, ex. its also possible that within the same group the order from open moves to status order 3
Sow when i do following select
SELECT * FROM Status WHERE (StatusGroup =1)
Result Set:
Id Name StatusOrder StatusGroup
1 Open 1 1
3 Load 2 1
2 Start 3 1
4 Close 4 1
5 Begin 1 2
6 Open 2 2
7 Close 3 2
I already found example with following article but i do not succeed in it to intgrate that only for one group the order changes
Using a sort order column in a database table
How Can help me?
If correctly understood, here you go:
QUERY
create table #t
(
Id INT,
Name VARCHAR(20),
StatusOrder INT,
StatusGroup INT
)
insert into #t values
(1 ,'Open', 1 , 1),
(2 ,'Start', 2 , 1),
(3 ,'Load', 3 , 1),
(4 ,'Close', 4 , 1),
(5 ,'Begin', 1 , 2),
(6 ,'Open', 2 , 2),
(7 ,'Close', 3 , 2)
;with cte as (
select *, row_number() over(partition by StatusGroup order by Id) rn
from #t
)
select case when StatusOrder = 2 then 3 when StatusOrder = 3 then 2 else Id end as Id,
case when StatusOrder = 2 then 'Load' when StatusOrder = 3 then 'Start' else Name end as Name,
StatusOrder,
StatusGroup
from cte
where rn = id
union all
select Id, Name, StatusOrder, StatusGroup
from cte
where rn <> id
drop table #t
OUTPUT
Id Name StatusOrder StatusGroup
1 Open 1 1
3 Load 2 1
2 Start 3 1
4 Close 4 1
5 Begin 1 2
6 Open 2 2
7 Close 3 2
UPDATE
So if you have table where you need update records you can do something like:
;with cte as (
select *, row_number() over(partition by StatusGroup order by Id) rn
from #t
)
update t
set t.Id = (case when cte.StatusOrder = 2 then 3
when cte.StatusOrder = 3 then 2 else t.Id end),
t.Name = (case when cte.StatusOrder = 2 then 'Load'
when cte.StatusOrder = 3 then 'Start' else t.Name end)
from cte
join #t t on cte.id = t.id
where cte.rn = cte.id