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

Gre*_*ida 25 database rdbms database-design

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

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

 ________________
| -Item1         |
| -Item2         |
| -Item3         |
| -Item4         |
|________________|
Run Code Online (Sandbox Code Playgroud)

这些项目必须保持用户给出的顺序.然而,用户可以将该顺序改变任意次数.

 ________________
| -Item1         |
| -Item4         |
| -Item2         |
| -Item3         |
|________________|
Run Code Online (Sandbox Code Playgroud)

方法1

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

Page           Item
-----------    ---------------
FK | pid       FK | pid 
   | name      PK | iid 
                  | index
                  | content 
Run Code Online (Sandbox Code Playgroud)

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

方法2

我还考虑制作一个"链表",如数据结构,其中每个项指向列表中的下一个项.

Page           Item
-----------    ---------------
FK | pid       FK | pid 
   | name      PK | iid 
                  | next
                  | content 
Run Code Online (Sandbox Code Playgroud)

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

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

Ale*_*ird 23

解决方案:创建index一个字符串(因为字符串本质上具有无限的"任意精度").或者如果使用int,则增加index100而不是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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

更好的是:这是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
Run Code Online (Sandbox Code Playgroud)

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

  • 对于任何想知道如何实现字母顺序排名解决方案的人,@m69 有 [一个惊人的答案](/sf/ask/2724636351/ Between-two-given- strings/38927158#38927158) 完成所需的 js 和 C 算法 (7认同)
  • 但是,您不能在`0 | hzztzz:`和`0 | hzztzz:a`之间插入任何内容 (5认同)
  • 我能快速找到的最好的:https://confluence.atlassian.com/jirakb/lexorank-management-779159218.html (2认同)
  • @AlexanderBird我正在寻找一个可行的实现,我想说,每次到达`a`时插入第二个字母就可以缓解问题 (2认同)
  • @SamLevin 我在这里有一个基本的实现,只要我测试过它就可以工作:https://github.com/smaspe/taskman/tree/master/src/tools 有时候我有时间我会把它和那个仓库分开并把它变成一个图书馆 (2认同)
  • @njzk2:这个问题的解决方案很简单。我们将这些字符串视为以 B 为基数的分数,并带有无限数量的尾随零。因此,我们插入的所有字符串都应以非零数字结尾,因为零是隐含的。字符串的字典顺序与数字顺序一致。我们总能在任意两个 B-adic 分数之间找到一个 B-adic 数(例如 (X+Y)/2,尽管最好计算分母最小的那个)。如果 [az] 是我们的字母表,而 `a` 是零位数字,则 ` Between(aax, aaxb) == aaxab` 。 (2认同)
  • @Redline JIRA 使用它来引用存储桶编号。它们有 3 个存储桶 - 0、1 和 2。当一个存储桶耗尽所有排名值时,JIRA 将使用下一个存储桶进行称为“平衡”的操作。 (2认同)
  • 在 TypeScript 中有一个很好的实现:[@wewatch/lexorrank](https://github.com/wewatch/lexorrank)。演示显示任意精度的操作:https://codesandbox.io/s/lexorank-demo-bwf1pf?file=/src/index.ts(当然,您通常不会插入这样的项目;这只是为了演示任意精度) (2认同)

Ala*_*lan 6

我认为@ 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)
);
Run Code Online (Sandbox Code Playgroud)

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


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

假设

  1. 有用户.

  2. 有页面.

  3. 有物品.

  4. (页面,用户)标识一组项目.

  5. (页面,用户)标识插槽的有序LIST,如果我们愿意,我们可以在其中存储项目.

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

计划A.

杀死details上面的桌子.

添加表,ItemsByPageAndUser以表示由(页面,用户)标识的项目的SET.

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)   
)
Run Code Online (Sandbox Code Playgroud)

添加表,, 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)
)
Run Code Online (Sandbox Code Playgroud)

注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)to 上添加唯一约束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),
   slotNum int,
 primary key (pid, uid, iid),   
 unique (pid, uid, slotNum)
)
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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


Bar*_*aye 5

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