How can I select the top 'n' records w/o a cursor where 'n' is a value in a table? I've constructed two sample tables and a udf (below) that shows what I'm trying (unsuccessfully) to do.
Essentially, I'm trying to allow my users to rank players (football) and then define how many of the top ranked players they would like to see. Here is my code (you should be able to paste it into your query analyzer and run it.
Thanks!
--**************************************
--snip
--**************************************
use comm2
set nocount on
--first create a table that will hold player
--scores per user, i.e., users
--will score the same player differently
drop table tblRating
create table dbo.tblRating
(
idUser int,
idPlayer int,
nmScore numeric
)
go
insert into tblRating(idUser, idPlayer, nmScore)
select 1, 1, 100
insert into tblRating(idUser, idPlayer, nmScore)
select 1, 2, 103
insert into tblRating(idUser, idPlayer, nmScore)
select 1, 3, 95
insert into tblRating(idUser, idPlayer, nmScore)
select 1, 4, 10
insert into tblRating(idUser, idPlayer, nmScore)
select 2, 1, 80
insert into tblRating(idUser, idPlayer, nmScore)
select 2, 2, 107
insert into tblRating(idUser, idPlayer, nmScore)
select 2, 3, 200
insert into tblRating(idUser, idPlayer, nmScore)
select 2, 4, 99
--now create a user table that shows how many
--players they want to rank
--in this example user '1' only want to see the
--top 2 players per his score system
--and user '2' wants to see the top 3 per his system
drop table tblUser
create table tblUser
(
idUser int,
selectTopN int
)
insert into tblUser(idUser, selectTopN)
select 1, 2
insert into tblUser(idUser, selectTopN)
select 2, 3
--now create a function that gives each player a
--ranking, e.g., 1, 2, 3 based upon
--the rank order of score per user
drop function udf_rankPlayers
go
create FUNCTION dbo.udf_rankPlayers
(
@idUser int
)
RETURNS @t TABLE
(
idUser int,
idRank int identity(1,1),
idPlayer int,
nmScore numeric
)
AS
BEGIN
INSERT @t (idUser, idPlayer, nmScore)
select idUser, idPlayer, nmScore
from tblRating
where idUser = @idUser
order by nmScore desc
RETURN
END
GO
--now use the function to retreive
--the rankings for any user.
--This works fine, but isn't quite what I want
declare @idUser int
select @idUser = 1
select *
from dbo.udf_rankPlayers(@idUser)
where idRank <= 2
--Below is what I want. Note that I get
--multiple rows for each row in tblUser,
--i.e., each user gets their own rankings (some
--may want to see the top 2, top 3 ... top n)
--based upon their player scores.
/*
select *
from tblUser u
cross join (
select *
from dbo.udf_rankPlayers(u.idUser)
) z
*/
--I am open to workarounds, but I DON'T want a
--cursor--too slow, in the real system
--there are ~50 thousand users
--and ~500 players and I don't
--want to open up a 50,000 X 500
--= 25,000,000 million record cursor
--I don't want to open a cursor at all
--if it is avoidable
--FYI, I have experimented with a query
--that coalesces the udf into one row
--of comma delimited values and returned
--it in the sql select. This got
--all the idPlayers per user into one
--comma delimited string, but then I had
--to use a 50,000 record cursor on
--tblUser to build and exec stmt with the
--comma delimited string--MESSY AND SLOW. I need better.
--**************************************
--snip
--**************************************
declare @idUser1 int
declare @list varchar(255), @sql varchar(512)
declare tUser cursor for
select idUser
from tblUser
OPEN tUser
FETCH NEXT FROM tUser INTO @idUser1
WHILE @@FETCH_STATUS = 0
BEGIN
select @list = coalesce(@list + ', ', '') + cast(idPlayer as varchar(2))
from dbo.udf_rankPlayers(@idUser1) r
join tblUser u
on u.idUser = r.idUser
where r.idRank <= selectTopN
select @sql = 'select idUser, idPlayer from tblRating where idPlayer in (' + @list + ') and idUser = ' + cast(@idUser1 as varchar(2)) + ' order by nmScore desc'
exec(@sql)
set @list = null
FETCH NEXT FROM tUser INTO @idUser1
END
CLOSE tUser
DEALLOCATE tUser
Essentially, I'm trying to allow my users to rank players (football) and then define how many of the top ranked players they would like to see. Here is my code (you should be able to paste it into your query analyzer and run it.
Thanks!
--**************************************
--snip
--**************************************
use comm2
set nocount on
--first create a table that will hold player
--scores per user, i.e., users
--will score the same player differently
drop table tblRating
create table dbo.tblRating
(
idUser int,
idPlayer int,
nmScore numeric
)
go
insert into tblRating(idUser, idPlayer, nmScore)
select 1, 1, 100
insert into tblRating(idUser, idPlayer, nmScore)
select 1, 2, 103
insert into tblRating(idUser, idPlayer, nmScore)
select 1, 3, 95
insert into tblRating(idUser, idPlayer, nmScore)
select 1, 4, 10
insert into tblRating(idUser, idPlayer, nmScore)
select 2, 1, 80
insert into tblRating(idUser, idPlayer, nmScore)
select 2, 2, 107
insert into tblRating(idUser, idPlayer, nmScore)
select 2, 3, 200
insert into tblRating(idUser, idPlayer, nmScore)
select 2, 4, 99
--now create a user table that shows how many
--players they want to rank
--in this example user '1' only want to see the
--top 2 players per his score system
--and user '2' wants to see the top 3 per his system
drop table tblUser
create table tblUser
(
idUser int,
selectTopN int
)
insert into tblUser(idUser, selectTopN)
select 1, 2
insert into tblUser(idUser, selectTopN)
select 2, 3
--now create a function that gives each player a
--ranking, e.g., 1, 2, 3 based upon
--the rank order of score per user
drop function udf_rankPlayers
go
create FUNCTION dbo.udf_rankPlayers
(
@idUser int
)
RETURNS @t TABLE
(
idUser int,
idRank int identity(1,1),
idPlayer int,
nmScore numeric
)
AS
BEGIN
INSERT @t (idUser, idPlayer, nmScore)
select idUser, idPlayer, nmScore
from tblRating
where idUser = @idUser
order by nmScore desc
RETURN
END
GO
--now use the function to retreive
--the rankings for any user.
--This works fine, but isn't quite what I want
declare @idUser int
select @idUser = 1
select *
from dbo.udf_rankPlayers(@idUser)
where idRank <= 2
--Below is what I want. Note that I get
--multiple rows for each row in tblUser,
--i.e., each user gets their own rankings (some
--may want to see the top 2, top 3 ... top n)
--based upon their player scores.
/*
select *
from tblUser u
cross join (
select *
from dbo.udf_rankPlayers(u.idUser)
) z
*/
--I am open to workarounds, but I DON'T want a
--cursor--too slow, in the real system
--there are ~50 thousand users
--and ~500 players and I don't
--want to open up a 50,000 X 500
--= 25,000,000 million record cursor
--I don't want to open a cursor at all
--if it is avoidable
--FYI, I have experimented with a query
--that coalesces the udf into one row
--of comma delimited values and returned
--it in the sql select. This got
--all the idPlayers per user into one
--comma delimited string, but then I had
--to use a 50,000 record cursor on
--tblUser to build and exec stmt with the
--comma delimited string--MESSY AND SLOW. I need better.
--**************************************
--snip
--**************************************
declare @idUser1 int
declare @list varchar(255), @sql varchar(512)
declare tUser cursor for
select idUser
from tblUser
OPEN tUser
FETCH NEXT FROM tUser INTO @idUser1
WHILE @@FETCH_STATUS = 0
BEGIN
select @list = coalesce(@list + ', ', '') + cast(idPlayer as varchar(2))
from dbo.udf_rankPlayers(@idUser1) r
join tblUser u
on u.idUser = r.idUser
where r.idRank <= selectTopN
select @sql = 'select idUser, idPlayer from tblRating where idPlayer in (' + @list + ') and idUser = ' + cast(@idUser1 as varchar(2)) + ' order by nmScore desc'
exec(@sql)
set @list = null
FETCH NEXT FROM tUser INTO @idUser1
END
CLOSE tUser
DEALLOCATE tUser