SQL Procedure to get row number to determine 'Rank' - sql

I have a table which contains a column called 'Score'. I want to write a procedure which loops through all rows in this table, and determines where out of all rows, each specific row ranks i.e biggest score is rank 1, smallest score = n.
This is my poor attempt so far:
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #Rank INT = 0;
DECLARE #UserID UNIQUEIDENTIFIER;
DECLARE cur CURSOR FOR SELECT UserID FROM tblMember
OPEN cur
FETCH NEXT FROM cur INTO #UserID
--loop through all users
WHILE ##FETCH_STATUS = 0 BEGIN
SELECT #Rank = ROW_NUMBER() OVER(ORDER BY Score DESC) FROM tblDetails WHERE UserID = #UserID;
PRINT #Rank
Print ' For '
Print #UserID;
UPDATE tblDetails SET Rank = #Rank WHERE UserID = #UserID;
FETCH NEXT FROM cur INTO #UserID
END
END
Unfortauntely this effort ranks each entry as 1 - probably becuase the query has the WHERE clause refining the result set. But I cannot determine what the correct query should be!

WITH q AS
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY userId ORDER BY score DESC) rn
FROM tblDetail
)
UPDATE q
SET rank = rn

I believe the correct query you want is:
with toupdate as (
select d.*,
row_number() over (order by score desc) as seqnum
from tblDetails
)
update toupdate
set [rank] = seqnum;
I used row_number() because your example does. You might want rank() or dense_rank() if you want scores with the same value to have the same rank:
with toupdate as (
select d.*,
rank() over (order by score desc) as seqnum
from tblDetails
)
update toupdate
set [rank] = seqnum;
The important difference is that there is no partition by clause in the query.

may be basing on the userid you are trying to update or increment the rank column..So as per my understanding what ever the Userid = 1 you want to update Rank column
declare #t INT;
SELECT #t = MAX(Userid) From tblDetails
;
with cte(w) as
(select 1
UNION ALL
Select Userid + 1
From
tblDetails where Userid < #t)
UPdate tblDetails
set [Rank]=cte.w
FROM cte
INNER JOIN tblDetails S
ON S.Userid = cte.w

Related

Selecting data from table where sum of values in a column equal to the value in another column

Sample data:
create table #temp (id int, qty int, checkvalue int)
insert into #temp values (1,1,3)
insert into #temp values (2,2,3)
insert into #temp values (3,1,3)
insert into #temp values (4,1,3)
According to data above, I would like to show exact number of lines from top to bottom where sum(qty) = checkvalue. Note that checkvalue is same for all the records all the time. Regarding the sample data above, the desired output is:
Id Qty checkValue
1 1 3
2 2 3
Because 1+2=3 and no more data is needed to show. If checkvalue was 4, we would show the third record: Id:3 Qty:1 checkValue:4 as well.
This is the code I am handling this problem. The code is working very well.
declare #checkValue int = (select top 1 checkvalue from #temp);
declare #counter int = 0, #sumValue int = 0;
while #sumValue < #checkValue
begin
set #counter = #counter + 1;
set #sumValue = #sumValue + (
select t.qty from
(
SELECT * FROM (
SELECT
ROW_NUMBER() OVER (ORDER BY id ASC) AS rownumber,
id,qty,checkvalue
FROM #temp
) AS foo
WHERE rownumber = #counter
) t
)
end
declare #sql nvarchar(255) = 'select top '+cast(#counter as varchar(5))+' * from #temp'
EXECUTE sp_executesql #sql, N'#counter int', #counter = #counter;
However, I am not sure if this is the best way to deal with it and wonder if there is a better approach. There are many professionals here and I'd like to hear from them about what they think about my approach and how we can improve it. Any advice would be appreciated!
Try this:
select id, qty, checkvalue from (
select t1.*,
sum(t1.qty) over (partition by t2.id) [sum]
from #temp [t1] join #temp [t2] on t1.id <= t2.id
) a where checkvalue = [sum]
Smart self-join is all you need :)
For SQL Server 2012, and onwards, you can easily achieve this using ROWS BETWEEN in your OVER clause and the use of a CTE:
WITH Running AS(
SELECT *,
SUM(qty) OVER (ORDER BY id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS RunningQty
FROM #temp t)
SELECT id, qty, checkvalue
FROM Running
WHERE RunningQty <= checkvalue;
One basic improvement is to try & reduce the no. of iterations. You're incrementing by 1, but if you repurpose the logic behind binary searching, you'd get something close to this:
DECLARE #RoughAverage int = 1 -- Some arbitrary value. The closer it is to the real average, the faster things should be.
DECLARE #CheckValue int = (SELECT TOP 1 checkvalue FROM #temp)
DECLARE #Sum int = 0
WHILE 1 = 1 -- Refer to BREAK below.
BEGIN
SELECT TOP (#RoughAverage) #Sum = SUM(qty) OVER(ORDER BY id)
FROM #temp
ORDER BY id
IF #Sum = #CheckValue
BREAK -- Indicating you reached your objective.
ELSE
SET #RoughAverage = #CheckValue - #Sum -- Most likely incomplete like this.
END
For SQL 2008 you can use recursive cte. Top 1 with ties limits result with first combination. Remove it to see all combinations
with cte as (
select
*, rn = row_number() over (order by id)
from
#temp
)
, rcte as (
select
i = id, id, qty, sumV = qty, checkvalue, rn
from
cte
union all
select
a.id, b.id, b.qty, a.sumV + b.qty, a.checkvalue, b.rn
from
rcte a
join cte b on a.rn + 1 = b.rn
where
a.sumV < b.checkvalue
)
select
top 1 with ties id, qty, checkvalue
from (
select
*, needed = max(case when sumV = checkvalue then 1 else 0 end) over (partition by i)
from
rcte
) t
where
needed = 1
order by dense_rank() over (order by i)

SQL Server - loop through table and update based on count

I have a SQL Server database. I need to loop through a table to get the count of each value in the column 'RevID'. Each value should only be in the table a certain number of times - for example 125 times. If the count of the value is greater than 125 or less than 125, I need to update the column to ensure all values in the RevID (are over 25 different values) is within the same range of 125 (ok to be a few numbers off)
For example, the count of RevID = "A2" is = 45 and the count of RevID = 'B2' is = 165 then I need to update RevID so the 45 count increases and the 165 decreases until they are within the 125 range.
This is what I have so far:
DECLARE #i INT = 1,
#RevCnt INT = SELECT RevId, COUNT(RevId) FROM MyTable group by RevId
WHILE(#RevCnt >= 50)
BEGIN
UPDATE MyTable
SET RevID= (SELECT COUNT(RevID) FROM MyTable)
WHERE RevID < 50)
#i = #i + 1
END
I have also played around with a cursor and instead of trigger. Any idea on how to achieve this? Thanks for any input.
Okay I cam back to this because I found it interesting even though clearly there are some business rules/discussion that you and I and others are not seeing. anyway, if you want to evenly and distribute arbitrarily there are a few ways you could do it by building recursive Common Table Expressions [CTE] or by building temp tables and more. Anyway here is a way that I decided to give it a try, I did utilize 1 temp table because sql was throwing in a little inconsistency with the main logic table as a cte about every 10th time but the temp table seems to have cleared that up. Anyway, this will evenly spread RevId arbitrarily and randomly assigning any remainder (# of Records / # of RevIds) to one of the RevIds. This script also doesn't rely on having a UniqueID or anything it works dynamically over row numbers it creates..... here you go just subtract out test data etc and you have what you more than likely want. Though rebuilding the table/values would probably be easier.
--Build Some Test Data
DECLARE #Table AS TABLE (RevId VARCHAR(10))
DECLARE #C AS INT = 1
WHILE #C <= 400
BEGIN
IF #C <= 200
BEGIN
INSERT INTO #Table (RevId) VALUES ('A1')
END
IF #c <= 170
BEGIN
INSERT INTO #Table (RevId) VALUES ('B2')
END
IF #c <= 100
BEGIN
INSERT INTO #Table (RevId) VALUES ('C3')
END
IF #c <= 400
BEGIN
INSERT INTO #Table (RevId) VALUES ('D4')
END
IF #c <= 1
BEGIN
INSERT INTO #Table (RevId) VALUES ('E5')
END
SET #C = #C+ 1
END
--save starting counts of test data to temp table to compare with later
IF OBJECT_ID('tempdb..#StartingCounts') IS NOT NULL
BEGIN
DROP TABLE #StartingCounts
END
SELECT
RevId
,COUNT(*) as Occurences
INTO #StartingCounts
FROM
#Table
GROUP BY
RevId
ORDER BY
RevId
/************************ This is the main method **********************************/
--clear temp table that is the main processing logic
IF OBJECT_ID('tempdb..#RowNumsToChange') IS NOT NULL
BEGIN
DROP TABLE #RowNumsToChange
END
--figure out how many records there are and how many there should be for each RevId
;WITH cteTargetNumbers AS (
SELECT
RevId
--,COUNT(*) as RevIdCount
--,SUM(COUNT(*)) OVER (PARTITION BY 1) / COUNT(*) OVER (PARTITION BY 1) +
--CASE
--WHEN ROW_NUMBER() OVER (PARTITION BY 1 ORDER BY NEWID()) <=
--SUM(COUNT(*)) OVER (PARTITION BY 1) % COUNT(*) OVER (PARTITION BY 1)
--THEN 1
--ELSE 0
--END as TargetNumOfRecords
,SUM(COUNT(*)) OVER (PARTITION BY 1) / COUNT(*) OVER (PARTITION BY 1) +
CASE
WHEN ROW_NUMBER() OVER (PARTITION BY 1 ORDER BY NEWID()) <=
SUM(COUNT(*)) OVER (PARTITION BY 1) % COUNT(*) OVER (PARTITION BY 1)
THEN 1
ELSE 0
END - COUNT(*) AS NumRecordsToUpdate
FROM
#Table
GROUP BY
RevId
)
, cteEndRowNumsToChange AS (
SELECT *
,SUM(CASE WHEN NumRecordsToUpdate > 1 THEN NumRecordsToUpdate ELSE 0 END)
OVER (PARTITION BY 1 ORDER BY RevId) AS ChangeEndRowNum
FROM
cteTargetNumbers
)
SELECT
*
,LAG(ChangeEndRowNum,1,0) OVER (PARTITION BY 1 ORDER BY RevId) as ChangeStartRowNum
INTO #RowNumsToChange
FROM
cteEndRowNumsToChange
;WITH cteOriginalTableRowNum AS (
SELECT
RevId
,ROW_NUMBER() OVER (PARTITION BY RevId ORDER BY (SELECT 0)) as RowNumByRevId
FROM
#Table t
)
, cteRecordsAllowedToChange AS (
SELECT
o.RevId
,o.RowNumByRevId
,ROW_NUMBER() OVER (PARTITION BY 1 ORDER BY (SELECT 0)) as ChangeRowNum
FROM
cteOriginalTableRowNum o
INNER JOIN #RowNumsToChange t
ON o.RevId = t.RevId
AND t.NumRecordsToUpdate < 0
AND o.RowNumByRevId <= ABS(t.NumRecordsToUpdate)
)
UPDATE o
SET RevId = u.RevId
FROM
cteOriginalTableRowNum o
INNER JOIN cteRecordsAllowedToChange c
ON o.RevId = c.RevId
AND o.RowNumByRevId = c.RowNumByRevId
INNER JOIN #RowNumsToChange u
ON c.ChangeRowNum > u.ChangeStartRowNum
AND c.ChangeRowNum <= u.ChangeEndRowNum
AND u.NumRecordsToUpdate > 0
IF OBJECT_ID('tempdb..#RowNumsToChange') IS NOT NULL
BEGIN
DROP TABLE #RowNumsToChange
END
/***************************** End of Main Method *******************************/
-- Compare the results and clean up
;WITH ctePostUpdateResults AS (
SELECT
RevId
,COUNT(*) as AfterChangeOccurences
FROM
#Table
GROUP BY
RevId
)
SELECT *
FROM
#StartingCounts s
INNER JOIN ctePostUpdateResults r
ON s.RevId = r.RevId
ORDER BY
s.RevId
IF OBJECT_ID('tempdb..#StartingCounts') IS NOT NULL
BEGIN
DROP TABLE #StartingCounts
END
Since you've given no rules for how you'd like the balance to operate we're left to speculate. Here's an approach that would find the most overrepresented value and then find an underrepresented value that can take on the entire overage.
I have no idea how optimal this is and it will probably run in an infinite loop without more logic.
declare #balance int = 125;
declare #cnt_over int;
declare #cnt_under int;
declare #revID_overrepresented varchar(32);
declare #revID_underrepresented varchar(32);
declare #rowcount int = 1;
while #rowcount > 0
begin
select top 1 #revID_overrepresented = RevID, #cnt_over = count(*)
from T
group by RevID
having count(*) > #balance
order by count(*) desc
select top 1 #revID_underrepresented = RevID, #cnt_under = count(*)
from T
group by RevID
having count(*) < #balance - #cnt_over
order by count(*) desc
update top #cnt_over - #balance T
set RevId = #revID_underrepresented
where RevId = #revID_overrepresented;
set #rowcount = ##rowcount;
end
The problem is I don't even know what you mean by balance...You say it needs to be evenly represented but it seems like you want it to be 125. 125 is not "even", it is just 125.
I can't tell what you are trying to do, but I'm guessing this is not really an SQL problem. But you can use SQL to help. Here is some helpful SQL for you. You can use this in your language of choice to solve the problem.
Find the rev values and their counts:
SELECT RevID, COUNT(*)
FROM MyTable
GROUP BY MyTable
Update #X rows (with RevID of value #RevID) to a new value #NewValue
UPDATE TOP #X FROM MyTable
SET RevID = #NewValue
WHERE RevID = #RevID
Using these two queries you should be able to apply your business rules (which you never specified) in a loop or whatever to change the data.

How can I update a column with row_number in SQL Server?

How can I udpate a column with row_number in SQL Server 2008 R2?
BEGIN TRANSACTION
DECLARE #count int
DECLARE #maxcount int
SET #count = 1
SET #maxcount = (SELECT count(*)
FROM Applicant_Detail ad
WHERE ad.identification_code = 1)
PRINT #maxcount
WHILE (#count<#maxcount)
BEGIN
UPDATE ad
SET ad.NRIC_nbr = s.myRowNumber
FROM Applicant_Detail ad
INNER JOIN (
SELECT ROW_NUMBER() OVER (ORDER BY NRIC_nbr ASC) AS myRowNumber
FROM Applicant_Detail ad
)S
ON s.myRowNumber = #count
SET #count = #count+1
END
This query takes a lot of time. I do not have any column in the applicant_detail table which has sequential data? I use the count logic but takes lot of time?
What i want?
Update the column of the table with sequential data like 1,2,3,4,5,6,7,8,9...... max row of the table?
Try this:
declare #count int = (select count(1) from Applicant_Detail)
;with cte as
(select *, row_number() over (order by #count) rn
from Applicant_Detail)
update cte
set NRIC_nbr = rn
select * from Applicant_Detail
Demo
Solution for this problem
BEGIN TRANSACTION
;WITH numbering AS
( SELECT AD.NRIC_nbr,
AD.application_number,
ROW_NUMBER() OVER(ORDER BY AD.application_number) AS ROWNUMBER
FROM Applicant_Detail ad WHERE AD.identification_code=1
)
UPDATE numbering
SET NRIC_nbr=ROWNUMBER
when you want to update the column with row_numebr. it is the perfect solution
WITH TEMP AS
(
SELECT id as rid,
ROW_NUMBER() OVER (ORDER BY [ID] ASC) AS RN
FROM ORG
)
update ORG Set columnname1 ='200'+(select RN FROM TEMP where TEMP.rid=ORG.id)

sql server 2008, cannot use order by in subquery

here's my sql server 2008 stored procedure.
ALTER PROCEDURE [dbo].[GetSharedSmoothies]
#Page INT ,
#Status INT ,
#ItemPerPage INT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #X INT
DECLARE #Y INT
SET #X = ( #Page - 1 ) * #ItemPerPage
SET #Y = #Page * #ItemPerPage
SELECT *
FROM ( SELECT S.* ,
U.Avatar ,
U.Displayname ,
( SELECT COUNT(Id)
FROM Vote
WHERE Vote.SmoothieId = S.Id
) AS Votes ,
ROW_NUMBER() OVER ( ORDER BY S.Id ) rownum
FROM dbo.Smoothie AS S
INNER JOIN dbo.[User] AS U ON S.UserId = U.Id
WHERE S.IsPublic = 1
AND S.Status = 3
AND S.UserId > 0
-- ORDER BY S.CreatedDate DESC
) seq
WHERE seq.rownum BETWEEN #X AND #Y
ORDER BY seq.rownum
END
in my code, you will see I comment out the order by
-- ORDER BY S.CreatedDate DESC
because order by will not work in subquery. i need to show the lastest one on the top. is there a way I can use order by in my code?
You may add S.CreatedDate within the Row_NUMBER()
ROW_NUMBER() OVER (PARTITION BY S.Id ORDER BY S.CreatedDate DESC) AS RowNum
That's right. It is not allowed, because it will do nothing.
Having the latest one at the top in the subquery will do nothing to the result set using the subquery.
Add the needed column to the result set ORDER BY:
ORDER BY seq.CreatedDate DESC, seq.rownum
Or:
ORDER BY seq.rownum, seq.CreatedDate DESC

fill empty column with sequence

Is there any way to fill an empty column with a ascending sequence of numbers starting from 1, without using identity?
I tried the following cursor but it is filling the same value (450) for all rows in the column:
declare cur3 cursor for
select new_id from sheet1$
declare #no int
declare #no1 int
set #no1 = 1
open cur3
fetch next from cur3 into #no
while(##FETCH_STATUS = 0)
begin
update sheet1$ set new_id = #no1
set #no1 = #no1 + 1
fetch next from cur3 into #no
end
close cur3
deallocate cur3
The problem is that you are missing a WHERE clause from your update query, so each loop is updating all rows. It should be:
UPDATE sheet1$
SET new_id = #no1
WHERE New_ID = #No -- ONLY UPDATE 1 ROW
Just as an aside, and assuming your SQL-Server tag is correct, you could do this without a cursor using the ROW_NUMBER() Function
WITH CTE AS
( SELECT New_ID, [RN] = ROW_NUMBER() OVER(ORDER BY New_ID)
FROM yourTable
)
UPDATE CTE
SET New_ID = RN
EDIT - EXPLANATION
ROW_NUMBER simply provides a sequence of numbers, the Common Table Expression is like a dynamic view:
If you run:
WITH CTE AS
( SELECT New_ID, [RN] = ROW_NUMBER() OVER(ORDER BY New_ID)
FROM yourTable
)
SELECT *
FROM CTE
you should get a better idea of what is being done within the CTE, then the beauty of CTEs is that you can UPDATE them directly without having to reference back to the original table, so Updating the CTE is equivalent to:
UPDATE yourTable
SET New_ID = RN
FROM yourTable
INNER JOIN
( SELECT New_ID, [RN] = ROW_NUMBER() OVER(ORDER BY New_ID)
FROM yourTable
) n
ON n.New_ID = yourTable.New_ID;
Yep, ROW_NUMBER.
Use like the following :
select ROW_NUMBER()OVER(ORDER BY Somefield) As Id
,*
from SomeTable
You can read all about it : http://msdn.microsoft.com/en-us/library/ms186734(v=sql.105).aspx