继我在2014年的回答之后,我最终回到了这个问题,并建立在我之前的方法和其致命缺陷基础上。我提出了以下解决方案,使用SQL Server存储过程来展示逻辑。
首先,是电影表:
CREATE TABLE [dbo].[usermovies]
([userid] [int], [movieid] [int], [position] [int], [subposition] [int])
还有测试数据。请注意,当我们加载数据时,初始电影排名设置在位置列中,子位置设置为0:
insert into usermovies (userid, movieid, position, subposition)
values (123, 99, 1, 0)
,(123, 98, 2, 0)
,(123, 97, 3, 0)
,(123, 96, 4, 0)
,(123, 95, 5, 0)
,(123, 94, 6, 0)
,(987, 99, 1, 0)
,(987, 98, 2, 0)
,(987, 97, 3, 0)
,(987, 96, 4, 0)
,(987, 95, 5, 0)
,(987, 94, 6, 0)
重要的是要理解每部电影(movierank)的排名不是根据位置值确定的,而是根据记录按位置和次位置排序后行的排名确定的。我创建了一个视图来提供movierank:
CREATE OR ALTER VIEW vwUserMoviesWithRank
as
with userMoviesWithRanks as (
SELECT *
, dense_rank() over (partition by userid order by position asc, subposition asc) as movierank
FROM usermovies
)
SELECT * FROM userMoviesWithRanks
GO
每个用户只能有一个具有给定位置/子位置值的电影,因为这提供了唯一的电影排名。向表中添加一个唯一的聚集索引可以很好地强制执行此规则,并且在有足够数据的情况下,还可以实现更快的数据访问。
CREATE UNIQUE CLUSTERED INDEX [IX_usermovies]
ON [dbo].[usermovies] ([userid] ASC, [position] ASC, [subposition] ASC)
下面的存储过程执行更新操作,允许用户更改电影排名。我已经添加了注释以帮助解释逻辑:
CREATE OR ALTER PROC proc_ChangeUserMovieRank
@userID int,
@movieID int,
@moveToRank int
as
DECLARE @moveFromRank int
DECLARE @movieIDAtNewRank int
DECLARE @positionAtNewRank int
DECLARE @subpositionAtNewRank int
IF @moveToRank<1 THROW 51000, '@moveToRank must be >= 1', 1;
BEGIN TRAN
SELECT @moveFromRank=movierank FROM vwUserMoviesWithRank WHERE userid=@userID and movieid=@movieID
IF @moveFromRank<>@moveToRank BEGIN
SELECT @positionAtNewRank=position, @subpositionAtNewRank=subposition
FROM vwUserMoviesWithRank
WHERE userid=@userID and movierank=(@moveToRank + CASE WHEN @moveToRank>@moveFromRank THEN 1 ELSE 0 END)
IF @positionAtNewRank IS NULL BEGIN
SELECT @positionAtNewRank=max(p.position)+1, @subpositionAtNewRank=0
FROM vwUserMoviesWithRank p WHERE p.userid=@userID
END ELSE BEGIN
UPDATE m
SET subposition=subposition+1
FROM usermovies m
WHERE userid=@userID AND position=@positionAtNewRank and subposition>=@subpositionAtNewRank
END
UPDATE m
SET position=@positionAtNewRank, subposition=@subpositionAtNewRank
FROM usermovies m
WHERE m.userid=@userID AND m.movieid=@movieID
END
COMMIT TRAN
GO
这是使用上述测试数据进行的测试运行。电影列表使用以下SELECT语句列出,为了简洁起见,我没有在下面每次重复。这是我们的电影排名:
SELECT movieid, movierank FROM vwUserMoviesWithRank WHERE userid=123 ORDER BY movierank
movieid movierank
99 1
98 2
97 3
96 4
95 5
94 6
将电影98移动到第5名:
EXEC proc_ChangeUserMovieRank @userID=123, @movieID=98, @moveToRank=5
movieid movierank
----------- --------------------
99 1
97 2
96 3
95 4
98 5
94 6
将电影94移至排名第2:
EXEC proc_ChangeUserMovieRank @userID=123, @movieID=94, @moveToRank=2
movieid movierank
99 1
94 2
97 3
96 4
95 5
98 6
将电影95移到排名第1位:
EXEC proc_ChangeUserMovieRank @userID=123, @movieID=95, @moveToRank=1
movieid movierank
----------- --------------------
95 1
99 2
94 3
97 4
96 5
98 6
将电影99移动到第4名:
EXEC proc_ChangeUserMovieRank @userID=123, @movieID=99, @moveToRank=4
movieid movierank
95 1
94 2
97 3
99 4
96 5
98 6
将电影97移动到第6名:
EXEC proc_ChangeUserMovieRank @userID=123, @movieID=97, @moveToRank=6
movieid movierank
95 1
94 2
99 3
96 4
98 5
97 6
将电影97移动到排名4:
EXEC proc_ChangeUserMovieRank @userID=123, @movieID=97, @moveToRank=4
movieid movierank
95 1
94 2
99 3
97 4
96 5
98 6
将电影95移动到第4名:
EXEC proc_ChangeUserMovieRank @userID=123, @movieID=95, @moveToRank=4
movieid movierank
94 1
99 2
97 3
95 4
96 5
98 6
我认为这一切看起来都很好。
请注意,在执行这些操作后,位置/子位置数据现在如下所示:
select * from vwUserMoviesWithRank WHERE userid=123 order by movierank
userid movieid position subposition movierank
----------- ----------- ----------- ----------- --------------------
123 94 3 0 1
123 99 4 0 2
123 97 4 1 3
123 95 4 2 4
123 96 4 3 5
123 98 6 0 6
这些值与确定的电影排名相差很大。
当电影排名发生变化时,该位置可能会在多行中变得相同,例如上面的第4个位置。当发生这种情况时,排名变化时需要更新更多的行,因此建议定期将位置和子位置重置为movierank值:
UPDATE usermovies
SET position=vwUserMoviesWithRank.movierank, subposition=0
FROM vwUserMoviesWithRank
INNER JOIN usermovies on usermovies.userid=vwUserMoviesWithRank.userid AND usermovies.movieid=vwUserMoviesWithRank.movieid
WHERE usermovies.position<>vwUserMoviesWithRank.movierank OR usermovies.subposition<>0
这个非常高效,而且能够很好地扩展,我认为所有的都运行正常,如果你有不同看法,请告诉我,我会再次查看(这次我不会等待8年!)
另外,我想指出我尝试在这里添加一个 SQL Fiddle 链接,但似乎他们目前没有 SQL Server 主机 :-/