【问题标题】:database design for song position under play-list播放列表下歌曲位置的数据库设计
【发布时间】:2019-01-08 10:08:37
【问题描述】:

我有一个音乐流媒体应用程序,其中有播放列表。每个播放列表最多有 100 首歌曲。我有一个名为PlaylistSongMapping 的映射表,它具有以下架构

+------+------------+--------+
|SongId| PlaylistId |Position|
+------+------------+--------+
|   1  |     10     |    2   |
|   2  |     10     |    1   |
|   3  |     10     |    3   | 
|   5  |     10     |    4   | 
|   6  |     11     |    1   |
+------+------------+--------+

使用Position 列管理播放列表中每首歌曲的位置。在播放列表中,我需要订单更改功能。当前的逻辑是更新Position 列。 问题是如果我需要将带有SongId: 3 的歌曲移动到第一个位置,我需要更新SongId 1,2,3 的行。当播放列表中有更多歌曲时,这个数字会很大。是否有更好的逻辑使更新查询的数量非常少。

【问题讨论】:

  • 一个好的RDBMS,一个设计良好的数据库,不太可能同时更新几(百)行。但是,您还没有在这里提出问题;它是什么?关于数据库设计的问题最好放在dba; Stack Overflow 是针对关于编码的特定问题。您还标记了 2 个不同的 RDBMS、MySQL 和 SQL Server。这是 2 种截然不同的产品,所以我建议您仔细检查一下您真正使用的是哪一种。

标签: sql sql-server database-design


【解决方案1】:

如果您在移动位置时使用更新表中所有相关位置的 SP,这应该可以完成工作:

USE Sandbox;
GO

--Small sample set
CREATE TABLE dbo.Playlist (SongID int,
                           PlaylistID int,
                           Position int);
INSERT INTO dbo.Playlist (SongID,
                      PlaylistID,
                      Position)
VALUES(1,1,1),
      (3,1,2),
      (5,1,3),
      (2,1,4),
      (2,2,1),
      (10,2,2);
GO

--Check data
SELECT *
FROM dbo.Playlist;
GO

--CREATE the SP
CREATE PROC dbo.MoveSongPosition @PlaylistID int,
                             @SongID int,
                             @NewPosition int
AS BEGIN

    UPDATE P
    SET Position = CASE WHEN SongID = @SongID THEN @NewPosition ELSE Position +1 END
    FROM dbo.Playlist P
    WHERE P.PlaylistID = @PlaylistID
      AND (P.SongID = @SongID
       OR  P.Position BETWEEN @NewPosition AND (SELECT sq.Position
                                               FROM dbo.Playlist sq
                                               WHERE sq.SongID = @SongID
                                                 AND sq.PlaylistID = @PlaylistID));
END
GO

--Run and test the SP
EXEC dbo.MoveSongPosition @PlaylistID = 1,
                          @SongID = 2,
                          @NewPosition = 2;
GO

--Check the new data
SELECT *
FROM dbo.Playlist;
GO

--Clean up
DROP PROC dbo.MoveSongPosition;
DROP TABLE dbo.Playlist;

此解决方案假定您使用的是 SQL Server;自从我发表评论以来,您还没有更新您的标签,所以我们不知道您真正使用的是什么 RDBM。

编辑:逻辑变化,相信我已经更正了。

CREATE PROC dbo.MoveSongPosition @PlaylistID int,
                                 @SongID int,
                                 @NewPosition int
AS BEGIN

    UPDATE P
    SET Position = CASE WHEN P.SongID = @SongID THEN @NewPosition
                        WHEN P.Position = @NewPosition THEN P.Position + V.Direction
                        WHEN P.Position < @NewPosition THEN P.Position - V.Direction
                        WHEN P.Position > @NewPosition THEN P.Position + V.Direction
                   END
    FROM dbo.Playlist P
         CROSS APPLY (SELECT ca.Position
                      FROM dbo.Playlist ca
                      WHERE ca.PlaylistID = P.PlaylistID
                        AND ca.SongID = @SongID) CS
         CROSS APPLY (VALUES(CASE WHEN CS.Position < @NewPosition THEN -1
                                  WHEN @NewPosition < CS.Position THEN 1
                                  ELSE 0 END)) V(Direction)
    WHERE P.PlaylistID = @PlaylistID
      AND ((P.Position >= @NewPosition AND P.Position <= CS.Position
       OR   (P.Position >= CS.Position AND P.Position <= @NewPosition)));
END

【讨论】:

  • 此方法可能会创建重复的职位。假设我将歌曲 8 移动到位置 2。我可能有两首歌曲具有相同的位置 2
  • @M14 同一个播放列表中的 2 首歌曲怎么会有相同的位置?那应该是不可能的。 2 首歌曲不能同时在播放列表中占据同一个位置。就像两辆车不能同时占用一个停车场的同一个空间一样。如果可以,那是您的设计存在缺陷。播放列表和位置的组合应该是唯一的,就像歌曲和播放列表一样。
  • 您的(小)样本数据没有显示这种情况或问题,也没有说明这是一种可能性。但无论哪种方式,@M14 我问,为什么播放列表1 在位置2 有两首歌(比如34)?当你听到那首歌时,你打算让两首歌同时播放吗?我猜那会让人听得很糟糕。 :D
  • 我的意思是,如果我们将歌曲 8 移动到位置 2,那么歌曲 8 和歌曲 1 将具有相同的位置,对吗?对不起,我没有正确理解逻辑。
  • 好像你还没有运行 SQL @M14;这就是为什么我以完全可消耗的格式提供它。我的示例明确将 Song 2 移动到 Position 2(之前由 Song 3 持有)。我已经为您创建了一个db<>fiddle,但是,我建议您在提出索赔之前运行您提供的 SQL。 :)
【解决方案2】:

“问题是如果我需要将 SongId: 3 的歌曲移到第一个 位置我需要更新 SongId 1,2,3"的行

你不应该需要两个以上的语句。

您可以根据需要使用增量/减量在一个语句中更新所有位置值(除了要移动到第一个位置的位置值),然后使用第二个语句来更新您要移动的特定歌曲 -例如

update PlaylistSongMapping 
set Position = Position + 1 
where 
  Playlistid = 10 
  and position < 3 and position >= 1; 

update PlaylistSongMapping 
set position = 1 
where songId = 3;

这假设您知道您正在移动的歌曲的 Id(因此您可以确定它的当前位置,这是第一个查询中 where 子句所需要的) - 但您应该始终知道,根据您的描述。

请注意,在上面的示例中,position &gt;= 1 不是绝对必要的,因为 1 是列表中的第一个位置,但此代码旨在涵盖更一般的情况 - 例如如果您想将其移动到位置 2,则您不希望将位置 1 的歌曲包含在增量中,因此在这种情况下,您将改为写 position &gt;= 2

【讨论】:

  • 两首歌曲的位置可能相同(如果我将歌曲 3 移动到位置 2)
  • 如果您想将歌曲 3 移动到位置 2,那么您应该写 update PlaylistSongMapping set Position = Position + 1 where Playlistid = 10 and position &lt; 3 and position &gt;= 2; - 再次,查询是根据您对想要进行的移动的了解完成的。我根据您提供的信息专门回答了,但是更一般的情况是允许将事物移动到列表的中间,而不是开头或结尾。我已经更新了答案以反映这一点
【解决方案3】:

理论上更优化(但难度更大)

如果您将该列视为排序顺序而不是确切位置,您可以采取一些有间隙的策略,例如编号为 10、20、30。然后,如果您想将第 3 首歌曲移到第 1 首位置,你可以给它数字 5,或者你甚至可以给它一个负数。

问题是您仍然需要读取所有数据,并且编号的实现变得更加复杂,因为现在您需要检查是否需要更新任何内容,或者所有内容,或者只是一个列表的一部分。

很简单(基本上就是你现在拥有的)

所以,如果我是你,我会保持简单,只更新需要更新的所有内容。毕竟,每个播放列表没有那么多行(即使每个播放列表允许 1000 首或更多歌曲),如果您进行批量更新,数据库应该能够处理得很好。您可以一次移动所有行,然后将其中一行更新到新位置。这样,您只需要两个语句:1 插入、更新或删除特定歌曲,1 移动整个或部分列表以保持编号顺序。

非常简单(到目前为止可能仍然足够快)

在过去,我以一种更懒惰的方式实现了这一点,我保持了一个有间隙的排序顺序,有 2、4、6、8 ......然后,当我想更新或插入某个位置,我可以只使用sortorder = position*2-1 插入或更新行,或者干脆删除任何行:

update Song
set 
  SortOrder = :NewPosition*2-1
where
  SortOrder = :OldPosition*2;

之后,我只是简单地更新了所有行以再次修复编号,无论我的修改在哪里,根据排序顺序生成一个新序列。
这意味着我对已经正确的行进行了冗余更新,但这真的非常简单,仍然非常快(因为数据库擅长这样的事情),并且它也有一些自动修复效果,因为整个清单每次都会重新编号,纠正过去的任何错误。 它确实取决于您的数据库如何准确地实现它。我使用的是 Oracle,它非常好并且可以生成这样的序列。在 MySQL 中它稍微麻烦一些,但仍然没有那么难。

【讨论】:

    猜你喜欢
    • 2015-10-12
    • 1970-01-01
    • 2012-05-15
    • 2016-02-25
    • 1970-01-01
    • 2016-07-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多