25

设想:

有一个用户拥有的电影数据库,电影显示在一个名为“我的电影”的页面上,可以按照用户想要的顺序显示电影。例如位置#1 的“搏击俱乐部”,位置#3 的“Drive”等等。

显而易见的解决方案是为每个项目存储一个位置,例如:

电影ID,用户ID,位置
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3

然后在输出数据时按位置排序。此方法适用于输出,但是在更新时存在问题:项目的位置所有其他位置都需要更新,因为位置是相对的。如果电影 #3 现在位于位置 2,则现在需要将电影 #3 更新到位置 #2。如果数据库包含 10,000 部电影,并且电影从位置 #1 移动到位置 #9999,那么几乎有 10,000 行需要更新!

我唯一的解决方案是单独存储定位,而不是为每个项目位置设置一个单独的字段,它只是在运行时获取并与每个项目(json、xml 等)相关联的位置的一个大数据转储,但感觉.. . 效率低下,因为不能让数据库进行排序。

我总结的问题:将项目位置存储在对获取和更新友好的列表中的最有效方法是什么?

4

5 回答 5

17

如果您使用用户将电影放置在给定位置的位置和时间戳的组合,而不是尝试保持实际位置,那么您可以实现一种相当简单的方法来选择和更新数据。例如; 一组基本数据:

create table usermovies (userid int, movieid int, position int, positionsetdatetime datetime)

insert into usermovies (userid, movieid, position, positionsetdatetime)
values (123, 99, 1, getutcdate())
insert into usermovies (userid, movieid, position, positionsetdatetime)
values (123, 98, 2, getutcdate())
insert into usermovies (userid, movieid, position, positionsetdatetime)
values (123, 97, 3, getutcdate())
insert into usermovies (userid, movieid, position, positionsetdatetime)
values (123, 96, 4, getutcdate())
insert into usermovies (userid, movieid, position, positionsetdatetime)
values (123, 95, 5, getutcdate())
insert into usermovies (userid, movieid, position, positionsetdatetime)
values (123, 94, 6, getutcdate())

insert into usermovies (userid, movieid, position, positionsetdatetime)
values (987, 99, 1, getutcdate())
insert into usermovies (userid, movieid, position, positionsetdatetime)
values (987, 98, 2, getutcdate())
insert into usermovies (userid, movieid, position, positionsetdatetime)
values (987, 97, 3, getutcdate())
insert into usermovies (userid, movieid, position, positionsetdatetime)
values (987, 96, 4, getutcdate())
insert into usermovies (userid, movieid, position, positionsetdatetime)
values (987, 95, 5, getutcdate())
insert into usermovies (userid, movieid, position, positionsetdatetime)
values (987, 94, 6, getutcdate())

如果您使用如下查询查询用户的电影:

;with usermovieswithrank as (
  select userid
  , movieid 
  , dense_rank() over (partition by userid order by position asc, positionsetdatetime desc) as movierank
  from usermovies
)
select * from usermovieswithrank where userid=123 order by userid, movierank asc

然后你会得到预期的结果:

USERID  MOVIEID     MOVIERANK
123     99          1
123     98          2
123     97          3
123     96          4
123     95          5
123     94          6

要移动电影的排名之一,我们需要更新 position 和 positionsetdatetime 列。例如,如果用户 ID 123 将电影 95 从排名 5 移动到排名 2,那么我们这样做:

update usermovies set position=2, positionsetdatetime=getutcdate() 
where userid=123 and movieid=95 

这导致了这个(在更新之后使用上面的 SELECT 查询):

USERID  MOVIEID     MOVIERANK
123     99          1
123     95          2
123     98          3
123     97          4
123     96          5
123     94          6

然后如果用户 ID 123 将电影 96 移动到排名 1:

update usermovies set position=1, positionsetdatetime=getutcdate()
where userid=123 and movieid=96 

我们得到:

USERID  MOVIEID     MOVIERANK
123     96          1
123     99          2
123     95          3
123     98          4
123     97          5
123     94          6

当然,您最终会在 usermovies 表中得到重复的位置列值,但是使用这种方法,您将永远不会显示该列,您只需将其与 positionsetdatetime 一起使用来确定每个用户的排序排名,您确定的排名是真实的位置。

如果您希望位置列在不参考 positionsetdatetime 的情况下正确反映电影排名,您可以使用上面选择查询中的 movierank 来更新 usermovies 位置列值,因为它实际上不会影响确定的电影排名。

于 2014-06-13T15:45:08.193 回答
11

我一直在努力解决如何最好地处理这种情况,并意识到BY FAR最好的解决方案是按照您想要的顺序排列电影的列表/数组,例如;

用户 ID、电影订单

1 : [4,3,9,1...]

显然你会序列化你的数组。

“感觉……效率低下”?

考虑用户有一个包含 100 部电影的列表。按位置搜索将是一个数据库查询,一个字符串到数组的转换,然后是 moviesOrder[index]。可能比直接数据库查找慢,但仍然非常非常快。

OTOH,考虑是否更改订单;

与数组拼接相比,存储在数据库中的位置最多需要 100 行更改。链表的想法很有趣,但并不能像展示的那样工作,如果单个元素失败,会破坏一切,而且看起来也慢了很多。其他一些想法,比如留出空隙,使用浮点数虽然很乱,但在某些时候很容易失败,除非你 GC。

似乎应该有更好的方法在 SQL 中执行此操作,但实际上没有。

于 2012-10-11T21:44:39.043 回答
4

存储订单链表样式。不是保存绝对位置,而是保存上一项的ID。这样,任何更改只需要您更新两行。

movieid | userid  | previousid
   1    |    1    | 
   2    |    1    |    1
   3    |    1    |    4
   4    |    1    |    2

为了让电影有序...

SELECT movieid WHERE userid = 1 ORDER BY previousid

-> 1, 2, 4, 3

(比如说)将#4 向上移动一个空格:

DECLARE @previousid int, @currentid int
SET @previousid = SELECT previousid FROM movies WHERE movieid = @currentid

-- current movie's previous becomes its preceding's preceding
UPDATE movies SET previousid = 
    (SELECT previousid FROM movies WHERE movieid = @previousid)
WHERE movieid = @currentid

-- the preceding movie's previous becomes the current one's previous
UPDATE movies SET previousid = @currentid WHERE movieid = @previousid

这仍然是 1 次读取 + 2 次写入,但超过了 10,000 次写入。

于 2012-06-19T04:30:47.113 回答
1
ID   NAME  POSITION
7     A       1
9     B       2
13    C       3
15    D       4
21    F       5

给定当前场景,如果我们想将项目 D 移动到位置 2,我们可以搜索 2(我们要移动项目的位置)和 4(项目的当前位置)之间的间隔,并编写查询 ADD +1 到此区间内每个元素的位置,因此在这种情况下,我们可以执行以下步骤:

  1. 在 position >= 2 AND position < 4 的区间内搜索项目,并将 +1 添加到其位置
  2. 将项目 D 位置设置为 2。

这将生成:A->1, B->3, C-> 4, D->2, F->5

如果我们想将 B 移动到 D,那么我们需要做相反的事情并应用 -1 代替。

  1. 在 position > 2 AND position <= 4 从其位置减去 -1 的区间中搜索项目
  2. 将项目位置设置为 4

从表中删除项目时,我们需要更新其位置大于被删除元素位置的每个项目。

并且在创建 Item 时,它的位置等于每个项目的 COUNT +1。

免责声明:如果您的金额非常大,则此解决方案可能不是您想要的,但在大多数情况下都可以。通常,用户不会将项目从位置 10000 移动到位置 2,但如果用户删除项目 1,那么查询会将 -1 减去剩余的 9999 个项目。如果这是您的情况,那么使用链表的解决方案可能是最适合您的解决方案,但是订购将更具挑战性,因为您需要逐项查看列表中的下一个。

示例查询

-- MOVE DOWN
UPDATE movie SET position = position-1  WHERE position <= 18 AND position > 13 AND id > 0;
UPDATE movie SET position = 18 WHERE id = 130;

-- MOVE UP
UPDATE movie SET position = position+1  WHERE position < 18 AND position >= 13 AND id > 0;
UPDATE movie SET position = 13 WHERE id = 130;
于 2020-10-21T23:40:52.580 回答
1

这里真的很有趣的解决方案。另一种可能性是用一些空间存储位置,比如 10 或 100 的倍数。

ID   NAME  POSITION
7     A       100
9     B       200
13    C       300
15    D       400
21    F       500

每次新增都可以完成这个 100 的倍数。然后将行 C 移动到位置 1,将是当前值的 -1 或当前值之后的 +1。甚至-50,以便将来可以实现。

ID   NAME  POSITION
7     A       100
9     B       200
13    C       50
15    D       400
21    F       500

这可以继续,并且在运动太多而不可能的情况下,再次对所有行进行重新排序。

于 2021-10-27T11:50:30.013 回答