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

时间:2012-03-02 15:51:59

标签: database database-design rdbms

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

我有一个页面,用户将放置一些项目。

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

这些项目必须保持用户提供的顺序。但是,该订单可能会被用户更改任意次数。

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

方法1

我原来的想法是给项目一个索引来代表列表中的位置

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

使用此解决方案,您可以选择方便的项where pid = Page.pidorder by index。但是,每次更改订单时,您必须在另一个项目(最佳案例)和所有其他项目(最坏情况)之间进行任何更改。

方法2

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

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

这可能会使订单更改成本更低,但我们必须依靠前端编程来提取订单。

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

6 个答案:

答案 0 :(得分:14)

解决方案:使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。字符串排名的优点是您在两个项目之间用尽了空间,仍然不必重新排名其他任何内容。您只需开始在字符串中添加更多字符以缩小焦点范围。

答案 1 :(得分:5)

我认为@ 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. (页面,用户)标识一组项目。

  5. (页面,用户)标识了一个有序的LIST列表,我们可以根据需要存储项目。

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

  7. 计划A

    杀死上面的details表。

    添加一个表ItemsByPageAndUser,以表示由(页面,用户)标识的项目集。

    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,以表示可能包含项目的有序LIST列表。

    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)
    )
    

    注1 iidInSlot可以为空,以便我们可以根据需要设置空插槽。但如果存在一个项目,则必须根据项目表进行检查。

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

    注释3 (pid, uid, iid)上的唯一约束强制实现我们的设计目标,即在列表中包含唯一项(假设6)。如果没有这个,只要它们位于不同的插槽中,我们就可以在(页面,用户)标识的集合中添加尽可能多的项目。

    现在我们很好地将这些项目从他们的插槽中解耦,同时保持他们对(页面,用户)的共同依赖。

    这个设计肯定在3NF,可能在BCNF,虽然我在这方面担心SlotsByPageAndUser

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

    计划B

    1. 杀死SlotsByPageAndUser表。

    2. slotNum添加ItemsByPageAndUser列。

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

    4. 现在是:

      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可空保留我们指定集合中不在列表中的项目的能力。但是。 。

      Note 5 :对涉及可空列的表达式设置唯一约束可能会在某些数据库中导致“有趣”的结果。我认为它将按照我们在Postgres中的意图行事。 (请参阅此处的this discussion。)对于其他数据库,您的里程可能会有所不同。

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

      这不是BCNF,因为[(pid, uid, iid) - &gt; slotNum]和[(pid,uid,slotNum) - &gt; iid]。但这就是为什么我们在(pid,uid,slotNum)上有唯一约束来阻止数据进入不一致状态。

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

答案 2 :(得分:3)

如果您期望的项目数量不是很大,您可以使用第一种方法的位修改版本。只是在连续索引之间做出差距。例如,第一个项目有索引100,第二个200等。这样,您不必每次都更新所有索引,只有在找不到差距时

答案 3 :(得分:2)

您可以在名为Page的{​​{1}}表中添加一个新字符(nvarchar)列,其中包含您喜欢的order的分隔列表,即{{1} }}。优点是只需要在一个表中保存一个字段 - 明显的缺点是需要编写一个实用程序函数来在字符和数字类型之间进行转换,实际上可能不会花费太长时间。

答案 4 :(得分:2)

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

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

答案 5 :(得分:0)

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

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

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