繁体   English   中英

数据库中有序列表的最佳表示?

[英]Best representation of an ordered list in a database?

我知道这违反了关系数据库的原则,但让我描述一下这种情况。

我有一个页面,用户将在其中放置许多项目。

 ________________
| -Item1         |
| -Item2         |
| -Item3         |
| -Item4         |
|________________|

这些项目必须按照用户给它们的顺序排列。 然而,这个顺序可以被用户改变任意次数。

 ________________
| -Item1         |
| -Item4         |
| -Item2         |
| -Item3         |
|________________|

方法一

我最初的想法是给项目一个索引来代表他们在列表中的位置

Page           Item
-----------    ---------------
FK | pid       FK | pid 
   | name      PK | iid 
                  | index
                  | content 

使用此解决方案,您可以 select 项目where pid = Page.pid并按order by index这很方便。 但是,每次更改顺序时,您都必须在其他项目(最好的情况)和所有其他项目(最坏的情况)之间的任何位置进行更改。

方法二

我还考虑制作一个类似数据结构的“链表”,其中每个项目都指向列表中的下一个项目。

Page           Item
-----------    ---------------
FK | pid       FK | pid 
   | name      PK | iid 
                  | next
                  | content 

这可能会降低更改订单的成本,但我们将不得不依靠前端编程来提取订单。

有没有我没有想到的方法? 请告诉我。

解决方案:将index设为字符串(因为字符串本质上具有无限的“任意精度”)。 或者,如果您使用 int,则将index增加 100 而不是 1。

性能问题是这样的:两个排序项之间没有“中间”值。

item      index
-----------------
gizmo     1
              <<------ Oh no! no room between 1 and 2.
                       This requires incrementing _every_ item after it
gadget    2
gear      3
toolkit   4
box       5

相反,这样做(下面更好的解决方案):

item      index
-----------------
gizmo     100
              <<------ Sweet :). I can re-order 99 (!) items here
                       without having to change anything else
gadget    200
gear      300
toolkit   400
box       500

更好的是:这是 Jira 解决这个问题的方法。 他们的“排名”(你称之为索引)是一个字符串值,它允许在排名项目之间有大量的喘息空间。

这是我使用的 jira 数据库的真实示例

   id    | jira_rank
---------+------------
 AP-2405 | 0|hzztxk:
 ES-213  | 0|hzztxs:
 AP-2660 | 0|hzztzc:
 AP-2688 | 0|hzztzk:
 AP-2643 | 0|hzztzs:
 AP-2208 | 0|hzztzw:
 AP-2700 | 0|hzztzy:
 AP-2702 | 0|hzztzz:
 AP-2411 | 0|hzztzz:i
 AP-2440 | 0|hzztzz:r

注意这个例子hzztzz:i 字符串排序的优点是两个项目之间的空间不足,您仍然不必重新排序其他任何内容。 您只需开始向字符串附加更多字符以缩小焦点范围。

编辑:如评论中所述,您不能在0|hzztzz:0|hzztzz:a之间插入任何内容。 我想这就是为什么我看到 jira 的数据库会定期在末尾自动附加:i而不是:a以避免这种情况。 如果你真的想避免的问题,那么我认为你可以改变你的算法使每个时间(例如),你会插入:a底,你不是插:ai 通过这种方式,您在逻辑上保证没有排名会以字母a结尾——这应该意味着您将始终有“空间”来插入更多项目而无需重新订购任何东西。

我认为@a1ex07 在这里是正确的(+1)。 我不认为itemOrder差距违反 3NF,但我确实担心对 3NF 的不同违反(更多内容见下文)。 我们还必须注意itemOrder字段中的错误数据。 这是我的开始方式:

create table pages (
  pid int,
  primary key (pid)
);

create table users (
  uid int,
  primary key (uid)
);

create table items (
  iid int,
  primary key (iid)
);

create table details (
  pid int not null references pages(pid),
  uid int not null references users(uid),
  iid int not null references items(iid), 
  itemOrder int,
  primary key (pid, uid, iid),
  unique (pid, uid, itemOrder)
);

主键确保对于每个页面,对于每个用户,都有唯一的项目。 唯一约束确保对于每个页面,对于每个用户,都有唯一的 itemOrders。 这是我对 3NF 的担忧:在这种情况下, itemOrder并不完全依赖于主键; 它仅取决于(pid, uid)部分。 那甚至不是 2NF; 这是一个问题。 我们可以在主键中包含itemOrder ,但是我担心它可能不是最小的,因为 PK 需要如此。 我们可能需要将其分解为更多的表。 仍然在想 。 . .


[编辑 - 关于这个话题的更多思考。 . . ]

假设

  1. 有用户。

  2. 有页面。

  3. 有物品。

  4. (page, user) 标识一组项目。

  5. (page, user) 标识一个有序的插槽列表,如果我们愿意,我们可以在其中存储项目。

  6. 我们不希望在(页面,用户)的列表中有重复的项目。

计划A

杀死上面的details表。

添加一个表ItemsByPageAndUser ,以表示由 (page, user) 标识的项目集。

create table ItemsByPageAndUser (
   pid int not null references pages(pid),
   uid int not null references users(uid),
   iid int not null references items(iid),
  primary key (pid, uid, iid)   
)

添加表SlotsByPageAndUser以表示可能包含项目的插槽的有序列表。

create table SlotsByPageAndUser (
   pid       int not null references pages(pid),
   uid       int not null references users(uid),
   slotNum   int not null,
   iidInSlot int          references items(iid),
 primary key (pid, uid, slotNum),   
 foreign key (pid, uid, iid) references ItemsByPageAndUser(pid, uid, iid),
 unique (pid, uid, iid)
)

注 1iidInSlot为空,因此我们可以根据需要拥有空槽。 但是,如果存在项目,则必须对照项目表进行检查。

注意 2 :我们需要最后一个 FK 以确保我们不会为此(用户,页面)添加不在可能项目集中的任何项目。

注 3 :对(pid, uid, iid)的唯一约束强制执行我们的设计目标,即在列表中具有唯一项(假设 6)。 如果没有这个,我们可以从 (page,user) 标识的集合中添加任意数量的项目,只要它们在不同的插槽中即可。

现在我们已经很好地将项目与其插槽分离,同时保留了它们对(页面、用户)的共同依赖。

这个设计肯定是 3NF 并且可能是 BCNF,尽管我担心SlotsByPageAndUser在这方面。

问题在于,由于表SlotsByPageAndUser中的唯一约束, SlotsByPageAndUserItemsByPageAndUser之间关系的基数是一对一的。 通常,不是实体子类型的 1-1 关系是错误的。 当然,也有例外,也许这就是其中之一。 但也许有更好的方法。 . .

B计划

  1. SlotsByPageAndUser表。

  2. slotNum列添加到ItemsByPageAndUser

  3. ItemsByPageAndUser添加对(pid, uid, iid)的唯一约束。

现在是:

create table ItemsByPageAndUser (
   pid     int not null references pages(pid),
   uid     int not null references users(uid),
   iid     int not null references items(iid),
   slotNum int,
 primary key (pid, uid, iid),   
 unique (pid, uid, slotNum)
)

注意 4 :让slotNum空可以保留我们在集合中指定不在列表中的项目的能力。 但 。 . .

注 5 :对涉及可空列的表达式施加唯一约束可能会在某些数据库中导致“有趣”的结果。 我认为它会按照我们在 Postgres 中的预期工作。 (请参阅此处关于 SO 的讨论。)对于其他数据库,您的里程可能会有所不同。

现在没有混乱的 1-1 关系,所以更好。 它仍然是 3NF,因为唯一的非键属性 ( slotNum ) 取决于键、整个键,除了键之外什么都没有。 (你不能在不告诉我你在谈论什么页面、用户和项目的情况下询问slotNum 。)

它不是 BCNF,因为 [ (pid, uid, iid) -> slotNum ] 和 [ (pid,uid,slotNum) -> iid ]。 但这就是为什么我们对 (pid, uid, slotNum) 有唯一的约束来防止数据进入不一致的状态。

我认为这是一个可行的解决方案。

您可以将一个新字符 (nvarchar) 列添加到名为orderPage表中,该列包含一个按您喜欢的顺序(即1,4,3,2分隔的iid列表。 优点是只需维护一张表中的一个字段 - 明显的缺点是需要编写一个实用程序函数来在字符和数字类型之间进行转换,而这实际上可能不会花费太长时间。

如果您预计项目数量不多,您可以使用第一种方法的修改版本。 只需在连续索引之间设置间隙即可。 例如,第一项有索引 100,第二项有 200 等。这样你就不必每次都更新所有索引,只有在你找不到间隙的情况下

使用方法 1并了解索引更新对性能的影响。 除非您每页处理数百万个项目,否则您不太可能发现性能不足,并且您保留了 SQL 处理数据的所有功能。

除了使用纯非过程 SQL 更难处理之外,方法 2仍然需要您遍历列表以在重新排序项目时找到重新连接“链接”的正确位置。

Page           Item
-----------    ---------------
PK | pid       PK, FK | pid 
   | name      PK     | index 
                      | content 

其中 index 可以是字符串(字典顺序),或者如果您希望按数字排序(是否有间隙取决于特定用例)

复合主键确保您可以相对于任何给定的 pid 进行本地索引,而不是问题中提到的全局“iid”想法。

为什么不像您建议的那样在 C 中构建一个像链表这样的有序列表?

创建一个代表不同列表的“Orderedlist”表。 然后创建一个“ListElement”表,该表具有指向下一个 listElement 的(可为空的)自引用。 OrderedList 表的实例将具有指向名为“StartingElement”的 ListElement 表实例的引用。

如果您需要重新排序,只需更新相关节点的“NextElement”引用;)

如果一些视觉效果可以提供帮助: https://www.geeksforgeeks.org/data-structures/linked-list/

ListElement 表的巧妙排序可能有助于提高 select 操作的性能。 (例如,当重建列表时。递归也可能是一个想法)不知道你到底在调用前端编程,但你可以在 SQL 中创建函数来帮助你在检索数据之前获得结果。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM